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 Theme,7} 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 NftIdTuple,25} from './fetch';26import {27 extractRmrkCoreTxResult,28 extractRmrkEquipTxResult, isCollectionPropertyExists, isNftOwnedBy, isNftPropertyExists, isTxResultSuccess, makeNftOwner,29 findResourceById, getResourceById, checkResourceStatus,30} 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: string,41): 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(events, 'CollectionCreated', (data) => {52 return parseInt(data[1].toString(), 10);53 });54 expect(collectionResult.success, 'Error: unable to create a collection').to.be.true;5556 collectionId = collectionResult.successData!;5758 const newCollectionCount = await getCollectionsCount(api);59 const collectionOption = await getCollection(api, collectionId);6061 expect(newCollectionCount).to.be.equal(oldCollectionCount + 1, 'Error: NFT collection count should increase');62 expect(collectionOption.isSome, 'Error: unable to fetch created NFT collection').to.be.true;6364 const collection = collectionOption.unwrap();6566 expect(collection.metadata.toUtf8()).to.be.equal(metadata, 'Error: Invalid NFT collection metadata');67 expect(collection.max.isSome).to.be.equal(max !== null, 'Error: Invalid NFT collection max');6869 if (collection.max.isSome) {70 expect(collection.max.unwrap().toNumber()).to.be.equal(max, 'Error: Invalid NFT collection max');71 }72 expect(collection.symbol.toUtf8()).to.be.equal(symbol, "Error: Invalid NFT collection's symbol");73 expect(collection.nftsCount.toNumber()).to.be.equal(0, "Error: NFT collection shoudn't have any tokens");74 expect(collection.issuer.toString()).to.be.equal(issuer.address, 'Error: Invalid NFT collection issuer');7576 return collectionId;77}7879export async function changeIssuer(80 api: ApiPromise,81 issuerUri: string,82 collectionId: number,83 newIssuer: string,84) {85 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;86 const alice = privateKey(issuerUri, Number(ss58Format));87 const bob = privateKey(newIssuer, Number(ss58Format));8889 90 91 92 93 94 9596 const tx = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address);97 const events = await executeTransaction(api, alice, tx);98 const changeIssuerResult = extractRmrkCoreTxResult(events, 'IssuerChanged', (data) => {99 return parseInt(data[2].toString(), 10);100 });101 expect(changeIssuerResult.success, 'Error: Unable to change NFT collection issuer').to.be.true;102 expect(changeIssuerResult.successData!, 'Error: Invalid collection id after changing the issuer')103 .to.be.eq(collectionId);104105 await getCollection(api, collectionId).then((collectionOption) => {106 const collection = collectionOption.unwrap();107 expect(collection.issuer.toString())108 .to.be.deep.eq(bob.address, 'Error: Invalid NFT collection issuer');109 });110}111112export async function deleteCollection(113 api: ApiPromise,114 issuerUri: string,115 collectionId: string,116): Promise<number> {117 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;118 const issuer = privateKey(issuerUri, Number(ss58Format));119 const tx = api.tx.rmrkCore.destroyCollection(collectionId);120 const events = await executeTransaction(api, issuer, tx);121122 const collectionTxResult = extractRmrkCoreTxResult(123 events,124 'CollectionDestroy',125 (data) => {126 return parseInt(data[1].toString(), 10);127 },128 );129 expect(collectionTxResult.success, 'Error: Unable to delete NFT collection').to.be.true;130131 const collection = await getCollection(132 api,133 parseInt(collectionId, 10),134 );135 expect(collection.isEmpty, 'Error: NFT collection should be deleted').to.be.true;136137 return 0;138}139140export async function negativeDeleteCollection(141 api: ApiPromise,142 issuerUri: string,143 collectionId: string,144): Promise<number> {145 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;146 const issuer = privateKey(issuerUri, Number(ss58Format));147 const tx = api.tx.rmrkCore.destroyCollection(collectionId);148 await expect(executeTransaction(api, issuer, tx)).to.be.rejected;149150 return 0;151}152153export async function setNftProperty(154 api: ApiPromise,155 issuerUri: string,156 collectionId: number,157 nftId: number,158 key: string,159 value: string,160) {161 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;162 const issuer = privateKey(issuerUri, Number(ss58Format));163 const nftIdOpt = api.createType('Option<u32>', nftId);164 const tx = api.tx.rmrkCore.setProperty(165 collectionId,166 nftIdOpt,167 key,168 value,169 );170 const events = await executeTransaction(api, issuer, tx);171172 const propResult = extractRmrkCoreTxResult(events, 'PropertySet', (data) => {173 return {174 collectionId: parseInt(data[0].toString(), 10),175 nftId: data[1] as Option<u32>,176 key: data[2] as Bytes,177 value: data[3] as Bytes,178 };179 });180181 expect(propResult.success, 'Error: Unable to set NFT property').to.be.true;182 const eventData = propResult.successData!;183 const eventDescription = 'from set NFT property event';184185 expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription)186 .to.be.equal(collectionId);187188 expect(eventData.nftId.eq(nftIdOpt), 'Error: Invalid NFT ID ' + eventDescription)189 .to.be.true;190191 expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription)192 .to.be.true;193194 expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription)195 .to.be.true;196197 expect(198 await isNftPropertyExists(api, collectionId, nftId, key, value),199 'Error: NFT property is not found',200 ).to.be.true;201}202203export async function mintNft(204 api: ApiPromise,205 issuerUri: string,206 ownerUri: string | null,207 collectionId: number,208 metadata: string,209 recipientUri: string | null = null,210 royalty: number | null = null,211 transferable = true,212 resources: {basic?: any, composable?: any, slot?: any}[] | null = null,213): Promise<number> {214 let nftId = 0;215 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;216 const issuer = privateKey(issuerUri, Number(ss58Format));217 const owner = ownerUri ? privateKey(ownerUri, Number(ss58Format)).address : null;218 const recipient = recipientUri ? privateKey(recipientUri, Number(ss58Format)).address : null;219 const royaltyOptional = royalty ? royalty.toString() : null;220221 const actualOwnerUri = ownerUri ? ownerUri : issuerUri;222 const actualOwnerAddress = ownerUri ? owner : issuer.address;223224 const collectionOpt = await getCollection(api, collectionId);225226 const tx = api.tx.rmrkCore.mintNft(227 owner,228 collectionId,229 recipient,230 royaltyOptional,231 metadata,232 transferable,233 resources,234 );235236 const events = await executeTransaction(api, issuer, tx);237 const nftResult = extractRmrkCoreTxResult(events, 'NftMinted', (data) => {238 return parseInt(data[2].toString(), 10);239 });240241 expect(nftResult.success, 'Error: Unable to mint NFT').to.be.true;242243 const newCollectionNftsCount = (await getCollection(api, collectionId))244 .unwrap()245 .nftsCount246 .toNumber();247248 const oldCollectionNftsCount = collectionOpt249 .unwrap()250 .nftsCount.toNumber();251252 expect(newCollectionNftsCount, 'Error: NFTs count should increase')253 .to.be.equal(oldCollectionNftsCount + 1);254255 nftId = nftResult.successData!;256257 const nftOption = await getNft(api, collectionId, nftId);258259 expect(nftOption.isSome, 'Error: Unable to fetch created NFT').to.be.true;260261 const nft = nftOption.unwrap();262263 expect(nft.owner.isAccountId, 'Error: NFT owner should be some user').to.be.true;264 expect(nft.owner.asAccountId.toString()).to.be.equal(actualOwnerAddress, 'Error: Invalid NFT owner');265266 const isOwnedInUniques = await isNftOwnedBy(api, actualOwnerUri, collectionId, nftId);267 expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${ownerUri}`)268 .to.be.true;269270 if (recipient === null && royalty === null) {271 expect(nft.royalty.isNone, 'Error: Invalid NFT recipient')272 .to.be.true;273 } else {274 expect(nft.royalty.isSome, 'Error: NFT royalty not found')275 .to.be.true;276277 const nftRoyalty = nft.royalty.unwrap();278 expect(nftRoyalty.recipient.eq(recipient), 'Error: Invalid NFT recipient')279 .to.be.true;280281 expect(nftRoyalty.amount.eq(royalty), 'Error: Invalid NFT royalty')282 .to.be.true;283 }284285 expect(nft.metadata.toUtf8()).to.be.equal(metadata, 'Error: Invalid NFT metadata');286 287 const nftResources = await getResources(api, collectionId, nftId);288 if (resources == null) {289 expect(nftResources, 'Error: Invalid NFT resources').to.be.empty;290 } else {291 expect(nftResources.length).to.be.equal(resources.length);292293 for (let i = 0; i < resources.length; i++) {294 let successFindingResource = false;295 const resource = resources[i];296 297 for (let j = 0; j < nftResources.length && !successFindingResource; j++) {298 const nftResourceData = nftResources[j].toHuman();299 expect(300 Object.prototype.hasOwnProperty.call(nftResourceData, 'resource'),301 `Error: Corrupted resource data on resource #${i}`,302 ).to.be.true;303 const nftResource = nftResourceData.resource!;304 type NftResourceKey = keyof typeof nftResource;305306 let typedResource = null;307 let typedNftResource = null;308309 if (resource.basic && Object.prototype.hasOwnProperty.call(nftResource, 'Basic')) {310 typedResource = resource.basic!;311 typedNftResource = nftResource['Basic' as NftResourceKey]!;312 } else if (resource.composable && Object.prototype.hasOwnProperty.call(nftResource, 'Composable')) {313 typedResource = resource.composable!;314 typedNftResource = nftResource['Composable' as NftResourceKey]! as any;315 if (typedResource.parts != undefined && typedResource.parts.toString() != typedNftResource.parts.toString()316 || typedResource.base != typedNftResource.base && typedResource.base != undefined) {317 continue;318 }319 } else if (resource.slot && Object.prototype.hasOwnProperty.call(nftResource, 'Slot')) {320 typedResource = resource.slot!;321 typedNftResource = nftResource['Slot' as NftResourceKey]! as any;322 if (typedResource.slot != typedNftResource.slot && typedResource.slot != undefined 323 || typedResource.base != typedNftResource.base && typedResource.base != undefined) {324 continue;325 }326 } else {327 continue;328 }329330 if (typedResource.src != typedNftResource.src 331 || typedResource.metadata != typedNftResource.metadata 332 || typedResource.thumb != typedNftResource.thumb333 || typedResource.license != typedNftResource.license334 ) {335 continue;336 }337338 339 expect(nftResourceData.pending, `Error: Resource #${i} is pending`).to.be.false;340 expect(nftResourceData.pendingRemoval, `Error: Resource #${i} is pending removal`).to.be.false;341342 343 nftResources.splice(j, 1);344 successFindingResource = true;345 }346347 expect(successFindingResource, `Error: Couldn't find resource #${i}'s counterpart among the returned`).to.be.true;348 }349 }350351 return nftId;352}353354export async function sendNft(355 api: ApiPromise,356 expectedStatus: 'pending' | 'sent',357 originalOwnerUri: string,358 collectionId: number,359 nftId: number,360 newOwner: string | NftIdTuple,361) {362 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;363 const originalOwner = privateKey(originalOwnerUri, Number(ss58Format));364 const newOwnerObj = makeNftOwner(api, newOwner);365366 const nftBeforeSendingOpt = await getNft(api, collectionId, nftId);367368 const tx = api.tx.rmrkCore.send(collectionId, nftId, newOwnerObj);369 const events = await executeTransaction(api, originalOwner, tx);370371 const sendResult = extractRmrkCoreTxResult(events, 'NFTSent', (data) => {372 return {373 dstOwner: data[1] as NftOwner,374 collectionId: parseInt(data[2].toString(), 10),375 nftId: parseInt(data[3].toString(), 10),376 };377 });378379 expect(sendResult.success, 'Error: Unable to send NFT').to.be.true;380 const sendData = sendResult.successData!;381382 expect(sendData.dstOwner.eq(newOwnerObj), 'Error: Invalid target user (from event data)')383 .to.be.true;384385 expect(sendData.collectionId)386 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');387388 expect(sendData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');389390 expect(nftBeforeSendingOpt.isSome, 'Error: Unable to fetch NFT before sending').to.be.true;391392 const nftBeforeSending = nftBeforeSendingOpt.unwrap();393394 const nftAfterSendingOpt = await getNft(api, collectionId, nftId);395396 expect(nftAfterSendingOpt.isSome, 'Error: Unable to fetch NFT after sending').to.be.true;397398 const nftAfterSending = nftAfterSendingOpt.unwrap();399400 const isOwnedByNewOwner = await isNftOwnedBy(api, newOwner, collectionId, nftId);401 const isPending = expectedStatus === 'pending';402403 expect(404 isOwnedByNewOwner,405 `Error: The NFT should be owned by ${newOwner.toString()}`,406 ).to.be.true;407408 expect(nftAfterSending.royalty.eq(nftBeforeSending.royalty), 'Error: Invalid NFT royalty after sending')409 .to.be.true;410411 expect(nftAfterSending.metadata.eq(nftBeforeSending.metadata), 'Error: Invalid NFT metadata after sending')412 .to.be.true;413414 expect(nftAfterSending.equipped.eq(nftBeforeSending.equipped), 'Error: Invalid NFT equipped status after sending')415 .to.be.true;416417 expect(nftAfterSending.pending.eq(isPending), 'Error: Invalid NFT pending state')418 .to.be.true;419}420421export async function acceptNft(422 api: ApiPromise,423 issuerUri: string,424 collectionId: number,425 nftId: number,426 newOwner: string | [number, number],427) {428 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;429 const issuer = privateKey(issuerUri, Number(ss58Format));430 const newOwnerObj = makeNftOwner(api, newOwner);431432 const nftBeforeOpt = await getNft(api, collectionId, nftId);433434 const tx = api.tx.rmrkCore.acceptNft(collectionId, nftId, newOwnerObj);435 const events = await executeTransaction(api, issuer, tx);436437 const acceptResult = extractRmrkCoreTxResult(events, 'NFTAccepted', (data) => {438 return {439 recipient: data[1] as NftOwner,440 collectionId: parseInt(data[2].toString(), 10),441 nftId: parseInt(data[3].toString(), 10),442 };443 });444445 expect(acceptResult.success, 'Error: Unable to accept NFT').to.be.true;446 const acceptData = acceptResult.successData!;447448 expect(acceptData.recipient.eq(newOwnerObj), 'Error: Invalid NFT recipient (from event data)')449 .to.be.true;450451 expect(acceptData.collectionId)452 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');453454 expect(acceptData.nftId)455 .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');456457 const nftBefore = nftBeforeOpt.unwrap();458459 const isPendingBeforeAccept = nftBefore.pending.isTrue;460461 const nftAfter = (await getNft(api, collectionId, nftId)).unwrap();462 const isPendingAfterAccept = nftAfter.pending.isTrue;463464 expect(isPendingBeforeAccept, 'Error: NFT should be pending to be accepted')465 .to.be.true;466467 expect(isPendingAfterAccept, 'Error: NFT should NOT be pending after accept')468 .to.be.false;469470 const isOwnedInUniques = await isNftOwnedBy(api, newOwner, collectionId, nftId);471 expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${newOwner.toString()}`)472 .to.be.true;473}474475export async function rejectNft(476 api: ApiPromise,477 issuerUri: string,478 collectionId: number,479 nftId: number,480) {481 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;482 const issuer = privateKey(issuerUri, Number(ss58Format));483 const nftBeforeOpt = await getNft(api, collectionId, nftId);484485 const tx = api.tx.rmrkCore.rejectNft(collectionId, nftId);486 const events = await executeTransaction(api, issuer, tx);487 const rejectResult = extractRmrkCoreTxResult(events, 'NFTRejected', (data) => {488 return {489 collectionId: parseInt(data[1].toString(), 10),490 nftId: parseInt(data[2].toString(), 10),491 };492 });493494 const rejectData = rejectResult.successData!;495496 expect(rejectData.collectionId)497 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');498499 expect(rejectData.nftId)500 .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');501502 const nftBefore = nftBeforeOpt.unwrap();503504 const isPendingBeforeReject = nftBefore.pending.isTrue;505506 const nftAfter = await getNft(api, collectionId, nftId);507508 expect(isPendingBeforeReject, 'Error: NFT should be pending to be rejected')509 .to.be.true;510511 expect(nftAfter.isNone, 'Error: NFT should be burned after reject')512 .to.be.true;513}514515export async function createBase(516 api: ApiPromise,517 issuerUri: string,518 baseType: string,519 symbol: string,520 parts: object[],521): Promise<number> {522 let baseId = 0;523 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;524 const issuer = privateKey(issuerUri, Number(ss58Format));525526 const partTypes = api.createType('Vec<RmrkTraitsPartPartType>', parts) as Vec<PartType>;527528 const tx = api.tx.rmrkEquip.createBase(baseType, symbol, partTypes);529 const events = await executeTransaction(api, issuer, tx);530531 const baseResult = extractRmrkEquipTxResult(events, 'BaseCreated', (data) => {532 return parseInt(data[1].toString(), 10);533 });534535 expect(baseResult.success, 'Error: Unable to create Base')536 .to.be.true;537538 baseId = baseResult.successData!;539 const baseOptional = await getBase(api, baseId);540541 expect(baseOptional.isSome, 'Error: Unable to fetch created Base')542 .to.be.true;543544 const base = baseOptional.unwrap();545 const baseParts = await getParts(api, baseId);546547 expect(base.issuer.toString()).to.be.equal(issuer.address, 'Error: Invalid Base issuer');548 expect(base.baseType.toUtf8()).to.be.equal(baseType, 'Error: Invalid Base type');549 expect(base.symbol.toUtf8()).to.be.equal(symbol, 'Error: Invalid Base symbol');550 expect(partTypes.eq(baseParts), 'Error: Received invalid base parts').to.be.true;551552 return baseId;553}554555export async function setResourcePriorities(556 api: ApiPromise,557 issuerUri: string,558 collectionId: number,559 nftId: number,560 priorities: number[],561) {562 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;563 const issuer = privateKey(issuerUri, Number(ss58Format));564565 const prioritiesVec = api.createType('Vec<u32>', priorities);566 const tx = api.tx.rmrkCore.setPriority(collectionId, nftId, prioritiesVec);567 const events = await executeTransaction(api, issuer, tx);568569 const prioResult = extractRmrkCoreTxResult(events, 'PrioritySet', (data) => {570 return {571 collectionId: parseInt(data[0].toString(), 10),572 nftId: parseInt(data[1].toString(), 10),573 };574 });575576 expect(prioResult.success, 'Error: Unable to set resource priorities').to.be.true;577 const eventData = prioResult.successData!;578579 expect(eventData.collectionId)580 .to.be.equal(collectionId, 'Error: Invalid collection ID (set priorities event data)');581582 expect(eventData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (set priorities event data');583584 for (let i = 0; i < priorities.length; i++) {585 const resourceId = priorities[i];586587 const fetchedPrio = await getResourcePriority(api, collectionId, nftId, resourceId);588 expect(fetchedPrio).to.be.equal(i, 'Error: Invalid priorities are set');589 }590591}592593export async function setEquippableList(594 api: ApiPromise,595 issuerUri: string,596 baseId: number,597 slotId: number,598 equippableList: 'All' | 'Empty' | { 'Custom': number[] },599) {600 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;601 const issuer = privateKey(issuerUri, Number(ss58Format));602 const equippable = api.createType('RmrkTraitsPartEquippableList', equippableList) as EquippableList;603604 const tx = api.tx.rmrkEquip.equippable(baseId, slotId, equippable);605 const events = await executeTransaction(api, issuer, tx);606607 const equipListResult = extractRmrkEquipTxResult(events, 'EquippablesUpdated', (data) => {608 return {609 baseId: parseInt(data[0].toString(), 10),610 slotId: parseInt(data[1].toString(), 10),611 };612 });613614 expect(equipListResult.success, 'Error: unable to update equippable list').to.be.true;615 const updateEvent = equipListResult.successData!;616617 expect(updateEvent.baseId)618 .to.be.equal(baseId, 'Error: invalid base ID from update equippable event');619620 expect(updateEvent.slotId)621 .to.be.equal(slotId, 'Error: invalid base ID from update equippable event');622623 const fetchedEquippableList = await getEquippableList(api, baseId, slotId);624625 expect(fetchedEquippableList, 'Error: unable to fetch equippable list').to.be.not.null;626 if (fetchedEquippableList) {627 expect(fetchedEquippableList)628 .to.be.deep.equal(equippableList, 'Error: invalid equippable list was set');629 }630}631632export async function addTheme(633 api: ApiPromise,634 issuerUri: string,635 baseId: number,636 themeObj: object,637 filterKeys: string[] | null = null,638) {639 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;640 const issuer = privateKey(issuerUri, Number(ss58Format));641 const theme = api.createType('RmrkTraitsTheme', themeObj) as Theme;642643 const tx = api.tx.rmrkEquip.themeAdd(baseId, theme);644 const events = await executeTransaction(api, issuer, tx);645646 expect(isTxResultSuccess(events), 'Error: Unable to add Theme').to.be.true;647648 const fetchedThemeOpt = await getTheme(api, baseId, theme.name.toUtf8(), null);649650 expect(fetchedThemeOpt.isSome, 'Error: Unable to fetch theme').to.be.true;651652 const fetchedTheme = fetchedThemeOpt.unwrap();653654 expect(theme.name.eq(fetchedTheme.name), 'Error: Invalid theme name').to.be.true;655656 for (let i = 0; i < theme.properties.length; i++) {657 const property = theme.properties[i];658 const propertyKey = property.key.toUtf8();659660 const propertyFoundCount = fetchedTheme.properties.filter((fetchedProp) => property.key.eq(fetchedProp.key)).length;661662 expect(propertyFoundCount > 1, `Error: Too many properties with key ${propertyKey} found`)663 .to.be.false;664665 if (filterKeys) {666 const isFiltered = fetchedTheme.properties.find((fetchedProp) => fetchedProp.key.eq(property.key)) === undefined;667668 if (isFiltered) {669 expect(propertyFoundCount === 0, `Error: Unexpected filtered key ${propertyKey}`)670 .to.be.true;671 continue;672 }673 }674675 expect(propertyFoundCount === 1, `Error: The property with key ${propertyKey} is not found`)676 .to.be.true;677 }678}679680export async function lockCollection(681 api: ApiPromise,682 issuerUri: string,683 collectionId: number,684 max = 0,685) {686 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;687 const issuer = privateKey(issuerUri, Number(ss58Format));688 const tx = api.tx.rmrkCore.lockCollection(collectionId);689 const events = await executeTransaction(api, issuer, tx);690 const lockResult = extractRmrkCoreTxResult(events, 'CollectionLocked', (data) => {691 return parseInt(data[1].toString(), 10);692 });693 expect(lockResult.success, 'Error: Unable to lock a collection').to.be.true;694 expect(lockResult.successData!, 'Error: Invalid collection was locked')695 .to.be.eq(collectionId);696697 await getCollection(api, collectionId).then((collectionOption) => {698 const collection = collectionOption.unwrap();699 expect(collection.max.unwrap().toNumber()).to.be.equal(max);700 });701}702703export async function setPropertyCollection(704 api: ApiPromise,705 issuerUri: string,706 collectionId: number,707 key: string,708 value: string,709) {710 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;711 const alice = privateKey(issuerUri, Number(ss58Format));712713 const tx = api.tx.rmrkCore.setProperty(collectionId, null, key, value);714 const events = await executeTransaction(api, alice, tx);715 const propResult = extractRmrkCoreTxResult(events, 'PropertySet', (data) => {716 return {717 collectionId: parseInt(data[0].toString(), 10),718 nftId: data[1] as Option<u32>,719 key: data[2] as Bytes,720 value: data[3] as Bytes,721 };722 });723724 expect(propResult.success, 'Error: Unable to set collection property').to.be.true;725 const eventData = propResult.successData!;726 const eventDescription = 'from set collection property event';727728 expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription)729 .to.be.equal(collectionId);730731 expect(eventData.nftId.eq(null), 'Error: Unexpected NFT ID ' + eventDescription)732 .to.be.true;733734 expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription)735 .to.be.true;736737 expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription)738 .to.be.true;739740 expect(await isCollectionPropertyExists(api, collectionId, key, value))741 .to.be.true;742}743744export async function burnNft(745 api: ApiPromise,746 issuerUri: string,747 collectionId: number,748 nftId: number,749) {750 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;751 const issuer = privateKey(issuerUri, Number(ss58Format));752 const maxBurns = 10;753 const tx = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns);754 const events = await executeTransaction(api, issuer, tx);755 const burnResult = extractRmrkCoreTxResult(events, 'NFTBurned', (data) => {756 return parseInt(data[1].toString(), 10);757 });758759 expect(burnResult.success, 'Error: Unable to burn an NFT').to.be.true;760 expect(burnResult.successData!, 'Error: Invalid NFT was burned')761 .to.be.eq(nftId);762763 const nftBurned = await getNft(api, collectionId, nftId);764 expect(nftBurned.isSome).to.be.false;765}766767export async function acceptNftResource(768 api: ApiPromise,769 issuerUri: string,770 collectionId: number,771 nftId: number,772 resourceId: number,773) {774 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;775 const issuer = privateKey(issuerUri, Number(ss58Format));776777 const tx = api.tx.rmrkCore.acceptResource(778 collectionId,779 nftId,780 resourceId,781 );782783 const events = await executeTransaction(api, issuer, tx);784 const acceptResult = extractRmrkCoreTxResult(events, 'ResourceAccepted', (data) => {785 return {786 nftId: parseInt(data[0].toString(), 10),787 resourceId: parseInt(data[1].toString(), 10),788 };789 });790791 expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true;792 expect(acceptResult.successData!.nftId, 'Error: Invalid NFT ID while accepting a resource')793 .to.be.eq(nftId);794 expect(acceptResult.successData!.resourceId, 'Error: Invalid resource ID while accepting a resource')795 .to.be.eq(resourceId);796797 const resource = await getResourceById(api, collectionId, nftId, resourceId);798 checkResourceStatus(resource, 'added');799}800801async function executeResourceCreation(802 api: ApiPromise,803 issuer: IKeyringPair,804 tx: any,805 collectionId: number,806 nftId: number,807 expectedStatus: 'pending' | 'added',808): Promise<ResourceInfo> {809 const events = await executeTransaction(api, issuer, tx);810811 const resourceResult = extractRmrkCoreTxResult(events, 'ResourceAdded', (data) => {812 return parseInt(data[1].toString(), 10);813 });814 expect(resourceResult.success, 'Error: Unable to add resource').to.be.true;815 const resourceId = resourceResult.successData!;816817 const resource = await getResourceById(api, collectionId, nftId, resourceId);818 checkResourceStatus(resource, expectedStatus);819820 return resource;821}822823export async function addNftBasicResource(824 api: ApiPromise,825 issuerUri: string,826 expectedStatus: 'pending' | 'added',827 collectionId: number,828 nftId: number,829 src: string | null,830 metadata: string | null,831 license: string | null,832 thumb: string | null,833): Promise<number> {834 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;835 const issuer = privateKey(issuerUri, Number(ss58Format));836837 const basicResource = api.createType('RmrkTraitsResourceBasicResource', {838 src: src,839 metadata: metadata,840 license: license,841 thumb: thumb,842 }) as BasicResource;843844 const tx = api.tx.rmrkCore.addBasicResource(845 collectionId,846 nftId,847 basicResource,848 );849850 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);851852 853 854 855 856 857 858 const resourceJson = resource.resource.toHuman() as any;859860 expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Basic'), 'Error: Expected basic resource type')861 .to.be.true;862863 const recvBasicRes = resourceJson['Basic'];864865 expect(recvBasicRes.src, 'Error: Invalid basic resource src')866 .to.be.eq(src);867 expect(recvBasicRes.metadata, 'Error: basic first resource metadata')868 .to.be.eq(metadata);869 expect(recvBasicRes.license, 'Error: basic first resource license')870 .to.be.eq(license);871 expect(recvBasicRes.thumb, 'Error: basic first resource thumb')872 .to.be.eq(thumb);873874 return resource.id.toNumber();875}876877export async function addNftComposableResource(878 api: ApiPromise,879 issuerUri: string,880 expectedStatus: 'pending' | 'added',881 collectionId: number,882 nftId: number,883 parts: number[],884 baseId: number,885 src: string | null,886 metadata: string | null,887 license: string | null,888 thumb: string | null,889): Promise<number> {890 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;891 const issuer = privateKey(issuerUri, Number(ss58Format));892893 const composableResource = api.createType('RmrkTraitsResourceComposableResource', {894 parts: parts, 895 base: baseId,896 src: src,897 metadata: metadata,898 license: license,899 thumb: thumb,900 }) as ComposableResource;901902 const tx = api.tx.rmrkCore.addComposableResource(903 collectionId,904 nftId,905 composableResource,906 );907908 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);909910 911 912 913 914 915 916 const resourceJson = resource.resource.toHuman() as any;917918 expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Composable'), 'Error: Expected composable resource type')919 .to.be.true;920921 const recvComposableRes = resourceJson['Composable'];922923 expect(recvComposableRes.parts.toString(), 'Error: Invalid composable resource parts')924 .to.be.eq(parts.toString());925 expect(recvComposableRes.base, 'Error: Invalid composable resource base id')926 .to.be.eq(baseId.toString());927 expect(recvComposableRes.src, 'Error: Invalid composable resource src')928 .to.be.eq(src);929 expect(recvComposableRes.metadata, 'Error: Invalid composable resource metadata')930 .to.be.eq(metadata);931 expect(recvComposableRes.license, 'Error: Invalid composable resource license')932 .to.be.eq(license);933 expect(recvComposableRes.thumb, 'Error: Invalid composable resource thumb')934 .to.be.eq(thumb);935936 return resource.id.toNumber();937}938939export async function addNftSlotResource(940 api: ApiPromise,941 issuerUri: string,942 expectedStatus: 'pending' | 'added',943 collectionId: number,944 nftId: number,945 baseId: number,946 slotId: number,947 src: string | null,948 metadata: string | null,949 license: string | null,950 thumb: string | null,951): Promise<number> {952 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;953 const issuer = privateKey(issuerUri, Number(ss58Format));954955 const slotResource = api.createType('RmrkTraitsResourceSlotResource', {956 base: baseId,957 src: src,958 metadata,959 slot: slotId,960 license: license,961 thumb: thumb,962 }) as SlotResource;963964 const tx = api.tx.rmrkCore.addSlotResource(965 collectionId,966 nftId,967 slotResource,968 );969970 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);971972 973 974 975 976 977 978 const resourceJson = resource.resource.toHuman() as any;979980 expect(Object.prototype.hasOwnProperty.call(resourceJson, 'Slot'), 'Error: Expected slot resource type')981 .to.be.true;982983 const recvSlotRes = resourceJson['Slot'];984985 expect(recvSlotRes.base, 'Error: Invalid slot resource base id')986 .to.be.eq(baseId.toString());987 expect(recvSlotRes.slot, 'Error: Invalid slot resource slot id')988 .to.be.eq(slotId.toString());989 expect(recvSlotRes.src, 'Error: Invalid slot resource src')990 .to.be.eq(src);991 expect(recvSlotRes.metadata, 'Error: Invalid slot resource metadata')992 .to.be.eq(metadata);993 expect(recvSlotRes.license, 'Error: Invalid slot resource license')994 .to.be.eq(license);995 expect(recvSlotRes.thumb, 'Error: Invalid slot resource thumb')996 .to.be.eq(thumb);997998 return resource.id.toNumber();999}10001001export async function equipNft(1002 api: ApiPromise,1003 issuerUri: string,1004 item: NftIdTuple,1005 equipper: NftIdTuple,1006 resource: number,1007 base: number,1008 slot: number,1009) {1010 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1011 const issuer = privateKey(issuerUri, Number(ss58Format));1012 const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot);1013 const events = await executeTransaction(api, issuer, tx);1014 const equipResult = extractRmrkEquipTxResult(events, 'SlotEquipped', (data) => {1015 return {1016 item_collection: parseInt(data[0].toString(), 10),1017 item_nft: parseInt(data[1].toString(), 10),1018 base_id: parseInt(data[2].toString(), 10),1019 slot_id: parseInt(data[3].toString(), 10),1020 };1021 });1022 expect(equipResult.success, 'Error: Unable to equip an item').to.be.true;1023 expect(equipResult.successData!.item_collection, 'Error: Invalid item collection id')1024 .to.be.eq(item[0]);1025 expect(equipResult.successData!.item_nft, 'Error: Invalid item NFT id')1026 .to.be.eq(item[1]);1027 expect(equipResult.successData!.base_id, 'Error: Invalid base id')1028 .to.be.eq(base);1029 expect(equipResult.successData!.slot_id, 'Error: Invalid slot id')1030 .to.be.eq(slot);1031}10321033export async function unequipNft(1034 api: ApiPromise,1035 issuerUri: string,1036 item: any,1037 equipper: any,1038 resource: number,1039 base: number,1040 slot: number,1041) {1042 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1043 const issuer = privateKey(issuerUri, Number(ss58Format));1044 const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot);1045 const events = await executeTransaction(api, issuer, tx);10461047 const unEquipResult = extractRmrkEquipTxResult(1048 events,1049 'SlotUnequipped',1050 (data) => {1051 return {1052 item_collection: parseInt(data[0].toString(), 10),1053 item_nft: parseInt(data[1].toString(), 10),1054 base_id: parseInt(data[2].toString(), 10),1055 slot_id: parseInt(data[3].toString(), 10),1056 };1057 },1058 );10591060 expect(unEquipResult.success, 'Error: Unable to unequip an item').to.be.true;1061 expect(unEquipResult.successData!.item_collection, 'Error: Invalid item collection id')1062 .to.be.eq(item[0]);1063 expect(unEquipResult.successData!.item_nft, 'Error: Invalid item NFT id')1064 .to.be.eq(item[1]);1065 expect(unEquipResult.successData!.base_id, 'Error: Invalid base id')1066 .to.be.eq(base);1067 expect(unEquipResult.successData!.slot_id, 'Error: Invalid slot id')1068 .to.be.eq(slot);1069}10701071export async function removeNftResource(1072 api: ApiPromise,1073 expectedStatus: 'pending' | 'removed',1074 issuerUri: string,1075 collectionId: number,1076 nftId: number,1077 resourceId: number,1078) {1079 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1080 const issuer = privateKey(issuerUri, Number(ss58Format));10811082 const tx = api.tx.rmrkCore.removeResource(collectionId, nftId, resourceId);1083 const events = await executeTransaction(api, issuer, tx);1084 const removeResult = extractRmrkCoreTxResult(events, 'ResourceRemoval', (data) => {1085 return {1086 nftId: parseInt(data[0].toString(), 10),1087 resourceId: parseInt(data[1].toString(), 10),1088 };1089 });1090 expect(removeResult.success, 'Error: Unable to remove a resource').to.be.true;1091 expect(removeResult.successData!.nftId, 'Error: Invalid NFT Id while removing a resource')1092 .to.be.eq(nftId);1093 expect(removeResult.successData!.resourceId, 'Error: Invalid resource Id while removing a resource')1094 .to.be.eq(resourceId);10951096 const afterDeleting = await findResourceById(api, collectionId, nftId, resourceId);10971098 if (expectedStatus === 'pending') {1099 expect(afterDeleting).not.to.be.null;1100 expect(afterDeleting?.pendingRemoval.isTrue).to.be.equal(true);1101 } else {1102 expect(afterDeleting).to.be.null;1103 }1104}11051106export async function acceptResourceRemoval(1107 api: ApiPromise,1108 issuerUri: string,1109 collectionId: number,1110 nftId: number,1111 resourceId: number,1112) {1113 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1114 const issuer = privateKey(issuerUri, Number(ss58Format));11151116 const tx = api.tx.rmrkCore.acceptResourceRemoval(collectionId, nftId, resourceId);1117 const events = await executeTransaction(api, issuer, tx);1118 const acceptResult = extractRmrkCoreTxResult(events, 'ResourceRemovalAccepted', (data) => {1119 return {1120 nftId: parseInt(data[0].toString(), 10),1121 resourceId: parseInt(data[1].toString(), 10),1122 };1123 });1124 expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true;1125 expect(acceptResult.successData!.nftId, 'Error: Invalid NFT Id while accepting a resource')1126 .to.be.eq(nftId);1127 expect(acceptResult.successData!.resourceId, 'Error: Invalid resource Id while accepting a resource')1128 .to.be.eq(resourceId);11291130 const afterDeleting = await findResourceById(api, collectionId, nftId, resourceId);1131 expect(afterDeleting, 'Error: resource deleting failed').to.be.null;1132}