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(nftResourceData.hasOwnProperty('resource'), `Error: Corrupted resource data on resource #${i}`);300 const nftResource = nftResourceData.resource!;301 type NftResourceKey = keyof typeof nftResource;302303 let typedResource = null;304 let typedNftResource = null;305306 if (resource.basic && nftResource.hasOwnProperty('Basic')) {307 typedResource = resource.basic!;308 typedNftResource = nftResource['Basic' as NftResourceKey]!;309 } else if (resource.composable && nftResource.hasOwnProperty('Composable')) {310 typedResource = resource.composable!;311 typedNftResource = nftResource['Composable' as NftResourceKey]! as any;312 if (typedResource.parts != undefined && typedResource.parts.toString() != typedNftResource.parts.toString()313 || typedResource.base != typedNftResource.base && typedResource.base != undefined) {314 continue;315 }316 } else if (resource.slot && nftResource.hasOwnProperty('Slot')) {317 typedResource = resource.slot!;318 typedNftResource = nftResource['Slot' as NftResourceKey]! as any;319 if (typedResource.slot != typedNftResource.slot && typedResource.slot != undefined 320 || typedResource.base != typedNftResource.base && typedResource.base != undefined) {321 continue;322 }323 } else {324 continue;325 }326327 if (typedResource.src != typedNftResource.src 328 || typedResource.metadata != typedNftResource.metadata 329 || typedResource.thumb != typedNftResource.thumb330 || typedResource.license != typedNftResource.license331 ) {332 continue;333 }334335 336 expect(nftResourceData.pending, `Error: Resource #${i} is pending`).to.be.false;337 expect(nftResourceData.pendingRemoval, `Error: Resource #${i} is pending removal`).to.be.false;338339 340 nftResources.splice(j, 1);341 successFindingResource = true;342 }343344 expect(successFindingResource, `Error: Couldn't find resource #${i}'s counterpart among the returned`).to.be.true;345 }346 }347348 return nftId;349}350351export async function sendNft(352 api: ApiPromise,353 expectedStatus: 'pending' | 'sent',354 originalOwnerUri: string,355 collectionId: number,356 nftId: number,357 newOwner: string | NftIdTuple,358) {359 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;360 const originalOwner = privateKey(originalOwnerUri, Number(ss58Format));361 const newOwnerObj = makeNftOwner(api, newOwner);362363 const nftBeforeSendingOpt = await getNft(api, collectionId, nftId);364365 const tx = api.tx.rmrkCore.send(collectionId, nftId, newOwnerObj);366 const events = await executeTransaction(api, originalOwner, tx);367368 const sendResult = extractRmrkCoreTxResult(events, 'NFTSent', (data) => {369 return {370 dstOwner: data[1] as NftOwner,371 collectionId: parseInt(data[2].toString(), 10),372 nftId: parseInt(data[3].toString(), 10),373 };374 });375376 expect(sendResult.success, 'Error: Unable to send NFT').to.be.true;377 const sendData = sendResult.successData!;378379 expect(sendData.dstOwner.eq(newOwnerObj), 'Error: Invalid target user (from event data)')380 .to.be.true;381382 expect(sendData.collectionId)383 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');384385 expect(sendData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');386387 expect(nftBeforeSendingOpt.isSome, 'Error: Unable to fetch NFT before sending').to.be.true;388389 const nftBeforeSending = nftBeforeSendingOpt.unwrap();390391 const nftAfterSendingOpt = await getNft(api, collectionId, nftId);392393 expect(nftAfterSendingOpt.isSome, 'Error: Unable to fetch NFT after sending').to.be.true;394395 const nftAfterSending = nftAfterSendingOpt.unwrap();396397 const isOwnedByNewOwner = await isNftOwnedBy(api, newOwner, collectionId, nftId);398 const isPending = expectedStatus === 'pending';399400 expect(401 isOwnedByNewOwner,402 `Error: The NFT should be owned by ${newOwner.toString()}`,403 ).to.be.true;404405 expect(nftAfterSending.royalty.eq(nftBeforeSending.royalty), 'Error: Invalid NFT royalty after sending')406 .to.be.true;407408 expect(nftAfterSending.metadata.eq(nftBeforeSending.metadata), 'Error: Invalid NFT metadata after sending')409 .to.be.true;410411 expect(nftAfterSending.equipped.eq(nftBeforeSending.equipped), 'Error: Invalid NFT equipped status after sending')412 .to.be.true;413414 expect(nftAfterSending.pending.eq(isPending), 'Error: Invalid NFT pending state')415 .to.be.true;416}417418export async function acceptNft(419 api: ApiPromise,420 issuerUri: string,421 collectionId: number,422 nftId: number,423 newOwner: string | [number, number],424) {425 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;426 const issuer = privateKey(issuerUri, Number(ss58Format));427 const newOwnerObj = makeNftOwner(api, newOwner);428429 const nftBeforeOpt = await getNft(api, collectionId, nftId);430431 const tx = api.tx.rmrkCore.acceptNft(collectionId, nftId, newOwnerObj);432 const events = await executeTransaction(api, issuer, tx);433434 const acceptResult = extractRmrkCoreTxResult(events, 'NFTAccepted', (data) => {435 return {436 recipient: data[1] as NftOwner,437 collectionId: parseInt(data[2].toString(), 10),438 nftId: parseInt(data[3].toString(), 10),439 };440 });441442 expect(acceptResult.success, 'Error: Unable to accept NFT').to.be.true;443 const acceptData = acceptResult.successData!;444445 expect(acceptData.recipient.eq(newOwnerObj), 'Error: Invalid NFT recipient (from event data)')446 .to.be.true;447448 expect(acceptData.collectionId)449 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');450451 expect(acceptData.nftId)452 .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');453454 const nftBefore = nftBeforeOpt.unwrap();455456 const isPendingBeforeAccept = nftBefore.pending.isTrue;457458 const nftAfter = (await getNft(api, collectionId, nftId)).unwrap();459 const isPendingAfterAccept = nftAfter.pending.isTrue;460461 expect(isPendingBeforeAccept, 'Error: NFT should be pending to be accepted')462 .to.be.true;463464 expect(isPendingAfterAccept, 'Error: NFT should NOT be pending after accept')465 .to.be.false;466467 const isOwnedInUniques = await isNftOwnedBy(api, newOwner, collectionId, nftId);468 expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${newOwner.toString()}`)469 .to.be.true;470}471472export async function rejectNft(473 api: ApiPromise,474 issuerUri: string,475 collectionId: number,476 nftId: number,477) {478 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;479 const issuer = privateKey(issuerUri, Number(ss58Format));480 const nftBeforeOpt = await getNft(api, collectionId, nftId);481482 const tx = api.tx.rmrkCore.rejectNft(collectionId, nftId);483 const events = await executeTransaction(api, issuer, tx);484 const rejectResult = extractRmrkCoreTxResult(events, 'NFTRejected', (data) => {485 return {486 collectionId: parseInt(data[1].toString(), 10),487 nftId: parseInt(data[2].toString(), 10),488 };489 });490491 const rejectData = rejectResult.successData!;492493 expect(rejectData.collectionId)494 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');495496 expect(rejectData.nftId)497 .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');498499 const nftBefore = nftBeforeOpt.unwrap();500501 const isPendingBeforeReject = nftBefore.pending.isTrue;502503 const nftAfter = await getNft(api, collectionId, nftId);504505 expect(isPendingBeforeReject, 'Error: NFT should be pending to be rejected')506 .to.be.true;507508 expect(nftAfter.isNone, 'Error: NFT should be burned after reject')509 .to.be.true;510}511512export async function createBase(513 api: ApiPromise,514 issuerUri: string,515 baseType: string,516 symbol: string,517 parts: object[],518): Promise<number> {519 let baseId = 0;520 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;521 const issuer = privateKey(issuerUri, Number(ss58Format));522523 const partTypes = api.createType('Vec<RmrkTraitsPartPartType>', parts) as Vec<PartType>;524525 const tx = api.tx.rmrkEquip.createBase(baseType, symbol, partTypes);526 const events = await executeTransaction(api, issuer, tx);527528 const baseResult = extractRmrkEquipTxResult(events, 'BaseCreated', (data) => {529 return parseInt(data[1].toString(), 10);530 });531532 expect(baseResult.success, 'Error: Unable to create Base')533 .to.be.true;534535 baseId = baseResult.successData!;536 const baseOptional = await getBase(api, baseId);537538 expect(baseOptional.isSome, 'Error: Unable to fetch created Base')539 .to.be.true;540541 const base = baseOptional.unwrap();542 const baseParts = await getParts(api, baseId);543544 expect(base.issuer.toString()).to.be.equal(issuer.address, 'Error: Invalid Base issuer');545 expect(base.baseType.toUtf8()).to.be.equal(baseType, 'Error: Invalid Base type');546 expect(base.symbol.toUtf8()).to.be.equal(symbol, 'Error: Invalid Base symbol');547 expect(partTypes.eq(baseParts), 'Error: Received invalid base parts').to.be.true;548549 return baseId;550}551552export async function setResourcePriorities(553 api: ApiPromise,554 issuerUri: string,555 collectionId: number,556 nftId: number,557 priorities: number[],558) {559 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;560 const issuer = privateKey(issuerUri, Number(ss58Format));561562 const prioritiesVec = api.createType('Vec<u32>', priorities);563 const tx = api.tx.rmrkCore.setPriority(collectionId, nftId, prioritiesVec);564 const events = await executeTransaction(api, issuer, tx);565566 const prioResult = extractRmrkCoreTxResult(events, 'PrioritySet', (data) => {567 return {568 collectionId: parseInt(data[0].toString(), 10),569 nftId: parseInt(data[1].toString(), 10),570 };571 });572573 expect(prioResult.success, 'Error: Unable to set resource priorities').to.be.true;574 const eventData = prioResult.successData!;575576 expect(eventData.collectionId)577 .to.be.equal(collectionId, 'Error: Invalid collection ID (set priorities event data)');578579 expect(eventData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (set priorities event data');580581 for (let i = 0; i < priorities.length; i++) {582 const resourceId = priorities[i];583584 const fetchedPrio = await getResourcePriority(api, collectionId, nftId, resourceId);585 expect(fetchedPrio).to.be.equal(i, 'Error: Invalid priorities are set');586 }587588}589590export async function setEquippableList(591 api: ApiPromise,592 issuerUri: string,593 baseId: number,594 slotId: number,595 equippableList: 'All' | 'Empty' | { 'Custom': number[] },596) {597 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;598 const issuer = privateKey(issuerUri, Number(ss58Format));599 const equippable = api.createType('RmrkTraitsPartEquippableList', equippableList) as EquippableList;600601 const tx = api.tx.rmrkEquip.equippable(baseId, slotId, equippable);602 const events = await executeTransaction(api, issuer, tx);603604 const equipListResult = extractRmrkEquipTxResult(events, 'EquippablesUpdated', (data) => {605 return {606 baseId: parseInt(data[0].toString(), 10),607 slotId: parseInt(data[1].toString(), 10),608 };609 });610611 expect(equipListResult.success, 'Error: unable to update equippable list').to.be.true;612 const updateEvent = equipListResult.successData!;613614 expect(updateEvent.baseId)615 .to.be.equal(baseId, 'Error: invalid base ID from update equippable event');616617 expect(updateEvent.slotId)618 .to.be.equal(slotId, 'Error: invalid base ID from update equippable event');619620 const fetchedEquippableList = await getEquippableList(api, baseId, slotId);621622 expect(fetchedEquippableList, 'Error: unable to fetch equippable list').to.be.not.null;623 if (fetchedEquippableList) {624 expect(fetchedEquippableList)625 .to.be.deep.equal(equippableList, 'Error: invalid equippable list was set');626 }627}628629export async function addTheme(630 api: ApiPromise,631 issuerUri: string,632 baseId: number,633 themeObj: object,634 filterKeys: string[] | null = null,635) {636 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;637 const issuer = privateKey(issuerUri, Number(ss58Format));638 const theme = api.createType('RmrkTraitsTheme', themeObj) as Theme;639640 const tx = api.tx.rmrkEquip.themeAdd(baseId, theme);641 const events = await executeTransaction(api, issuer, tx);642643 expect(isTxResultSuccess(events), 'Error: Unable to add Theme').to.be.true;644645 const fetchedThemeOpt = await getTheme(api, baseId, theme.name.toUtf8(), null);646647 expect(fetchedThemeOpt.isSome, 'Error: Unable to fetch theme').to.be.true;648649 const fetchedTheme = fetchedThemeOpt.unwrap();650651 expect(theme.name.eq(fetchedTheme.name), 'Error: Invalid theme name').to.be.true;652653 for (let i = 0; i < theme.properties.length; i++) {654 const property = theme.properties[i];655 const propertyKey = property.key.toUtf8();656657 const propertyFoundCount = fetchedTheme.properties.filter((fetchedProp) => property.key.eq(fetchedProp.key)).length;658659 expect(propertyFoundCount > 1, `Error: Too many properties with key ${propertyKey} found`)660 .to.be.false;661662 if (filterKeys) {663 const isFiltered = fetchedTheme.properties.find((fetchedProp) => fetchedProp.key.eq(property.key)) === undefined;664665 if (isFiltered) {666 expect(propertyFoundCount === 0, `Error: Unexpected filtered key ${propertyKey}`)667 .to.be.true;668 continue;669 }670 }671672 expect(propertyFoundCount === 1, `Error: The property with key ${propertyKey} is not found`)673 .to.be.true;674 }675}676677export async function lockCollection(678 api: ApiPromise,679 issuerUri: string,680 collectionId: number,681 max = 0,682) {683 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;684 const issuer = privateKey(issuerUri, Number(ss58Format));685 const tx = api.tx.rmrkCore.lockCollection(collectionId);686 const events = await executeTransaction(api, issuer, tx);687 const lockResult = extractRmrkCoreTxResult(events, 'CollectionLocked', (data) => {688 return parseInt(data[1].toString(), 10);689 });690 expect(lockResult.success, 'Error: Unable to lock a collection').to.be.true;691 expect(lockResult.successData!, 'Error: Invalid collection was locked')692 .to.be.eq(collectionId);693694 await getCollection(api, collectionId).then((collectionOption) => {695 const collection = collectionOption.unwrap();696 expect(collection.max.unwrap().toNumber()).to.be.equal(max);697 });698}699700export async function setPropertyCollection(701 api: ApiPromise,702 issuerUri: string,703 collectionId: number,704 key: string,705 value: string,706) {707 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;708 const alice = privateKey(issuerUri, Number(ss58Format));709710 const tx = api.tx.rmrkCore.setProperty(collectionId, null, key, value);711 const events = await executeTransaction(api, alice, tx);712 const propResult = extractRmrkCoreTxResult(events, 'PropertySet', (data) => {713 return {714 collectionId: parseInt(data[0].toString(), 10),715 nftId: data[1] as Option<u32>,716 key: data[2] as Bytes,717 value: data[3] as Bytes,718 };719 });720721 expect(propResult.success, 'Error: Unable to set collection property').to.be.true;722 const eventData = propResult.successData!;723 const eventDescription = 'from set collection property event';724725 expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription)726 .to.be.equal(collectionId);727728 expect(eventData.nftId.eq(null), 'Error: Unexpected NFT ID ' + eventDescription)729 .to.be.true;730731 expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription)732 .to.be.true;733734 expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription)735 .to.be.true;736737 expect(await isCollectionPropertyExists(api, collectionId, key, value))738 .to.be.true;739}740741export async function burnNft(742 api: ApiPromise,743 issuerUri: string,744 collectionId: number,745 nftId: number,746) {747 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;748 const issuer = privateKey(issuerUri, Number(ss58Format));749 const maxBurns = 10;750 const tx = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns);751 const events = await executeTransaction(api, issuer, tx);752 const burnResult = extractRmrkCoreTxResult(events, 'NFTBurned', (data) => {753 return parseInt(data[1].toString(), 10);754 });755756 expect(burnResult.success, 'Error: Unable to burn an NFT').to.be.true;757 expect(burnResult.successData!, 'Error: Invalid NFT was burned')758 .to.be.eq(nftId);759760 const nftBurned = await getNft(api, collectionId, nftId);761 expect(nftBurned.isSome).to.be.false;762}763764export async function acceptNftResource(765 api: ApiPromise,766 issuerUri: string,767 collectionId: number,768 nftId: number,769 resourceId: number,770) {771 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;772 const issuer = privateKey(issuerUri, Number(ss58Format));773774 const tx = api.tx.rmrkCore.acceptResource(775 collectionId,776 nftId,777 resourceId,778 );779780 const events = await executeTransaction(api, issuer, tx);781 const acceptResult = extractRmrkCoreTxResult(events, 'ResourceAccepted', (data) => {782 return {783 nftId: parseInt(data[0].toString(), 10),784 resourceId: parseInt(data[1].toString(), 10),785 };786 });787788 expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true;789 expect(acceptResult.successData!.nftId, 'Error: Invalid NFT ID while accepting a resource')790 .to.be.eq(nftId);791 expect(acceptResult.successData!.resourceId, 'Error: Invalid resource ID while accepting a resource')792 .to.be.eq(resourceId);793794 const resource = await getResourceById(api, collectionId, nftId, resourceId);795 checkResourceStatus(resource, 'added');796}797798async function executeResourceCreation(799 api: ApiPromise,800 issuer: IKeyringPair,801 tx: any,802 collectionId: number,803 nftId: number,804 expectedStatus: 'pending' | 'added',805): Promise<ResourceInfo> {806 const events = await executeTransaction(api, issuer, tx);807808 const resourceResult = extractRmrkCoreTxResult(events, 'ResourceAdded', (data) => {809 return parseInt(data[1].toString(), 10);810 });811 expect(resourceResult.success, 'Error: Unable to add resource').to.be.true;812 const resourceId = resourceResult.successData!;813814 const resource = await getResourceById(api, collectionId, nftId, resourceId);815 checkResourceStatus(resource, expectedStatus);816817 return resource;818}819820export async function addNftBasicResource(821 api: ApiPromise,822 issuerUri: string,823 expectedStatus: 'pending' | 'added',824 collectionId: number,825 nftId: number,826 src: string | null,827 metadata: string | null,828 license: string | null,829 thumb: string | null,830): Promise<number> {831 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;832 const issuer = privateKey(issuerUri, Number(ss58Format));833834 const basicResource = api.createType('RmrkTraitsResourceBasicResource', {835 src: src,836 metadata: metadata,837 license: license,838 thumb: thumb,839 }) as BasicResource;840841 const tx = api.tx.rmrkCore.addBasicResource(842 collectionId,843 nftId,844 basicResource,845 );846847 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);848849 850 851 852 853 854 855 const resourceJson = resource.resource.toHuman() as any;856857 expect(resourceJson.hasOwnProperty('Basic'), 'Error: Expected basic resource type')858 .to.be.true;859860 const recvBasicRes = resourceJson['Basic'];861862 expect(recvBasicRes.src, 'Error: Invalid basic resource src')863 .to.be.eq(src);864 expect(recvBasicRes.metadata, 'Error: basic first resource metadata')865 .to.be.eq(metadata);866 expect(recvBasicRes.license, 'Error: basic first resource license')867 .to.be.eq(license);868 expect(recvBasicRes.thumb, 'Error: basic first resource thumb')869 .to.be.eq(thumb);870871 return resource.id.toNumber();872}873874export async function addNftComposableResource(875 api: ApiPromise,876 issuerUri: string,877 expectedStatus: 'pending' | 'added',878 collectionId: number,879 nftId: number,880 parts: number[],881 baseId: number,882 src: string | null,883 metadata: string | null,884 license: string | null,885 thumb: string | null,886): Promise<number> {887 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;888 const issuer = privateKey(issuerUri, Number(ss58Format));889890 const composableResource = api.createType('RmrkTraitsResourceComposableResource', {891 parts: parts, 892 base: baseId,893 src: src,894 metadata: metadata,895 license: license,896 thumb: thumb,897 }) as ComposableResource;898899 const tx = api.tx.rmrkCore.addComposableResource(900 collectionId,901 nftId,902 composableResource,903 );904905 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);906907 908 909 910 911 912 913 const resourceJson = resource.resource.toHuman() as any;914915 expect(resourceJson.hasOwnProperty('Composable'), 'Error: Expected composable resource type')916 .to.be.true;917918 const recvComposableRes = resourceJson['Composable'];919920 expect(recvComposableRes.parts.toString(), 'Error: Invalid composable resource parts')921 .to.be.eq(parts.toString());922 expect(recvComposableRes.base, 'Error: Invalid composable resource base id')923 .to.be.eq(baseId.toString());924 expect(recvComposableRes.src, 'Error: Invalid composable resource src')925 .to.be.eq(src);926 expect(recvComposableRes.metadata, 'Error: Invalid composable resource metadata')927 .to.be.eq(metadata);928 expect(recvComposableRes.license, 'Error: Invalid composable resource license')929 .to.be.eq(license);930 expect(recvComposableRes.thumb, 'Error: Invalid composable resource thumb')931 .to.be.eq(thumb);932933 return resource.id.toNumber();934}935936export async function addNftSlotResource(937 api: ApiPromise,938 issuerUri: string,939 expectedStatus: 'pending' | 'added',940 collectionId: number,941 nftId: number,942 baseId: number,943 slotId: number,944 src: string | null,945 metadata: string | null,946 license: string | null,947 thumb: string | null,948): Promise<number> {949 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;950 const issuer = privateKey(issuerUri, Number(ss58Format));951952 const slotResource = api.createType('RmrkTraitsResourceSlotResource', {953 base: baseId,954 src: src,955 metadata,956 slot: slotId,957 license: license,958 thumb: thumb,959 }) as SlotResource;960961 const tx = api.tx.rmrkCore.addSlotResource(962 collectionId,963 nftId,964 slotResource,965 );966967 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);968969 970 971 972 973 974 975 const resourceJson = resource.resource.toHuman() as any;976977 expect(resourceJson.hasOwnProperty('Slot'), 'Error: Expected slot resource type')978 .to.be.true;979980 const recvSlotRes = resourceJson['Slot'];981982 expect(recvSlotRes.base, 'Error: Invalid slot resource base id')983 .to.be.eq(baseId.toString());984 expect(recvSlotRes.slot, 'Error: Invalid slot resource slot id')985 .to.be.eq(slotId.toString());986 expect(recvSlotRes.src, 'Error: Invalid slot resource src')987 .to.be.eq(src);988 expect(recvSlotRes.metadata, 'Error: Invalid slot resource metadata')989 .to.be.eq(metadata);990 expect(recvSlotRes.license, 'Error: Invalid slot resource license')991 .to.be.eq(license);992 expect(recvSlotRes.thumb, 'Error: Invalid slot resource thumb')993 .to.be.eq(thumb);994995 return resource.id.toNumber();996}997998export async function equipNft(999 api: ApiPromise,1000 issuerUri: string,1001 item: NftIdTuple,1002 equipper: NftIdTuple,1003 resource: number,1004 base: number,1005 slot: number,1006) {1007 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1008 const issuer = privateKey(issuerUri, Number(ss58Format));1009 const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot);1010 const events = await executeTransaction(api, issuer, tx);1011 const equipResult = extractRmrkEquipTxResult(events, 'SlotEquipped', (data) => {1012 return {1013 item_collection: parseInt(data[0].toString(), 10),1014 item_nft: parseInt(data[1].toString(), 10),1015 base_id: parseInt(data[2].toString(), 10),1016 slot_id: parseInt(data[3].toString(), 10),1017 };1018 });1019 expect(equipResult.success, 'Error: Unable to equip an item').to.be.true;1020 expect(equipResult.successData!.item_collection, 'Error: Invalid item collection id')1021 .to.be.eq(item[0]);1022 expect(equipResult.successData!.item_nft, 'Error: Invalid item NFT id')1023 .to.be.eq(item[1]);1024 expect(equipResult.successData!.base_id, 'Error: Invalid base id')1025 .to.be.eq(base);1026 expect(equipResult.successData!.slot_id, 'Error: Invalid slot id')1027 .to.be.eq(slot);1028}10291030export async function unequipNft(1031 api: ApiPromise,1032 issuerUri: string,1033 item: any,1034 equipper: any,1035 resource: number,1036 base: number,1037 slot: number,1038) {1039 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1040 const issuer = privateKey(issuerUri, Number(ss58Format));1041 const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot);1042 const events = await executeTransaction(api, issuer, tx);10431044 const unEquipResult = extractRmrkEquipTxResult(1045 events,1046 'SlotUnequipped',1047 (data) => {1048 return {1049 item_collection: parseInt(data[0].toString(), 10),1050 item_nft: parseInt(data[1].toString(), 10),1051 base_id: parseInt(data[2].toString(), 10),1052 slot_id: parseInt(data[3].toString(), 10),1053 };1054 },1055 );10561057 expect(unEquipResult.success, 'Error: Unable to unequip an item').to.be.true;1058 expect(unEquipResult.successData!.item_collection, 'Error: Invalid item collection id')1059 .to.be.eq(item[0]);1060 expect(unEquipResult.successData!.item_nft, 'Error: Invalid item NFT id')1061 .to.be.eq(item[1]);1062 expect(unEquipResult.successData!.base_id, 'Error: Invalid base id')1063 .to.be.eq(base);1064 expect(unEquipResult.successData!.slot_id, 'Error: Invalid slot id')1065 .to.be.eq(slot);1066}10671068export async function removeNftResource(1069 api: ApiPromise,1070 expectedStatus: 'pending' | 'removed',1071 issuerUri: string,1072 collectionId: number,1073 nftId: number,1074 resourceId: number,1075) {1076 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1077 const issuer = privateKey(issuerUri, Number(ss58Format));10781079 const tx = api.tx.rmrkCore.removeResource(collectionId, nftId, resourceId);1080 const events = await executeTransaction(api, issuer, tx);1081 const removeResult = extractRmrkCoreTxResult(events, 'ResourceRemoval', (data) => {1082 return {1083 nftId: parseInt(data[0].toString(), 10),1084 resourceId: parseInt(data[1].toString(), 10),1085 };1086 });1087 expect(removeResult.success, 'Error: Unable to remove a resource').to.be.true;1088 expect(removeResult.successData!.nftId, 'Error: Invalid NFT Id while removing a resource')1089 .to.be.eq(nftId);1090 expect(removeResult.successData!.resourceId, 'Error: Invalid resource Id while removing a resource')1091 .to.be.eq(resourceId);10921093 const afterDeleting = await findResourceById(api, collectionId, nftId, resourceId);10941095 if (expectedStatus === 'pending') {1096 expect(afterDeleting).not.to.be.null;1097 expect(afterDeleting?.pendingRemoval.isTrue).to.be.equal(true);1098 } else {1099 expect(afterDeleting).to.be.null;1100 }1101}11021103export async function acceptResourceRemoval(1104 api: ApiPromise,1105 issuerUri: string,1106 collectionId: number,1107 nftId: number,1108 resourceId: number,1109) {1110 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1111 const issuer = privateKey(issuerUri, Number(ss58Format));11121113 const tx = api.tx.rmrkCore.acceptResourceRemoval(collectionId, nftId, resourceId);1114 const events = await executeTransaction(api, issuer, tx);1115 const acceptResult = extractRmrkCoreTxResult(events, 'ResourceRemovalAccepted', (data) => {1116 return {1117 nftId: parseInt(data[0].toString(), 10),1118 resourceId: parseInt(data[1].toString(), 10),1119 };1120 });1121 expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true;1122 expect(acceptResult.successData!.nftId, 'Error: Invalid NFT Id while accepting a resource')1123 .to.be.eq(nftId);1124 expect(acceptResult.successData!.resourceId, 'Error: Invalid resource Id while accepting a resource')1125 .to.be.eq(resourceId);11261127 const afterDeleting = await findResourceById(api, collectionId, nftId, resourceId);1128 expect(afterDeleting, 'Error: resource deleting failed').to.be.null;1129}