git.delta.rocks / unique-network / refs/commits / b2ec226f4d76

difftreelog

source

tests/src/rmrk/util/tx.ts39.5 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 as any,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(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                // do final checks since this is now established to be the resource we seek339                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                // remove the matching resource from the resources we check343                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  // FIXME A workaround. It seems it is a PolkadotJS bug.853  // All of the following are `false`.854  //855  // console.log('>>> basic:', resource.resource.isBasic);856  // console.log('>>> composable:', resource.resource.isComposable);857  // console.log('>>> slot:', resource.resource.isSlot);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, // api.createType('Vec<u32>', 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  // FIXME A workaround. It seems it is a PolkadotJS bug.911  // All of the following are `false`.912  //913  // console.log('>>> basic:', resource.resource.isBasic);914  // console.log('>>> composable:', resource.resource.isComposable);915  // console.log('>>> slot:', resource.resource.isSlot);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  // FIXME A workaround. It seems it is a PolkadotJS bug.973  // All of the following are `false`.974  //975  // console.log('>>> basic:', resource.resource.isBasic);976  // console.log('>>> composable:', resource.resource.isComposable);977  // console.log('>>> slot:', resource.resource.isSlot);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}