git.delta.rocks / unique-network / refs/commits / 0a7dd513e470

difftreelog

source

tests/src/rmrk/util/tx.ts39.3 KiBsourcehistory
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  // This is only needed when RMRK uses `uniques` pallet by Parity90  // let tx = api.tx.uniques.setAcceptOwnership(91  //     api.createType('Option<u32>', collectionId)92  // );93  // let events = await executeTransaction(api, bob, tx);94  // expect(isTxResultSuccess(events), 'Error: Unable to accept ownership').to.be.true;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      // try to find the matching resource from the query297      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                // do final checks since this is now established to be the resource we seek336                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                // remove the matching resource from the resources we check340                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  // FIXME A workaround. It seems it is a PolkadotJS bug.850  // All of the following are `false`.851  //852  // console.log('>>> basic:', resource.resource.isBasic);853  // console.log('>>> composable:', resource.resource.isComposable);854  // console.log('>>> slot:', resource.resource.isSlot);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, // api.createType('Vec<u32>', 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  // FIXME A workaround. It seems it is a PolkadotJS bug.908  // All of the following are `false`.909  //910  // console.log('>>> basic:', resource.resource.isBasic);911  // console.log('>>> composable:', resource.resource.isComposable);912  // console.log('>>> slot:', resource.resource.isSlot);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  // FIXME A workaround. It seems it is a PolkadotJS bug.970  // All of the following are `false`.971  //972  // console.log('>>> basic:', resource.resource.isBasic);973  // console.log('>>> composable:', resource.resource.isComposable);974  // console.log('>>> slot:', resource.resource.isSlot);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}