git.delta.rocks / unique-network / refs/commits / 16ab55f38ef5

difftreelog

source

tests/src/util/playgrounds/unique.ts98.1 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034/* eslint-disable @typescript-eslint/no-var-requires */5/* eslint-disable function-call-argument-newline */6/* eslint-disable no-prototype-builtins */78import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {ApiInterfaceEvents} from '@polkadot/api/types';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';11import {IKeyringPair} from '@polkadot/types/types';12import {IApiListeners, IChainEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types';1314const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {15  const address = {} as ICrossAccountId;16  if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate;17  if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum;18  return address;19};202122const nesting = {23  toChecksumAddress(address: string): string {24    if (typeof address === 'undefined') return '';2526    if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);2728    address = address.toLowerCase().replace(/^0x/i,'');29    const addressHash = keccakAsHex(address).replace(/^0x/i,'');30    const checksumAddress = ['0x'];3132    for (let i = 0; i < address.length; i++) {33      // If ith character is 8 to f then make it uppercase34      if (parseInt(addressHash[i], 16) > 7) {35        checksumAddress.push(address[i].toUpperCase());36      } else {37        checksumAddress.push(address[i]);38      }39    }40    return checksumAddress.join('');41  },42  tokenIdToAddress(collectionId: number, tokenId: number) {43    return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8, '0')}${tokenId.toString(16).padStart(8, '0')}`);44  },45};4647class UniqueUtil {48  static transactionStatus = {49    NOT_READY: 'NotReady',50    FAIL: 'Fail',51    SUCCESS: 'Success',52  };5354  static chainLogType = {55    EXTRINSIC: 'extrinsic',56    RPC: 'rpc',57  };5859  static getNestingTokenAddress(collectionId: number, tokenId: number) {60    return nesting.tokenIdToAddress(collectionId, tokenId);61  }6263  static getDefaultLogger(): ILogger {64    return {65      log(msg: any, level = 'INFO') {66        console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));67      },68      level: {69        ERROR: 'ERROR',70        WARNING: 'WARNING',71        INFO: 'INFO',72      },73    };74  }7576  static vec2str(arr: string[] | number[]) {77    return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');78  }7980  static str2vec(string: string) {81    if (typeof string !== 'string') return string;82    return Array.from(string).map(x => x.charCodeAt(0));83  }8485  static fromSeed(seed: string, ss58Format = 42) {86    const keyring = new Keyring({type: 'sr25519', ss58Format});87    return keyring.addFromUri(seed);88  }8990  static normalizeSubstrateAddress(address: string, ss58Format = 42) {91    return encodeAddress(decodeAddress(address), ss58Format);92  }9394  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') {95    if (creationResult.status !== this.transactionStatus.SUCCESS) {96      throw Error(`Unable to create collection for ${label}`);97    }9899    let collectionId = null;100    creationResult.result.events.forEach(({event: {data, method, section}}) => {101      if ((section === 'common') && (method === 'CollectionCreated')) {102        collectionId = parseInt(data[0].toString(), 10);103      }104    });105106    if (collectionId === null) {107      throw Error(`No CollectionCreated event for ${label}`);108    }109110    return collectionId;111  }112113  static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') {114    if (creationResult.status !== this.transactionStatus.SUCCESS) {115      throw Error(`Unable to create tokens for ${label}`);116    }117    let success = false;118    const tokens = [] as any;119    creationResult.result.events.forEach(({event: {data, method, section}}) => {120      if (method === 'ExtrinsicSuccess') {121        success = true;122      } else if ((section === 'common') && (method === 'ItemCreated')) {123        tokens.push({124          collectionId: parseInt(data[0].toString(), 10),125          tokenId: parseInt(data[1].toString(), 10),126          owner: data[2].toJSON(),127        });128      }129    });130    return {success, tokens};131  }132133  static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') {134    if (burnResult.status !== this.transactionStatus.SUCCESS) {135      throw Error(`Unable to burn tokens for ${label}`);136    }137    let success = false;138    const tokens = [] as any;139    burnResult.result.events.forEach(({event: {data, method, section}}) => {140      if (method === 'ExtrinsicSuccess') {141        success = true;142      } else if ((section === 'common') && (method === 'ItemDestroyed')) {143        tokens.push({144          collectionId: parseInt(data[0].toString(), 10),145          tokenId: parseInt(data[1].toString(), 10),146          owner: data[2].toJSON(),147        });148      }149    });150    return {success, tokens};151  }152153  static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) {154    let eventId = null;155    events.forEach(({event: {data, method, section}}) => {156      if ((section === expectedSection) && (method === expectedMethod)) {157        eventId = parseInt(data[0].toString(), 10);158      }159    });160161    if (eventId === null) {162      throw Error(`No ${expectedMethod} event for ${label}`);163    }164    return eventId === collectionId;165  }166167  static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {168    const normalizeAddress = (address: string | ICrossAccountId) => {169      if(typeof address === 'string') return address;170      const obj = {} as any;171      Object.keys(address).forEach(k => {172        obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];173      });174      if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)};175      if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()};176      return address;177    };178    let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;179    events.forEach(({event: {data, method, section}}) => {180      if ((section === 'common') && (method === 'Transfer')) {181        const hData = (data as any).toJSON();182        transfer = {183          collectionId: hData[0],184          tokenId: hData[1],185          from: normalizeAddress(hData[2]),186          to: normalizeAddress(hData[3]),187          amount: BigInt(hData[4]),188        };189      }190    });191    let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;192    isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);193    isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);194    isSuccess = isSuccess && amount === transfer.amount;195    return isSuccess;196  }197}198199200class ChainHelperBase {201  transactionStatus = UniqueUtil.transactionStatus;202  chainLogType = UniqueUtil.chainLogType;203  util: typeof UniqueUtil;204  logger: ILogger;205  api: ApiPromise | null;206  forcedNetwork: TUniqueNetworks | null;207  network: TUniqueNetworks | null;208  chainLog: IUniqueHelperLog[];209210  constructor(logger?: ILogger) {211    this.util = UniqueUtil;212    if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();213    this.logger = logger;214    this.api = null;215    this.forcedNetwork = null;216    this.network = null;217    this.chainLog = [];218  }219220  clearChainLog(): void {221    this.chainLog = [];222  }223224  forceNetwork(value: TUniqueNetworks): void {225    this.forcedNetwork = value;226  }227228  async connect(wsEndpoint: string, listeners?: IApiListeners) {229    if (this.api !== null) throw Error('Already connected');230    const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);231    this.api = api;232    this.network = network;233  }234235  async disconnect() {236    if (this.api === null) return;237    await this.api.disconnect();238    this.api = null;239    this.network = null;240  }241242  static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {243    const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;244    if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;245    return 'opal';246  }247248  static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {249    const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});250    await api.isReady;251252    const network = await this.detectNetwork(api);253254    await api.disconnect();255256    return network;257  }258259  static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{260    api: ApiPromise;261    network: TUniqueNetworks;262  }> {263    if(typeof network === 'undefined' || network === null) network = 'opal';264    const supportedRPC = {265      opal: {266        unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,267      },268      quartz: {269        unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,270      },271      unique: {272        unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,273      },274    };275    if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);276    const rpc = supportedRPC[network];277278    // TODO: investigate how to replace rpc in runtime279    // api._rpcCore.addUserInterfaces(rpc);280281    const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});282283    await api.isReadyOrError;284285    if (typeof listeners === 'undefined') listeners = {};286    for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {287      if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;288      api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);289    }290291    return {api, network};292  }293294  getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) {295    const {events, status} = data;296    if (status.isReady) {297      return this.transactionStatus.NOT_READY;298    }299    if (status.isBroadcast) {300      return this.transactionStatus.NOT_READY;301    }302    if (status.isInBlock || status.isFinalized) {303      const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed');304      if (errors.length > 0) {305        return this.transactionStatus.FAIL;306      }307      if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {308        return this.transactionStatus.SUCCESS;309      }310    }311312    return this.transactionStatus.FAIL;313  }314315  signTransaction(sender: TSigner, transaction: any, label = 'transaction', options: any = null) {316    const sign = (callback: any) => {317      if(options !== null) return transaction.signAndSend(sender, options, callback);318      return transaction.signAndSend(sender, callback);319    };320    return new Promise(async (resolve, reject) => {321      try {322        const unsub = await sign((result: any) => {323          const status = this.getTransactionStatus(result);324325          if (status === this.transactionStatus.SUCCESS) {326            this.logger.log(`${label} successful`);327            unsub();328            resolve({result, status});329          } else if (status === this.transactionStatus.FAIL) {330            let moduleError = null;331332            if (result.hasOwnProperty('dispatchError')) {333              const dispatchError = result['dispatchError'];334335              if (dispatchError && dispatchError.isModule) {336                const modErr = dispatchError.asModule;337                const errorMeta = dispatchError.registry.findMetaError(modErr);338339                moduleError = `${errorMeta.section}.${errorMeta.name}`;340              }341              else {342                this.logger.log(result, this.logger.level.ERROR);343              }344            }345346            this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);347            unsub();348            reject({status, moduleError, result});349          }350        });351      } catch (e) {352        this.logger.log(e, this.logger.level.ERROR);353        reject(e);354      }355    });356  }357358  constructApiCall(apiCall: string, params: any[]) {359    if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);360    let call = this.api as any;361    for(const part of apiCall.slice(4).split('.')) {362      call = call[part];363    }364    return call(...params);365  }366367  async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') {368    if(this.api === null) throw Error('API not initialized');369    if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);370371    const startTime = (new Date()).getTime();372    let result: ITransactionResult;373    let events = [];374    try {375      result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult;376      events = result.result.events.map((x: any) => x.toHuman());377    }378    catch(e) {379      if(!(e as object).hasOwnProperty('status')) throw e;380      result = e as ITransactionResult;381    }382383    const endTime = (new Date()).getTime();384385    const log = {386      executedAt: endTime,387      executionTime: endTime - startTime,388      type: this.chainLogType.EXTRINSIC,389      status: result.status,390      call: extrinsic,391      params,392    } as IUniqueHelperLog;393394    if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError;395    if(events.length > 0) log.events = events;396397    this.chainLog.push(log);398399    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage);400    return result;401  }402403  async callRpc(rpc: string, params?: any[]) {404    if(typeof params === 'undefined') params = [];405    if(this.api === null) throw Error('API not initialized');406    if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);407408    const startTime = (new Date()).getTime();409    let result;410    let error = null;411    const log = {412      type: this.chainLogType.RPC,413      call: rpc,414      params,415    } as IUniqueHelperLog;416417    try {418      result = await this.constructApiCall(rpc, params);419    }420    catch(e) {421      error = e;422    }423424    const endTime = (new Date()).getTime();425426    log.executedAt = endTime;427    log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';428    log.executionTime = endTime - startTime;429430    this.chainLog.push(log);431432    if(error !== null) throw error;433434    return result;435  }436437  getSignerAddress(signer: IKeyringPair | string): string {438    if(typeof signer === 'string') return signer;439    return signer.address;440  }441}442443444class HelperGroup {445  helper: UniqueHelper;446447  constructor(uniqueHelper: UniqueHelper) {448    this.helper = uniqueHelper;449  }450}451452453class CollectionGroup extends HelperGroup {454  /**455 * Get number of blocks when sponsored transaction is available.456 *457 * @param collectionId ID of collection458 * @param tokenId ID of token459 * @param addressObj address for which the sponsorship is checked460 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});461 * @returns number of blocks or null if sponsorship hasn't been set462 */463  async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {464    return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();465  }466467  /**468   * Get the number of created collections.469   *470   * @returns number of created collections471   */472  async getTotalCount(): Promise<number> {473    return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();474  }475476  /**477   * Get information about the collection with additional data, including the number of tokens it contains, its administrators, the normalized address of the collection's owner, and decoded name and description.478   *479   * @param collectionId ID of collection480   * @example await getData(2)481   * @returns collection information object482   */483  async getData(collectionId: number): Promise<{484    id: number;485    name: string;486    description: string;487    tokensCount: number;488    admins: ICrossAccountId[];489    normalizedOwner: TSubstrateAccount;490    raw: any491  } | null> {492    const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);493    const humanCollection = collection.toHuman(), collectionData = {494      id: collectionId, name: null, description: null, tokensCount: 0, admins: [],495      raw: humanCollection,496    } as any, jsonCollection = collection.toJSON();497    if (humanCollection === null) return null;498    collectionData.raw.limits = jsonCollection.limits;499    collectionData.raw.permissions = jsonCollection.permissions;500    collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);501    for (const key of ['name', 'description']) {502      collectionData[key] = this.helper.util.vec2str(humanCollection[key]);503    }504505    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0;506    collectionData.admins = await this.getAdmins(collectionId);507508    return collectionData;509  }510511  /**512   * Get the normalized addresses of the collection's administrators.513   *514   * @param collectionId ID of collection515   * @example await getAdmins(1)516   * @returns array of administrators517   */518  async getAdmins(collectionId: number): Promise<ICrossAccountId[]> {519    const normalized = [];520    for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) {521      if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)});522      else normalized.push(admin);523    }524    return normalized;525  }526527  /**528   * Get the normalized addresses added to the collection allow-list.529   * @param collectionId ID of collection530   * @example await getAllowList(1)531   * @returns array of allow-listed addresses532   */533  async getAllowList(collectionId: number): Promise<ICrossAccountId[]> {534    const normalized = [];535    const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();536    for (const address of allowListed) {537      if (address.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(address.Substrate)});538      else normalized.push(address);539    }540    return normalized;541  }542543  /**544   * Get the effective limits of the collection instead of null for default values545   *546   * @param collectionId ID of collection547   * @example await getEffectiveLimits(2)548   * @returns object of collection limits549   */550  async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {551    return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();552  }553554  /**555   * Burns the collection if the signer has sufficient permissions and collection is empty.556   *557   * @param signer keyring of signer558   * @param collectionId ID of collection559   * @param label extra label for log560   * @example await helper.collection.burn(aliceKeyring, 3);561   * @returns ```true``` if extrinsic success, otherwise ```false```562   */563  async burn(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {564    if(typeof label === 'undefined') label = `collection #${collectionId}`;565    const result = await this.helper.executeExtrinsic(566      signer,567      'api.tx.unique.destroyCollection', [collectionId],568      true, `Unable to burn collection for ${label}`,569    );570571    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label);572  }573574  /**575   * Sets the sponsor for the collection (Requires the Substrate address).576   *577   * @param signer keyring of signer578   * @param collectionId ID of collection579   * @param sponsorAddress Sponsor substrate address580   * @param label extra label for log581   * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")582   * @returns ```true``` if extrinsic success, otherwise ```false```583   */584  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise<boolean> {585    if(typeof label === 'undefined') label = `collection #${collectionId}`;586    const result = await this.helper.executeExtrinsic(587      signer,588      'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],589      true, `Unable to set collection sponsor for ${label}`,590    );591592    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label);593  }594595  /**596   * Confirms consent to sponsor the collection on behalf of the signer.597   *598   * @param signer keyring of signer599   * @param collectionId ID of collection600   * @param label extra label for log601   * @example confirmSponsorship(aliceKeyring, 10)602   * @returns ```true``` if extrinsic success, otherwise ```false```603   */604  async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {605    if(typeof label === 'undefined') label = `collection #${collectionId}`;606    const result = await this.helper.executeExtrinsic(607      signer,608      'api.tx.unique.confirmSponsorship', [collectionId],609      true, `Unable to confirm collection sponsorship for ${label}`,610    );611612    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label);613  }614615  /**616   * Sets the limits of the collection. At least one limit must be specified for a correct call.617   *618   * @param signer keyring of signer619   * @param collectionId ID of collection620   * @param limits collection limits object621   * @param label extra label for log622   * @example623   * await setLimits(624   *   aliceKeyring,625   *   10,626   *   {627   *     sponsorTransferTimeout: 0,628   *     ownerCanDestroy: false629   *   }630   * )631   * @returns ```true``` if extrinsic success, otherwise ```false```632   */633  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise<boolean> {634    if(typeof label === 'undefined') label = `collection #${collectionId}`;635    const result = await this.helper.executeExtrinsic(636      signer,637      'api.tx.unique.setCollectionLimits', [collectionId, limits],638      true, `Unable to set collection limits for ${label}`,639    );640641    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label);642  }643644  /**645   * Changes the owner of the collection to the new Substrate address.646   *647   * @param signer keyring of signer648   * @param collectionId ID of collection649   * @param ownerAddress substrate address of new owner650   * @param label extra label for log651   * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")652   * @returns ```true``` if extrinsic success, otherwise ```false```653   */654  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise<boolean> {655    if(typeof label === 'undefined') label = `collection #${collectionId}`;656    const result = await this.helper.executeExtrinsic(657      signer,658      'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],659      true, `Unable to change collection owner for ${label}`,660    );661662    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label);663  }664665  /**666   * Adds a collection administrator.667   *668   * @param signer keyring of signer669   * @param collectionId ID of collection670   * @param adminAddressObj Administrator address (substrate or ethereum)671   * @param label extra label for log672   * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})673   * @returns ```true``` if extrinsic success, otherwise ```false```674   */675  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {676    if(typeof label === 'undefined') label = `collection #${collectionId}`;677    const result = await this.helper.executeExtrinsic(678      signer,679      'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],680      true, `Unable to add collection admin for ${label}`,681    );682683    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label);684  }685686  /**687   * Adds an address to allow list688   * @param signer keyring of signer689   * @param collectionId ID of collection690   * @param addressObj address to add to the allow list691   * @param label extra label for log692   * @returns ```true``` if extrinsic success, otherwise ```false```693   */694  async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId, label?: string): Promise<boolean> {695    if(typeof label === 'undefined') label = `collection #${collectionId}`;696    const result = await this.helper.executeExtrinsic(697      signer,698      'api.tx.unique.addToAllowList', [collectionId, addressObj],699      true, `Unable to add address to allow list for ${label}`,700    );701702    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');703  }704705  /**706   * Removes an address from allow list.707   *708   * @param signer keyring of signer709   * @param collectionId ID of collection710   * @param addressObj address to be removed from allow list (substrate or ethereum)711   * @param label extra label for log712   * @example removeFromAllowList(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})713   * @returns ```true``` if extrinsic success, otherwise ```false```714   */715  async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId, label?: string): Promise<boolean> {716    if(typeof label === 'undefined') label = `collection #${collectionId}`;717    const result = await this.helper.executeExtrinsic(718      signer,719      'api.tx.unique.removeFromAllowList', [collectionId, addressObj],720      true, `Unable to remove address from allow list for ${label}`,721    );722723    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved', label);724  }725726  /**727   * Removes a collection administrator.728   *729   * @param signer keyring of signer730   * @param collectionId ID of collection731   * @param adminAddressObj Administrator address (substrate or ethereum)732   * @param label extra label for log733   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})734   * @returns ```true``` if extrinsic success, otherwise ```false```735   */736  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {737    if(typeof label === 'undefined') label = `collection #${collectionId}`;738    const result = await this.helper.executeExtrinsic(739      signer,740      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],741      true, `Unable to remove collection admin for ${label}`,742    );743744    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label);745  }746747  /**748   * Sets onchain permissions for selected collection.749   *750   * @param signer keyring of signer751   * @param collectionId ID of collection752   * @param permissions collection permissions object753   * @param label extra label for log754   * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});755   * @returns ```true``` if extrinsic success, otherwise ```false```756   */757  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise<boolean> {758    if(typeof label === 'undefined') label = `collection #${collectionId}`;759    const result = await this.helper.executeExtrinsic(760      signer,761      'api.tx.unique.setCollectionPermissions', [collectionId, permissions],762      true, `Unable to set collection permissions for ${label}`,763    );764765    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label);766  }767768  /**769   * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.770   *771   * @param signer keyring of signer772   * @param collectionId ID of collection773   * @param permissions nesting permissions object774   * @param label extra label for log775   * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});776   * @returns ```true``` if extrinsic success, otherwise ```false```777   */778  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise<boolean> {779    return await this.setPermissions(signer, collectionId, {nesting: permissions}, label);780  }781782  /**783   * Disables nesting for selected collection.784   *785   * @param signer keyring of signer786   * @param collectionId ID of collection787   * @param label extra label for log788   * @example disableNesting(aliceKeyring, 10);789   * @returns ```true``` if extrinsic success, otherwise ```false```790   */791  async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {792    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label);793  }794795  /**796   * Sets onchain properties to the collection.797   *798   * @param signer keyring of signer799   * @param collectionId ID of collection800   * @param properties array of property objects801   * @param label extra label for log802   * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);803   * @returns ```true``` if extrinsic success, otherwise ```false```804   */805  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {806    if(typeof label === 'undefined') label = `collection #${collectionId}`;807    const result = await this.helper.executeExtrinsic(808      signer,809      'api.tx.unique.setCollectionProperties', [collectionId, properties],810      true, `Unable to set collection properties for ${label}`,811    );812813    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);814  }815816  /**817   * Deletes onchain properties from the collection.818   *819   * @param signer keyring of signer820   * @param collectionId ID of collection821   * @param propertyKeys array of property keys to delete822   * @param label823   * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);824   * @returns ```true``` if extrinsic success, otherwise ```false```825   */826  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {827    if(typeof label === 'undefined') label = `collection #${collectionId}`;828    const result = await this.helper.executeExtrinsic(829      signer,830      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],831      true, `Unable to delete collection properties for ${label}`,832    );833834    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);835  }836837  /**838   * Changes the owner of the token.839   *840   * @param signer keyring of signer841   * @param collectionId ID of collection842   * @param tokenId ID of token843   * @param addressObj address of a new owner844   * @param amount amount of tokens to be transfered. For NFT must be set to 1n845   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})846   * @returns true if the token success, otherwise false847   */848  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {849    const result = await this.helper.executeExtrinsic(850      signer,851      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],852      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,853    );854855    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);856  }857858  /**859   *860   * Change ownership of a token(s) on behalf of the owner.861   *862   * @param signer keyring of signer863   * @param collectionId ID of collection864   * @param tokenId ID of token865   * @param fromAddressObj address on behalf of which the token will be sent866   * @param toAddressObj new token owner867   * @param amount amount of tokens to be transfered. For NFT must be set to 1n868   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})869   * @returns true if the token success, otherwise false870   */871  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {872    const result = await this.helper.executeExtrinsic(873      signer,874      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],875      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,876    );877    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);878  }879880  /**881   *882   * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.883   *884   * @param signer keyring of signer885   * @param collectionId ID of collection886   * @param tokenId ID of token887   * @param label888   * @param amount amount of tokens to be burned. For NFT must be set to 1n889   * @example burnToken(aliceKeyring, 10, 5);890   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```891   */892  async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{893    success: boolean,894    token: number | null895  }> {896    if(typeof label === 'undefined') label = `collection #${collectionId}`;897    const burnResult = await this.helper.executeExtrinsic(898      signer,899      'api.tx.unique.burnItem', [collectionId, tokenId, amount],900      true, `Unable to burn token for ${label}`,901    );902    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);903    if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');904    return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};905  }906907  /**908   * Destroys a concrete instance of NFT on behalf of the owner909   *910   * @param signer keyring of signer911   * @param collectionId ID of collection912   * @param fromAddressObj address on behalf of which the token will be burnt913   * @param tokenId ID of token914   * @param label915   * @param amount amount of tokens to be burned. For NFT must be set to 1n916   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})917   * @returns ```true``` if extrinsic success, otherwise ```false```918   */919  async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {920    if(typeof label === 'undefined') label = `collection #${collectionId}`;921    const burnResult = await this.helper.executeExtrinsic(922      signer,923      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],924      true, `Unable to burn token from for ${label}`,925    );926    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);927    return burnedTokens.success && burnedTokens.tokens.length > 0;928  }929930  /**931   * Set, change, or remove approved address to transfer the ownership of the NFT.932   *933   * @param signer keyring of signer934   * @param collectionId ID of collection935   * @param tokenId ID of token936   * @param toAddressObj937   * @param label938   * @param amount amount of token to be approved. For NFT must be set to 1n939   * @returns ```true``` if extrinsic success, otherwise ```false```940   */941  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {942    if(typeof label === 'undefined') label = `collection #${collectionId}`;943    const approveResult = await this.helper.executeExtrinsic(944      signer,945      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],946      true, `Unable to approve token for ${label}`,947    );948949    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);950  }951952  /**953   * Get the amount of token pieces approved to transfer954   * @param collectionId ID of collection955   * @param tokenId ID of token956   * @param toAccountObj957   * @param fromAccountObj958   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})959   * @returns number of approved to transfer pieces960   */961  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {962    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();963  }964965  /**966   * Get the last created token id967   * @param collectionId ID of collection968   * @example getLastTokenId(10);969   * @returns id of the last created token970   */971  async getLastTokenId(collectionId: number): Promise<number> {972    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();973  }974975  /**976   * Check if token exists977   * @param collectionId ID of collection978   * @param tokenId ID of token979   * @example isTokenExists(10, 20);980   * @returns true if the token exists, otherwise false981   */982  async isTokenExists(collectionId: number, tokenId: number): Promise<boolean> {983    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();984  }985}986987class NFTnRFT extends CollectionGroup {988  /**989   * Get tokens owned by account990   *991   * @param collectionId ID of collection992   * @param addressObj tokens owner993   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})994   * @returns array of token ids owned by account995   */996  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {997    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();998  }9991000  /**1001   * Get token data1002   * @param collectionId ID of collection1003   * @param tokenId ID of token1004   * @param blockHashAt1005   * @param propertyKeys1006   * @example getToken(10, 5);1007   * @returns human readable token data1008   */1009  async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{1010    properties: IProperty[];1011    owner: ICrossAccountId;1012    normalizedOwner: ICrossAccountId;1013  }| null> {1014    let tokenData;1015    if(typeof blockHashAt === 'undefined') {1016      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1017    }1018    else {1019      if(typeof propertyKeys === 'undefined') {1020        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1021        if(!collection) return null;1022        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1023      }1024      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1025    }1026    tokenData = tokenData.toHuman();1027    if (tokenData === null || tokenData.owner === null) return null;1028    const owner = {} as any;1029    for (const key of Object.keys(tokenData.owner)) {1030      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];1031    }1032    tokenData.normalizedOwner = crossAccountIdFromLower(owner);1033    return tokenData;1034  }10351036  /**1037   * Set permissions to change token properties1038   * @param signer keyring of signer1039   * @param collectionId ID of collection1040   * @param permissions permissions to change a property by the collection owner or admin1041   * @param label1042   * @example setTokenPropertyPermissions(1043   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1044   * )1045   * @returns true if extrinsic success otherwise false1046   */1047  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {1048    if(typeof label === 'undefined') label = `collection #${collectionId}`;1049    const result = await this.helper.executeExtrinsic(1050      signer,1051      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1052      true, `Unable to set token property permissions for ${label}`,1053    );10541055    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);1056  }10571058  /**1059   * Set token properties1060   * @param signer keyring of signer1061   * @param collectionId ID of collection1062   * @param tokenId ID of token1063   * @param properties1064   * @param label1065   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1066   * @returns ```true``` if extrinsic success, otherwise ```false```1067   */1068  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {1069    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1070    const result = await this.helper.executeExtrinsic(1071      signer,1072      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1073      true, `Unable to set token properties for ${label}`,1074    );10751076    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);1077  }10781079  /**1080   * Delete the provided properties of a token1081   * @param signer keyring of signer1082   * @param collectionId ID of collection1083   * @param tokenId ID of token1084   * @param propertyKeys property keys to be deleted1085   * @param label1086   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1087   * @returns ```true``` if extrinsic success, otherwise ```false```1088   */1089  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {1090    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1091    const result = await this.helper.executeExtrinsic(1092      signer,1093      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1094      true, `Unable to delete token properties for ${label}`,1095    );10961097    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);1098  }10991100  /**1101   * Mint new collection1102   * @param signer keyring of signer1103   * @param collectionOptions basic collection options and properties1104   * @param mode NFT or RFT type of a collection1105   * @param errorLabel1106   * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1107   * @returns object of the created collection1108   */1109  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {1110    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1111    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1112    for (const key of ['name', 'description', 'tokenPrefix']) {1113      if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);1114    }1115    const creationResult = await this.helper.executeExtrinsic(1116      signer,1117      'api.tx.unique.createCollectionEx', [collectionOptions],1118      true, errorLabel,1119    );1120    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1121  }11221123  getCollectionObject(collectionId: number): any {1124    return null;1125  }11261127  getTokenObject(collectionId: number, tokenId: number): any {1128    return null;1129  }1130}113111321133class NFTGroup extends NFTnRFT {1134  /**1135   * Get collection object1136   * @param collectionId ID of collection1137   * @example getCollectionObject(2);1138   * @returns instance of UniqueNFTCollection1139   */1140  getCollectionObject(collectionId: number): UniqueNFTCollection {1141    return new UniqueNFTCollection(collectionId, this.helper);1142  }11431144  /**1145   * Get token object1146   * @param collectionId ID of collection1147   * @param tokenId ID of token1148   * @example getTokenObject(10, 5);1149   * @returns instance of UniqueNFTToken1150   */1151  getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {1152    return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));1153  }11541155  /**1156   * Get token's owner1157   * @param collectionId ID of collection1158   * @param tokenId ID of token1159   * @param blockHashAt1160   * @example getTokenOwner(10, 5);1161   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1162   */1163  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {1164    let owner;1165    if (typeof blockHashAt === 'undefined') {1166      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1167    } else {1168      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1169    }1170    return crossAccountIdFromLower(owner.toJSON());1171  }11721173  /**1174   * Is token approved to transfer1175   * @param collectionId ID of collection1176   * @param tokenId ID of token1177   * @param toAccountObj address to be approved1178   * @returns ```true``` if extrinsic success, otherwise ```false```1179   */1180  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1181    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1182  }11831184  /**1185   * Changes the owner of the token.1186   *1187   * @param signer keyring of signer1188   * @param collectionId ID of collection1189   * @param tokenId ID of token1190   * @param addressObj address of a new owner1191   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1192   * @returns ```true``` if extrinsic success, otherwise ```false```1193   */1194  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1195    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1196  }11971198  /**1199   *1200   * Change ownership of a NFT on behalf of the owner.1201   *1202   * @param signer keyring of signer1203   * @param collectionId ID of collection1204   * @param tokenId ID of token1205   * @param fromAddressObj address on behalf of which the token will be sent1206   * @param toAddressObj new token owner1207   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1208   * @returns ```true``` if extrinsic success, otherwise ```false```1209   */1210  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1211    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1212  }12131214  /**1215   * Recursively find the address that owns the token1216   * @param collectionId ID of collection1217   * @param tokenId ID of token1218   * @param blockHashAt1219   * @example getTokenTopmostOwner(10, 5);1220   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1221   */1222  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {1223    let owner;1224    if (typeof blockHashAt === 'undefined') {1225      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1226    } else {1227      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1228    }12291230    if (owner === null) return null;12311232    owner = owner.toHuman();12331234    return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;1235  }12361237  /**1238   * Get tokens nested in the provided token1239   * @param collectionId ID of collection1240   * @param tokenId ID of token1241   * @param blockHashAt1242   * @example getTokenChildren(10, 5);1243   * @returns tokens whose depth of nesting is <= 51244   */1245  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1246    let children;1247    if(typeof blockHashAt === 'undefined') {1248      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1249    } else {1250      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1251    }12521253    return children.toJSON().map((x: any) => {1254      return {collectionId: x.collection, tokenId: x.token};1255    });1256  }12571258  /**1259   * Nest one token into another1260   * @param signer keyring of signer1261   * @param tokenObj token to be nested1262   * @param rootTokenObj token to be parent1263   * @param label1264   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1265   * @returns ```true``` if extrinsic success, otherwise ```false```1266   */1267  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {1268    const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1269    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1270    if(!result) {1271      throw Error(`Unable to nest token for ${label}`);1272    }1273    return result;1274  }12751276  /**1277   * Remove token from nested state1278   * @param signer keyring of signer1279   * @param tokenObj token to unnest1280   * @param rootTokenObj parent of a token1281   * @param toAddressObj address of a new token owner1282   * @param label1283   * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1284   * @returns ```true``` if extrinsic success, otherwise ```false```1285   */1286  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {1287    const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1288    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1289    if(!result) {1290      throw Error(`Unable to unnest token for ${label}`);1291    }1292    return result;1293  }12941295  /**1296   * Mint new collection1297   * @param signer keyring of signer1298   * @param collectionOptions Collection options1299   * @param label1300   * @example1301   * mintCollection(aliceKeyring, {1302   *   name: 'New',1303   *   description: 'New collection',1304   *   tokenPrefix: 'NEW',1305   * })1306   * @returns object of the created collection1307   */1308  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {1309    return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;1310  }13111312  /**1313   * Mint new token1314   * @param signer keyring of signer1315   * @param data token data1316   * @param label1317   * @returns created token object1318   */1319  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {1320    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1321    const creationResult = await this.helper.executeExtrinsic(1322      signer,1323      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1324        nft: {1325          properties: data.properties,1326        },1327      }],1328      true, `Unable to mint NFT token for ${label}`,1329    );1330    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1331    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1332    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1333    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1334  }13351336  /**1337   * Mint multiple NFT tokens1338   * @param signer keyring of signer1339   * @param collectionId ID of collection1340   * @param tokens array of tokens with owner and properties1341   * @param label1342   * @example1343   * mintMultipleTokens(aliceKeyring, 10, [{1344   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1345   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1346   *   },{1347   *     owner: {Ethereum: "0x9F0583DbB855d..."},1348   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1349   * }]);1350   * @returns ```true``` if extrinsic success, otherwise ```false```1351   */1352  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1353    if(typeof label === 'undefined') label = `collection #${collectionId}`;1354    const creationResult = await this.helper.executeExtrinsic(1355      signer,1356      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1357      true, `Unable to mint NFT tokens for ${label}`,1358    );1359    const collection = this.getCollectionObject(collectionId);1360    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1361  }13621363  /**1364   * Mint multiple NFT tokens with one owner1365   * @param signer keyring of signer1366   * @param collectionId ID of collection1367   * @param owner tokens owner1368   * @param tokens array of tokens with owner and properties1369   * @param label1370   * @example1371   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1372   *   properties: [{1373   *   key: "gender",1374   *   value: "female",1375   *  },{1376   *   key: "age",1377   *   value: "33",1378   *  }],1379   * }]);1380   * @returns array of newly created tokens1381   */1382  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1383    if(typeof label === 'undefined') label = `collection #${collectionId}`;1384    const rawTokens = [];1385    for (const token of tokens) {1386      const raw = {NFT: {properties: token.properties}};1387      rawTokens.push(raw);1388    }1389    const creationResult = await this.helper.executeExtrinsic(1390      signer,1391      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1392      true, `Unable to mint NFT tokens for ${label}`,1393    );1394    const collection = this.getCollectionObject(collectionId);1395    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1396  }13971398  /**1399   * Destroys a concrete instance of NFT.1400   * @param signer keyring of signer1401   * @param collectionId ID of collection1402   * @param tokenId ID of token1403   * @param label1404   * @example burnToken(aliceKeyring, 10, 5);1405   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1406   */1407  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {1408    return await super.burnToken(signer, collectionId, tokenId, label, 1n);1409  }14101411  /**1412   * Set, change, or remove approved address to transfer the ownership of the NFT.1413   *1414   * @param signer keyring of signer1415   * @param collectionId ID of collection1416   * @param tokenId ID of token1417   * @param toAddressObj address to approve1418   * @param label1419   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1420   * @returns ```true``` if extrinsic success, otherwise ```false```1421   */1422  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {1423    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1424  }1425}142614271428class RFTGroup extends NFTnRFT {1429  /**1430   * Get collection object1431   * @param collectionId ID of collection1432   * @example getCollectionObject(2);1433   * @returns instance of UniqueRFTCollection1434   */1435  getCollectionObject(collectionId: number): UniqueRFTCollection {1436    return new UniqueRFTCollection(collectionId, this.helper);1437  }14381439  /**1440   * Get token object1441   * @param collectionId ID of collection1442   * @param tokenId ID of token1443   * @example getTokenObject(10, 5);1444   * @returns instance of UniqueNFTToken1445   */1446  getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {1447    return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));1448  }14491450  /**1451   * Get top 10 token owners with the largest number of pieces1452   * @param collectionId ID of collection1453   * @param tokenId ID of token1454   * @example getTokenTop10Owners(10, 5);1455   * @returns array of top 10 owners1456   */1457  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {1458    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);1459  }14601461  /**1462   * Get number of pieces owned by address1463   * @param collectionId ID of collection1464   * @param tokenId ID of token1465   * @param addressObj address token owner1466   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1467   * @returns number of pieces ownerd by address1468   */1469  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1470    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1471  }14721473  /**1474   * Transfer pieces of token to another address1475   * @param signer keyring of signer1476   * @param collectionId ID of collection1477   * @param tokenId ID of token1478   * @param addressObj address of a new owner1479   * @param amount number of pieces to be transfered1480   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1481   * @returns ```true``` if extrinsic success, otherwise ```false```1482   */1483  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {1484    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1485  }14861487  /**1488   * Change ownership of some pieces of RFT on behalf of the owner.1489   * @param signer keyring of signer1490   * @param collectionId ID of collection1491   * @param tokenId ID of token1492   * @param fromAddressObj address on behalf of which the token will be sent1493   * @param toAddressObj new token owner1494   * @param amount number of pieces to be transfered1495   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1496   * @returns ```true``` if extrinsic success, otherwise ```false```1497   */1498  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {1499    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1500  }15011502  /**1503   * Mint new collection1504   * @param signer keyring of signer1505   * @param collectionOptions Collection options1506   * @param label1507   * @example1508   * mintCollection(aliceKeyring, {1509   *   name: 'New',1510   *   description: 'New collection',1511   *   tokenPrefix: 'NEW',1512   * })1513   * @returns object of the created collection1514   */1515  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {1516    return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;1517  }15181519  /**1520   * Mint new token1521   * @param signer keyring of signer1522   * @param data token data1523   * @param label1524   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1525   * @returns created token object1526   */1527  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {1528    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1529    const creationResult = await this.helper.executeExtrinsic(1530      signer,1531      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1532        refungible: {1533          pieces: data.pieces,1534          properties: data.properties,1535        },1536      }],1537      true, `Unable to mint RFT token for ${label}`,1538    );1539    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1540    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1541    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1542    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1543  }15441545  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1546    throw Error('Not implemented');1547    if(typeof label === 'undefined') label = `collection #${collectionId}`;1548    const creationResult = await this.helper.executeExtrinsic(1549      signer,1550      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1551      true, `Unable to mint RFT tokens for ${label}`,1552    );1553    const collection = this.getCollectionObject(collectionId);1554    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1555  }15561557  /**1558   * Mint multiple RFT tokens with one owner1559   * @param signer keyring of signer1560   * @param collectionId ID of collection1561   * @param owner tokens owner1562   * @param tokens array of tokens with properties and pieces1563   * @param label1564   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1565   * @returns array of newly created RFT tokens1566   */1567  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1568    if(typeof label === 'undefined') label = `collection #${collectionId}`;1569    const rawTokens = [];1570    for (const token of tokens) {1571      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1572      rawTokens.push(raw);1573    }1574    const creationResult = await this.helper.executeExtrinsic(1575      signer,1576      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1577      true, `Unable to mint RFT tokens for ${label}`,1578    );1579    const collection = this.getCollectionObject(collectionId);1580    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1581  }15821583  /**1584   * Destroys a concrete instance of RFT.1585   * @param signer keyring of signer1586   * @param collectionId ID of collection1587   * @param tokenId ID of token1588   * @param label1589   * @param amount number of pieces to be burnt1590   * @example burnToken(aliceKeyring, 10, 5);1591   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1592   */1593  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {1594    return await super.burnToken(signer, collectionId, tokenId, label, amount);1595  }15961597  /**1598   * Set, change, or remove approved address to transfer the ownership of the RFT.1599   *1600   * @param signer keyring of signer1601   * @param collectionId ID of collection1602   * @param tokenId ID of token1603   * @param toAddressObj address to approve1604   * @param label1605   * @param amount number of pieces to be approved1606   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1607   * @returns true if the token success, otherwise false1608   */1609  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {1610    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1611  }16121613  /**1614   * Get total number of pieces1615   * @param collectionId ID of collection1616   * @param tokenId ID of token1617   * @example getTokenTotalPieces(10, 5);1618   * @returns number of pieces1619   */1620  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1621    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1622  }16231624  /**1625   * Change number of token pieces. Signer must be the owner of all token pieces.1626   * @param signer keyring of signer1627   * @param collectionId ID of collection1628   * @param tokenId ID of token1629   * @param amount new number of pieces1630   * @param label1631   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1632   * @returns true if the repartion was success, otherwise false1633   */1634  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {1635    if(typeof label === 'undefined') label = `collection #${collectionId}`;1636    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1637    const repartitionResult = await this.helper.executeExtrinsic(1638      signer,1639      'api.tx.unique.repartition', [collectionId, tokenId, amount],1640      true, `Unable to repartition RFT token for ${label}`,1641    );1642    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);1643    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);1644  }1645}164616471648class FTGroup extends CollectionGroup {1649  /**1650   * Get collection object1651   * @param collectionId ID of collection1652   * @example getCollectionObject(2);1653   * @returns instance of UniqueFTCollection1654   */1655  getCollectionObject(collectionId: number): UniqueFTCollection {1656    return new UniqueFTCollection(collectionId, this.helper);1657  }16581659  /**1660   * Mint new fungible collection1661   * @param signer keyring of signer1662   * @param collectionOptions Collection options1663   * @param decimalPoints number of token decimals1664   * @param errorLabel1665   * @example1666   * mintCollection(aliceKeyring, {1667   *   name: 'New',1668   *   description: 'New collection',1669   *   tokenPrefix: 'NEW',1670   * }, 18)1671   * @returns newly created fungible collection1672   */1673  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {1674    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1675    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1676    collectionOptions.mode = {fungible: decimalPoints};1677    for (const key of ['name', 'description', 'tokenPrefix']) {1678      if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);1679    }1680    const creationResult = await this.helper.executeExtrinsic(1681      signer,1682      'api.tx.unique.createCollectionEx', [collectionOptions],1683      true, errorLabel,1684    );1685    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1686  }16871688  /**1689   * Mint tokens1690   * @param signer keyring of signer1691   * @param collectionId ID of collection1692   * @param owner address owner of new tokens1693   * @param amount amount of tokens to be meanted1694   * @param label1695   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1696   * @returns ```true``` if extrinsic success, otherwise ```false```1697   */1698  async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {1699    if(typeof label === 'undefined') label = `collection #${collectionId}`;1700    const creationResult = await this.helper.executeExtrinsic(1701      signer,1702      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1703        fungible: {1704          value: amount,1705        },1706      }],1707      true, `Unable to mint fungible tokens for ${label}`,1708    );1709    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1710  }17111712  /**1713   * Mint multiple Fungible tokens with one owner1714   * @param signer keyring of signer1715   * @param collectionId ID of collection1716   * @param owner tokens owner1717   * @param tokens array of tokens with properties and pieces1718   * @param label1719   * @returns ```true``` if extrinsic success, otherwise ```false```1720   */1721  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {1722    if(typeof label === 'undefined') label = `collection #${collectionId}`;1723    const rawTokens = [];1724    for (const token of tokens) {1725      const raw = {Fungible: {Value: token.value}};1726      rawTokens.push(raw);1727    }1728    const creationResult = await this.helper.executeExtrinsic(1729      signer,1730      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1731      true, `Unable to mint RFT tokens for ${label}`,1732    );1733    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1734  }17351736  /**1737   * Get the top 10 owners with the largest balance for the Fungible collection1738   * @param collectionId ID of collection1739   * @example getTop10Owners(10);1740   * @returns array of ```ICrossAccountId```1741   */1742  async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {1743    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);1744  }17451746  /**1747   * Get account balance1748   * @param collectionId ID of collection1749   * @param addressObj address of owner1750   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})1751   * @returns amount of fungible tokens owned by address1752   */1753  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1754    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1755  }17561757  /**1758   * Transfer tokens to address1759   * @param signer keyring of signer1760   * @param collectionId ID of collection1761   * @param toAddressObj address recepient1762   * @param amount amount of tokens to be sent1763   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1764   * @returns ```true``` if extrinsic success, otherwise ```false```1765   */1766  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {1767    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1768  }17691770  /**1771   * Transfer some tokens on behalf of the owner.1772   * @param signer keyring of signer1773   * @param collectionId ID of collection1774   * @param fromAddressObj address on behalf of which tokens will be sent1775   * @param toAddressObj address where token to be sent1776   * @param amount number of tokens to be sent1777   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);1778   * @returns ```true``` if extrinsic success, otherwise ```false```1779   */1780  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {1781    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1782  }17831784  /**1785   * Destroy some amount of tokens1786   * @param signer keyring of signer1787   * @param collectionId ID of collection1788   * @param amount amount of tokens to be destroyed1789   * @param label1790   * @example burnTokens(aliceKeyring, 10, 1000n);1791   * @returns ```true``` if extrinsic success, otherwise ```false```1792   */1793  async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {1794    return (await super.burnToken(signer, collectionId, 0, label, amount)).success;1795  }17961797  /**1798   * Burn some tokens on behalf of the owner.1799   * @param signer keyring of signer1800   * @param collectionId ID of collection1801   * @param fromAddressObj address on behalf of which tokens will be burnt1802   * @param amount amount of tokens to be burnt1803   * @param label1804   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1805   * @returns ```true``` if extrinsic success, otherwise ```false```1806   */1807  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {1808    return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);1809  }18101811  /**1812   * Get total collection supply1813   * @param collectionId1814   * @returns1815   */1816  async getTotalPieces(collectionId: number): Promise<bigint> {1817    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();1818  }18191820  /**1821   * Set, change, or remove approved address to transfer tokens.1822   *1823   * @param signer keyring of signer1824   * @param collectionId ID of collection1825   * @param toAddressObj address to be approved1826   * @param amount amount of tokens to be approved1827   * @param label1828   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)1829   * @returns ```true``` if extrinsic success, otherwise ```false```1830   */1831  async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {1832    return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);1833  }18341835  /**1836   * Get amount of fungible tokens approved to transfer1837   * @param collectionId ID of collection1838   * @param fromAddressObj owner of tokens1839   * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner1840   * @returns number of tokens approved for the transfer1841   */1842  async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {1843    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);1844  }1845}184618471848class ChainGroup extends HelperGroup {1849  /**1850   * Get system properties of a chain1851   * @example getChainProperties();1852   * @returns ss58Format, token decimals, and token symbol1853   */1854  getChainProperties(): IChainProperties {1855    const properties = (this.helper.api as any).registry.getChainProperties().toJSON();1856    return {1857      ss58Format: properties.ss58Format.toJSON(),1858      tokenDecimals: properties.tokenDecimals.toJSON(),1859      tokenSymbol: properties.tokenSymbol.toJSON(),1860    };1861  }18621863  /**1864   * Get chain header1865   * @example getLatestBlockNumber();1866   * @returns the number of the last block1867   */1868  async getLatestBlockNumber(): Promise<number> {1869    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();1870  }18711872  /**1873   * Get block hash by block number1874   * @param blockNumber number of block1875   * @example getBlockHashByNumber(12345);1876   * @returns hash of a block1877   */1878  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {1879    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();1880    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;1881    return blockHash;1882  }18831884  /**1885   * Get account nonce1886   * @param address substrate address1887   * @example getNonce("5GrwvaEF5zXb26Fz...");1888   * @returns number, account's nonce1889   */1890  async getNonce(address: TSubstrateAccount): Promise<number> {1891    return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber();1892  }1893}189418951896class BalanceGroup extends HelperGroup {1897  /**1898   * Representation of the native token in the smallest unit1899   * @example getOneTokenNominal()1900   * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.1901   */1902  getOneTokenNominal(): bigint {1903    const chainProperties = this.helper.chain.getChainProperties();1904    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);1905  }19061907  /**1908   * Get substrate address balance1909   * @param address substrate address1910   * @example getSubstrate("5GrwvaEF5zXb26Fz...")1911   * @returns amount of tokens on address1912   */1913  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {1914    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();1915  }19161917  /**1918   * Get ethereum address balance1919   * @param address ethereum address1920   * @example getEthereum("0x9F0583DbB855d...")1921   * @returns amount of tokens on address1922   */1923  async getEthereum(address: TEthereumAccount): Promise<bigint> {1924    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();1925  }19261927  /**1928   * Transfer tokens to substrate address1929   * @param signer keyring of signer1930   * @param address substrate address of a recepient1931   * @param amount amount of tokens to be transfered1932   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);1933   * @returns ```true``` if extrinsic success, otherwise ```false```1934   */1935  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {1936    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`);19371938    let transfer = {from: null, to: null, amount: 0n} as any;1939    result.result.events.forEach(({event: {data, method, section}}) => {1940      if ((section === 'balances') && (method === 'Transfer')) {1941        transfer = {1942          from: this.helper.address.normalizeSubstrate(data[0]),1943          to: this.helper.address.normalizeSubstrate(data[1]),1944          amount: BigInt(data[2]),1945        };1946      }1947    });1948    let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;1949    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;1950    isSuccess = isSuccess && BigInt(amount) === transfer.amount;1951    return isSuccess;1952  }1953}195419551956class AddressGroup extends HelperGroup {1957  /**1958   * Normalizes the address to the specified ss58 format, by default ```42```.1959   * @param address substrate address1960   * @param ss58Format format for address conversion, by default ```42```1961   * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY1962   * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation1963   */1964  normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {1965    return this.helper.util.normalizeSubstrateAddress(address, ss58Format);1966  }19671968  /**1969   * Get address in the connected chain format1970   * @param address substrate address1971   * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network1972   * @returns address in chain format1973   */1974  async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise<TSubstrateAccount> {1975    const info = this.helper.chain.getChainProperties();1976    return encodeAddress(decodeAddress(address), info.ss58Format);1977  }19781979  /**1980   * Get substrate mirror of an ethereum address1981   * @param ethAddress ethereum address1982   * @param toChainFormat false for normalized account1983   * @example ethToSubstrate('0x9F0583DbB855d...')1984   * @returns substrate mirror of a provided ethereum address1985   */1986  async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise<TSubstrateAccount> {1987    if(!toChainFormat) return evmToAddress(ethAddress);1988    const info = this.helper.chain.getChainProperties();1989    return evmToAddress(ethAddress, info.ss58Format);1990  }19911992  /**1993   * Get ethereum mirror of a substrate address1994   * @param subAddress substrate account1995   * @example substrateToEth("5DnSF6RRjwteE3BrC...")1996   * @returns ethereum mirror of a provided substrate address1997   */1998  substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {1999    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join(''));2000  }2001}200220032004export class UniqueHelper extends ChainHelperBase {2005  chain: ChainGroup;2006  balance: BalanceGroup;2007  address: AddressGroup;2008  collection: CollectionGroup;2009  nft: NFTGroup;2010  rft: RFTGroup;2011  ft: FTGroup;20122013  constructor(logger?: ILogger) {2014    super(logger);2015    this.chain = new ChainGroup(this);2016    this.balance = new BalanceGroup(this);2017    this.address = new AddressGroup(this);2018    this.collection = new CollectionGroup(this);2019    this.nft = new NFTGroup(this);2020    this.rft = new RFTGroup(this);2021    this.ft = new FTGroup(this);2022  }2023}202420252026class UniqueCollectionBase {2027  helper: UniqueHelper;2028  collectionId: number;20292030  constructor(collectionId: number, uniqueHelper: UniqueHelper) {2031    this.collectionId = collectionId;2032    this.helper = uniqueHelper;2033  }20342035  async getData() {2036    return await this.helper.collection.getData(this.collectionId);2037  }20382039  async getLastTokenId() {2040    return await this.helper.collection.getLastTokenId(this.collectionId);2041  }20422043  async isTokenExists(tokenId: number) {2044    return await this.helper.collection.isTokenExists(this.collectionId, tokenId);2045  }20462047  async getAdmins() {2048    return await this.helper.collection.getAdmins(this.collectionId);2049  }20502051  async getAllowList() {2052    return await this.helper.collection.getAllowList(this.collectionId);2053  }20542055  async getEffectiveLimits() {2056    return await this.helper.collection.getEffectiveLimits(this.collectionId);2057  }20582059  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) {2060    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label);2061  }20622063  async confirmSponsorship(signer: TSigner, label?: string) {2064    return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label);2065  }20662067  async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) {2068    return await this.helper.collection.setLimits(signer, this.collectionId, limits, label);2069  }20702071  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) {2072    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label);2073  }20742075  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2076    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label);2077  }20782079  async addToAllowList(signer: TSigner, addressObj: ICrossAccountId, label?: string) {2080    return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj, label);2081  }20822083  async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId, label?: string) {2084    return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj, label);2085  }20862087  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2088    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label);2089  }20902091  async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2092    return await this.helper.collection.setProperties(signer, this.collectionId, properties, label);2093  }20942095  async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2096    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label);2097  }20982099  async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2100    return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2101  }21022103  async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) {2104    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label);2105  }21062107  async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) {2108    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label);2109  }21102111  async disableNesting(signer: TSigner, label?: string) {2112    return await this.helper.collection.disableNesting(signer, this.collectionId, label);2113  }21142115  async burn(signer: TSigner, label?: string) {2116    return await this.helper.collection.burn(signer, this.collectionId, label);2117  }2118}211921202121class UniqueNFTCollection extends UniqueCollectionBase {2122  getTokenObject(tokenId: number) {2123    return new UniqueNFTToken(tokenId, this);2124  }21252126  async getTokensByAddress(addressObj: ICrossAccountId) {2127    return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);2128  }21292130  async getToken(tokenId: number, blockHashAt?: string) {2131    return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt);2132  }21332134  async getTokenOwner(tokenId: number, blockHashAt?: string) {2135    return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);2136  }21372138  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {2139    return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);2140  }21412142  async getTokenChildren(tokenId: number, blockHashAt?: string) {2143    return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2144  }21452146  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2147    return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2148  }21492150  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2151    return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);2152  }21532154  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {2155    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label);2156  }21572158  async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {2159    return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);2160  }21612162  async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) {2163    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label);2164  }21652166  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) {2167    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label);2168  }21692170  async burnToken(signer: TSigner, tokenId: number, label?: string) {2171    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label);2172  }21732174  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2175    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2176  }21772178  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2179    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2180  }21812182  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2183    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2184  }21852186  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) {2187    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label);2188  }21892190  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2191    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label);2192  }2193}219421952196class UniqueRFTCollection extends UniqueCollectionBase {2197  getTokenObject(tokenId: number) {2198    return new UniqueRFTToken(tokenId, this);2199  }22002201  async getTokensByAddress(addressObj: ICrossAccountId) {2202    return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2203  }22042205  async getTop10TokenOwners(tokenId: number) {2206    return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);2207  }22082209  async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {2210    return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);2211  }22122213  async getTokenTotalPieces(tokenId: number) {2214    return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2215  }22162217  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) {2218    return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2219  }22202221  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2222    return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);2223  }22242225  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2226    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount);2227  }22282229  async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2230    return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);2231  }22322233  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) {2234    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label);2235  }22362237  async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) {2238    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label);2239  }22402241  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) {2242    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label);2243  }22442245  async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) {2246    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount);2247  }22482249  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2250    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2251  }22522253  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2254    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2255  }22562257  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2258    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2259  }2260}226122622263class UniqueFTCollection extends UniqueCollectionBase {2264  async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) {2265    return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label);2266  }22672268  async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) {2269    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label);2270  }22712272  async getBalance(addressObj: ICrossAccountId) {2273    return await this.helper.ft.getBalance(this.collectionId, addressObj);2274  }22752276  async getTop10Owners() {2277    return await this.helper.ft.getTop10Owners(this.collectionId);2278  }22792280  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) {2281    return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);2282  }22832284  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {2285    return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);2286  }22872288  async burnTokens(signer: TSigner, amount: bigint, label?: string) {2289    return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label);2290  }22912292  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) {2293    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label);2294  }22952296  async getTotalPieces() {2297    return await this.helper.ft.getTotalPieces(this.collectionId);2298  }22992300  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2301    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label);2302  }23032304  async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2305    return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);2306  }2307}230823092310class UniqueTokenBase implements IToken {2311  collection: UniqueNFTCollection | UniqueRFTCollection;2312  collectionId: number;2313  tokenId: number;23142315  constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {2316    this.collection = collection;2317    this.collectionId = collection.collectionId;2318    this.tokenId = tokenId;2319  }23202321  async getNextSponsored(addressObj: ICrossAccountId) {2322    return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2323  }23242325  async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2326    return await this.collection.setTokenProperties(signer, this.tokenId, properties, label);2327  }23282329  async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2330    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label);2331  }2332}233323342335class UniqueNFTToken extends UniqueTokenBase {2336  collection: UniqueNFTCollection;23372338  constructor(tokenId: number, collection: UniqueNFTCollection) {2339    super(tokenId, collection);2340    this.collection = collection;2341  }23422343  async getData(blockHashAt?: string) {2344    return await this.collection.getToken(this.tokenId, blockHashAt);2345  }23462347  async getOwner(blockHashAt?: string) {2348    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);2349  }23502351  async getTopmostOwner(blockHashAt?: string) {2352    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);2353  }23542355  async getChildren(blockHashAt?: string) {2356    return await this.collection.getTokenChildren(this.tokenId, blockHashAt);2357  }23582359  async nest(signer: TSigner, toTokenObj: IToken, label?: string) {2360    return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label);2361  }23622363  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2364    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label);2365  }23662367  async transfer(signer: TSigner, addressObj: ICrossAccountId) {2368    return await this.collection.transferToken(signer, this.tokenId, addressObj);2369  }23702371  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2372    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);2373  }23742375  async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) {2376    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label);2377  }23782379  async isApproved(toAddressObj: ICrossAccountId) {2380    return await this.collection.isTokenApproved(this.tokenId, toAddressObj);2381  }23822383  async burn(signer: TSigner, label?: string) {2384    return await this.collection.burnToken(signer, this.tokenId, label);2385  }2386}23872388class UniqueRFTToken extends UniqueTokenBase {2389  collection: UniqueRFTCollection;23902391  constructor(tokenId: number, collection: UniqueRFTCollection) {2392    super(tokenId, collection);2393    this.collection = collection;2394  }23952396  async getTop10Owners() {2397    return await this.collection.getTop10TokenOwners(this.tokenId);2398  }23992400  async getBalance(addressObj: ICrossAccountId) {2401    return await this.collection.getTokenBalance(this.tokenId, addressObj);2402  }24032404  async getTotalPieces() {2405    return await this.collection.getTokenTotalPieces(this.tokenId);2406  }24072408  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) {2409    return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);2410  }24112412  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2413    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);2414  }24152416  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2417    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label);2418  }24192420  async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {2421    return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);2422  }24232424  async repartition(signer: TSigner, amount: bigint, label?: string) {2425    return await this.collection.repartitionToken(signer, this.tokenId, amount, label);2426  }24272428  async burn(signer: TSigner, amount=100n, label?: string) {2429    return await this.collection.burnToken(signer, this.tokenId, amount, label);2430  }2431}