git.delta.rocks / unique-network / refs/commits / 9f934867f3c4

difftreelog

add Apache-2.0 license identifier

Max Andreev2022-08-12parent: #00b2c1c.patch.diff
in: master

2 files changed

modifiedtests/src/util/playgrounds/index.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/index.ts
+++ b/tests/src/util/playgrounds/index.ts
@@ -1,3 +1,6 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
 import {IKeyringPair} from '@polkadot/types/types';
 import {UniqueHelper} from './unique';
 import config from '../../config';
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
before · tests/src/util/playgrounds/unique.ts
1/* eslint-disable @typescript-eslint/no-var-requires */2/* eslint-disable function-call-argument-newline */3/* eslint-disable no-prototype-builtins */45import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';6import {ApiInterfaceEvents} from '@polkadot/api/types';7import {IKeyringPair} from '@polkadot/types/types';8import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';91011const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {12  const address = {} as ICrossAccountId;13  if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate;14  if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum;15  return address;16};171819const nesting = {20  toChecksumAddress(address: string): string {21    if (typeof address === 'undefined') return '';2223    if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);2425    address = address.toLowerCase().replace(/^0x/i,'');26    const addressHash = keccakAsHex(address).replace(/^0x/i,'');27    const checksumAddress = ['0x'];2829    for (let i = 0; i < address.length; i++) {30      // If ith character is 8 to f then make it uppercase31      if (parseInt(addressHash[i], 16) > 7) {32        checksumAddress.push(address[i].toUpperCase());33      } else {34        checksumAddress.push(address[i]);35      }36    }37    return checksumAddress.join('');38  },39  tokenIdToAddress(collectionId: number, tokenId: number) {40    return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8, '0')}${tokenId.toString(16).padStart(8, '0')}`);41  },42};434445interface IChainEvent {46  data: any;47  method: string;48  section: string;49}5051interface ITransactionResult {52    status: 'Fail' | 'Success';53    result: {54        events: {55          event: IChainEvent56        }[];57    },58    moduleError?: string;59}6061interface ILogger {62  log: (msg: any, level?: string) => void;63  level: {64    ERROR: 'ERROR';65    WARNING: 'WARNING';66    INFO: 'INFO';67    [key: string]: string;68  }69}7071interface IUniqueHelperLog {72  executedAt: number;73  executionTime: number;74  type: 'extrinsic' | 'rpc';75  status: 'Fail' | 'Success';76  call: string;77  params: any[];78  moduleError?: string;79  events?: any;80}8182interface IApiListeners {83  connected?: (...args: any[]) => any;84  disconnected?: (...args: any[]) => any;85  error?: (...args: any[]) => any;86  ready?: (...args: any[]) => any; 87  decorated?: (...args: any[]) => any;88}8990interface ICrossAccountId {91  Substrate?: TSubstrateAccount;92  Ethereum?: TEthereumAccount;93}9495interface ICrossAccountIdLower {96  substrate?: TSubstrateAccount;97  ethereum?: TEthereumAccount;98}99100interface ICollectionLimits {101  accountTokenOwnershipLimit?: number | null;102  sponsoredDataSize?: number | null;103  sponsoredDataRateLimit?: {blocks: number} | {sponsoringDisabled: null} | null;104  tokenLimit?: number | null;105  sponsorTransferTimeout?: number | null;106  sponsorApproveTimeout?: number | null;107  ownerCanTransfer?: boolean | null;108  ownerCanDestroy?: boolean | null;109  transfersEnabled?: boolean | null;110}111112interface INestingPermissions {113  tokenOwner?: boolean;114  collectionAdmin?: boolean;115  restricted?: number[] | null;116}117118interface ICollectionPermissions {119  access?: 'Normal' | 'AllowList';120  mintMode?: boolean;121  nesting?: INestingPermissions;122}123124interface IProperty {125  key: string;126  value: string;127}128129interface ITokenPropertyPermission {130  key: string;131  permission: {132    mutable: boolean;133    tokenOwner: boolean;134    collectionAdmin: boolean;135  }136}137138interface IToken {139  collectionId: number;140  tokenId: number;141}142143interface ICollectionCreationOptions {144  name: string | number[];145  description: string | number[];146  tokenPrefix: string | number[];147  mode?: {148    nft?: null;149    refungible?: null;150    fungible?: number;151  }152  permissions?: ICollectionPermissions;153  properties?: IProperty[];154  tokenPropertyPermissions?: ITokenPropertyPermission[];155  limits?: ICollectionLimits;156  pendingSponsor?: TSubstrateAccount;157}158159interface IChainProperties {160  ss58Format: number;161  tokenDecimals: number[];162  tokenSymbol: string[]163}164165type TSubstrateAccount = string;166type TEthereumAccount = string;167type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';168type TUniqueNetworks = 'opal' | 'quartz' | 'unique';169type TSigner = IKeyringPair; // | 'string'170171class UniqueUtil {172  static transactionStatus = {173    NOT_READY: 'NotReady',174    FAIL: 'Fail',175    SUCCESS: 'Success',176  };177178  static chainLogType = {179    EXTRINSIC: 'extrinsic',180    RPC: 'rpc',181  };182183  static getNestingTokenAddress(collectionId: number, tokenId: number) {184    return nesting.tokenIdToAddress(collectionId, tokenId);185  }186187  static getDefaultLogger(): ILogger {188    return {189      log(msg: any, level = 'INFO') {190        console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));191      },192      level: {193        ERROR: 'ERROR',194        WARNING: 'WARNING',195        INFO: 'INFO',196      },197    };198  }199200  static vec2str(arr: string[] | number[]) {201    return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');202  }203204  static str2vec(string: string) {205    if (typeof string !== 'string') return string;206    return Array.from(string).map(x => x.charCodeAt(0));207  }208209  static fromSeed(seed: string, ss58Format = 42) {210    const keyring = new Keyring({type: 'sr25519', ss58Format});211    return keyring.addFromUri(seed);212  }213214  static normalizeSubstrateAddress(address: string, ss58Format = 42) {215    return encodeAddress(decodeAddress(address), ss58Format);216  }217218  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') {219    if (creationResult.status !== this.transactionStatus.SUCCESS) {220      throw Error(`Unable to create collection for ${label}`);221    }222223    let collectionId = null;224    creationResult.result.events.forEach(({event: {data, method, section}}) => {225      if ((section === 'common') && (method === 'CollectionCreated')) {226        collectionId = parseInt(data[0].toString(), 10);227      }228    });229230    if (collectionId === null) {231      throw Error(`No CollectionCreated event for ${label}`);232    }233234    return collectionId;235  }236237  static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') {238    if (creationResult.status !== this.transactionStatus.SUCCESS) {239      throw Error(`Unable to create tokens for ${label}`);240    }241    let success = false;242    const tokens = [] as any;243    creationResult.result.events.forEach(({event: {data, method, section}}) => {244      if (method === 'ExtrinsicSuccess') {245        success = true;246      } else if ((section === 'common') && (method === 'ItemCreated')) {247        tokens.push({248          collectionId: parseInt(data[0].toString(), 10),249          tokenId: parseInt(data[1].toString(), 10),250          owner: data[2].toJSON(),251        });252      }253    });254    return {success, tokens};255  }256257  static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') {258    if (burnResult.status !== this.transactionStatus.SUCCESS) {259      throw Error(`Unable to burn tokens for ${label}`);260    }261    let success = false;262    const tokens = [] as any;263    burnResult.result.events.forEach(({event: {data, method, section}}) => {264      if (method === 'ExtrinsicSuccess') {265        success = true;266      } else if ((section === 'common') && (method === 'ItemDestroyed')) {267        tokens.push({268          collectionId: parseInt(data[0].toString(), 10),269          tokenId: parseInt(data[1].toString(), 10),270          owner: data[2].toJSON(),271        });272      }273    });274    return {success, tokens};275  }276277  static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) {278    let eventId = null;279    events.forEach(({event: {data, method, section}}) => {280      if ((section === expectedSection) && (method === expectedMethod)) {281        eventId = parseInt(data[0].toString(), 10);282      }283    });284285    if (eventId === null) {286      throw Error(`No ${expectedMethod} event for ${label}`);287    }288    return eventId === collectionId;289  }290291  static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {292    const normalizeAddress = (address: string | ICrossAccountId) => {293      if(typeof address === 'string') return address;294      const obj = {} as any;295      Object.keys(address).forEach(k => {296        obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];297      });298      if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)};299      if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()};300      return address;301    };302    let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;303    events.forEach(({event: {data, method, section}}) => {304      if ((section === 'common') && (method === 'Transfer')) {305        const hData = (data as any).toJSON();306        transfer = {307          collectionId: hData[0],308          tokenId: hData[1],309          from: normalizeAddress(hData[2]),310          to: normalizeAddress(hData[3]),311          amount: BigInt(hData[4]),312        };313      }314    });315    let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;316    isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);317    isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);318    isSuccess = isSuccess && amount === transfer.amount;319    return isSuccess;320  }321}322323324class ChainHelperBase {325  transactionStatus = UniqueUtil.transactionStatus;326  chainLogType = UniqueUtil.chainLogType;327  util: typeof UniqueUtil;328  logger: ILogger;329  api: ApiPromise | null;330  forcedNetwork: TUniqueNetworks | null;331  network: TUniqueNetworks | null;332  chainLog: IUniqueHelperLog[];333334  constructor(logger?: ILogger) {335    this.util = UniqueUtil;336    if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();337    this.logger = logger;338    this.api = null;339    this.forcedNetwork = null;340    this.network = null;341    this.chainLog = [];342  }343344  clearChainLog(): void {345    this.chainLog = [];346  }347348  forceNetwork(value: TUniqueNetworks): void {349    this.forcedNetwork = value;350  }351352  async connect(wsEndpoint: string, listeners?: IApiListeners) {353    if (this.api !== null) throw Error('Already connected');354    const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);355    this.api = api;356    this.network = network;357  }358359  async disconnect() {360    if (this.api === null) return;361    await this.api.disconnect();362    this.api = null;363    this.network = null;364  }365366  static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {367    const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;368    if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;369    return 'opal';370  }371372  static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {373    const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});374    await api.isReady;375376    const network = await this.detectNetwork(api);377378    await api.disconnect();379380    return network;381  }382383  static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{ 384    api: ApiPromise; 385    network: TUniqueNetworks; 386  }> {387    if(typeof network === 'undefined' || network === null) network = 'opal';388    const supportedRPC = {389      opal: {390        unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,391      },392      quartz: {393        unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,394      },395      unique: {396        unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,397      },398    };399    if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);400    const rpc = supportedRPC[network];401402    // TODO: investigate how to replace rpc in runtime403    // api._rpcCore.addUserInterfaces(rpc);404405    const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});406407    await api.isReadyOrError;408409    if (typeof listeners === 'undefined') listeners = {};410    for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {411      if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;412      api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);413    }414415    return {api, network};416  }417418  getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) {419    const {events, status} = data;420    if (status.isReady) {421      return this.transactionStatus.NOT_READY;422    }423    if (status.isBroadcast) {424      return this.transactionStatus.NOT_READY;425    }426    if (status.isInBlock || status.isFinalized) {427      const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed');428      if (errors.length > 0) {429        return this.transactionStatus.FAIL;430      }431      if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {432        return this.transactionStatus.SUCCESS;433      }434    }435436    return this.transactionStatus.FAIL;437  }438439  signTransaction(sender: TSigner, transaction: any, label = 'transaction', options = null) {440    const sign = (callback: any) => {441      if(options !== null) return transaction.signAndSend(sender, options, callback);442      return transaction.signAndSend(sender, callback);443    };444    return new Promise(async (resolve, reject) => {445      try {446        const unsub = await sign((result: any) => {447          const status = this.getTransactionStatus(result);448449          if (status === this.transactionStatus.SUCCESS) {450            this.logger.log(`${label} successful`);451            unsub();452            resolve({result, status});453          } else if (status === this.transactionStatus.FAIL) {454            let moduleError = null;455456            if (result.hasOwnProperty('dispatchError')) {457              const dispatchError = result['dispatchError'];458459              if (dispatchError && dispatchError.isModule) {460                const modErr = dispatchError.asModule;461                const errorMeta = dispatchError.registry.findMetaError(modErr);462463                moduleError = `${errorMeta.section}.${errorMeta.name}`;464              }465              else {466                this.logger.log(result, this.logger.level.ERROR);467              }468            }469470            this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);471            unsub();472            reject({status, moduleError, result});473          }474        });475      } catch (e) {476        this.logger.log(e, this.logger.level.ERROR);477        reject(e);478      }479    });480  }481482  constructApiCall(apiCall: string, params: any[]) {483    if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);484    let call = this.api as any;485    for(const part of apiCall.slice(4).split('.')) {486      call = call[part];487    }488    return call(...params);489  }490491  async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') {492    if(this.api === null) throw Error('API not initialized');493    if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);494495    const startTime = (new Date()).getTime();496    let result: ITransactionResult;497    let events = [];498    try {499      result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult;500      events = result.result.events.map((x: any) => x.toHuman());501    }502    catch(e) {503      if(!(e as object).hasOwnProperty('status')) throw e;504      result = e as ITransactionResult;505    }506507    const endTime = (new Date()).getTime();508509    const log = {510      executedAt: endTime,511      executionTime: endTime - startTime,512      type: this.chainLogType.EXTRINSIC,513      status: result.status,514      call: extrinsic,515      params,516    } as IUniqueHelperLog;517518    if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError;519    if(events.length > 0) log.events = events;520521    this.chainLog.push(log);522523    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage);524    return result;525  }526527  async callRpc(rpc: string, params?: any[]) {528    if(typeof params === 'undefined') params = [];529    if(this.api === null) throw Error('API not initialized');530    if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);531532    const startTime = (new Date()).getTime();533    let result;534    let error = null;535    const log = {536      type: this.chainLogType.RPC,537      call: rpc,538      params,539    } as IUniqueHelperLog;540541    try {542      result = await this.constructApiCall(rpc, params);543    }544    catch(e) {545      error = e;546    }547548    const endTime = (new Date()).getTime();549550    log.executedAt = endTime;551    log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';552    log.executionTime = endTime - startTime;553554    this.chainLog.push(log);555556    if(error !== null) throw error;557558    return result;559  }560561  getSignerAddress(signer: IKeyringPair | string): string {562    if(typeof signer === 'string') return signer;563    return signer.address;564  }565}566567568class HelperGroup {569  helper: UniqueHelper;570571  constructor(uniqueHelper: UniqueHelper) {572    this.helper = uniqueHelper;573  }574}575576577class CollectionGroup extends HelperGroup {578  /**579 * Get number of blocks when sponsored transaction is available.580 *581 * @param collectionId ID of collection582 * @param tokenId ID of token583 * @param addressObj address for which the sponsorship is checked584 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});585 * @returns number of blocks or null if sponsorship hasn't been set586 */587  async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {588    return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();589  }590591  /**592   * Get the number of created collections.593   * 594   * @returns number of created collections595   */596  async getTotalCount(): Promise<number> {597    return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();598  }599600  /**601   * 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.602   * 603   * @param collectionId ID of collection604   * @example await getData(2)605   * @returns collection information object606   */607  async getData(collectionId: number): Promise<{608    id: number;609    name: string;610    description: string;611    tokensCount: number;612    admins: ICrossAccountId[];613    normalizedOwner: TSubstrateAccount;614    raw: any615  } | null> {616    const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);617    const humanCollection = collection.toHuman(), collectionData = {618      id: collectionId, name: null, description: null, tokensCount: 0, admins: [],619      raw: humanCollection,620    } as any, jsonCollection = collection.toJSON();621    if (humanCollection === null) return null;622    collectionData.raw.limits = jsonCollection.limits;623    collectionData.raw.permissions = jsonCollection.permissions;624    collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);625    for (const key of ['name', 'description']) {626      collectionData[key] = this.helper.util.vec2str(humanCollection[key]);627    }628629    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0;630    collectionData.admins = await this.getAdmins(collectionId);631632    return collectionData;633  }634635  /**636   * Get the normalized addresses of the collection's administrators.637   * 638   * @param collectionId ID of collection639   * @example await getAdmins(1)640   * @returns array of administrators641   */642  async getAdmins(collectionId: number): Promise<ICrossAccountId[]> {643    const normalized = [];644    for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) {645      if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)});646      else normalized.push(admin);647    }648    return normalized;649  }650651  /**652   * Get the effective limits of the collection instead of null for default values653   * 654   * @param collectionId ID of collection655   * @example await getEffectiveLimits(2)656   * @returns object of collection limits657   */658  async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {659    return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();660  }661662  /**663   * Burns the collection if the signer has sufficient permissions and collection is empty.664   * 665   * @param signer keyring of signer666   * @param collectionId ID of collection667   * @param label extra label for log668   * @example await helper.collection.burn(aliceKeyring, 3);669   * @returns ```true``` if extrinsic success, otherwise ```false```670   */671  async burn(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {672    if(typeof label === 'undefined') label = `collection #${collectionId}`;673    const result = await this.helper.executeExtrinsic(674      signer,675      'api.tx.unique.destroyCollection', [collectionId],676      true, `Unable to burn collection for ${label}`,677    );678679    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label);680  }681682  /**683   * Sets the sponsor for the collection (Requires the Substrate address).684   * 685   * @param signer keyring of signer686   * @param collectionId ID of collection687   * @param sponsorAddress Sponsor substrate address688   * @param label extra label for log689   * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")690   * @returns ```true``` if extrinsic success, otherwise ```false```691   */692  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise<boolean> {693    if(typeof label === 'undefined') label = `collection #${collectionId}`;694    const result = await this.helper.executeExtrinsic(695      signer,696      'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],697      true, `Unable to set collection sponsor for ${label}`,698    );699700    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label);701  }702703  /**704   * Confirms consent to sponsor the collection on behalf of the signer.705   * 706   * @param signer keyring of signer707   * @param collectionId ID of collection708   * @param label extra label for log709   * @example confirmSponsorship(aliceKeyring, 10)710   * @returns ```true``` if extrinsic success, otherwise ```false```711   */712  async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {713    if(typeof label === 'undefined') label = `collection #${collectionId}`;714    const result = await this.helper.executeExtrinsic(715      signer,716      'api.tx.unique.confirmSponsorship', [collectionId],717      true, `Unable to confirm collection sponsorship for ${label}`,718    );719720    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label);721  }722723  /**724   * Sets the limits of the collection. At least one limit must be specified for a correct call.725   * 726   * @param signer keyring of signer727   * @param collectionId ID of collection728   * @param limits collection limits object729   * @param label extra label for log730   * @example731   * await setLimits(732   *   aliceKeyring,733   *   10,734   *   {735   *     sponsorTransferTimeout: 0,736   *     ownerCanDestroy: false737   *   }738   * )739   * @returns ```true``` if extrinsic success, otherwise ```false```740   */741  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise<boolean> {742    if(typeof label === 'undefined') label = `collection #${collectionId}`;743    const result = await this.helper.executeExtrinsic(744      signer,745      'api.tx.unique.setCollectionLimits', [collectionId, limits],746      true, `Unable to set collection limits for ${label}`,747    );748749    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label);750  }751752  /**753   * Changes the owner of the collection to the new Substrate address.754   * 755   * @param signer keyring of signer756   * @param collectionId ID of collection757   * @param ownerAddress substrate address of new owner758   * @param label extra label for log759   * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")760   * @returns ```true``` if extrinsic success, otherwise ```false```761   */762  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise<boolean> {763    if(typeof label === 'undefined') label = `collection #${collectionId}`;764    const result = await this.helper.executeExtrinsic(765      signer,766      'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],767      true, `Unable to change collection owner for ${label}`,768    );769770    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label);771  }772773  /**774   * Adds a collection administrator. 775   * 776   * @param signer keyring of signer777   * @param collectionId ID of collection778   * @param adminAddressObj Administrator address (substrate or ethereum)779   * @param label extra label for log780   * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})781   * @returns ```true``` if extrinsic success, otherwise ```false```782   */783  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {784    if(typeof label === 'undefined') label = `collection #${collectionId}`;785    const result = await this.helper.executeExtrinsic(786      signer,787      'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],788      true, `Unable to add collection admin for ${label}`,789    );790791    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label);792  }793794  /**795   * Removes a collection administrator.796   * 797   * @param signer keyring of signer798   * @param collectionId ID of collection799   * @param adminAddressObj Administrator address (substrate or ethereum)800   * @param label extra label for log801   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})802   * @returns ```true``` if extrinsic success, otherwise ```false```803   */804  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {805    if(typeof label === 'undefined') label = `collection #${collectionId}`;806    const result = await this.helper.executeExtrinsic(807      signer,808      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],809      true, `Unable to remove collection admin for ${label}`,810    );811812    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label);813  }814815  /**816   * Sets onchain permissions for selected collection.817   * 818   * @param signer keyring of signer819   * @param collectionId ID of collection820   * @param permissions collection permissions object821   * @param label extra label for log822   * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});823   * @returns ```true``` if extrinsic success, otherwise ```false```824   */825  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise<boolean> {826    if(typeof label === 'undefined') label = `collection #${collectionId}`;827    const result = await this.helper.executeExtrinsic(828      signer,829      'api.tx.unique.setCollectionPermissions', [collectionId, permissions],830      true, `Unable to set collection permissions for ${label}`,831    );832833    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label);834  }835836  /**837   * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.838   * 839   * @param signer keyring of signer840   * @param collectionId ID of collection841   * @param permissions nesting permissions object842   * @param label extra label for log843   * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});844   * @returns ```true``` if extrinsic success, otherwise ```false```845   */846  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise<boolean> {847    return await this.setPermissions(signer, collectionId, {nesting: permissions}, label);848  }849850  /**851   * Disables nesting for selected collection.852   * 853   * @param signer keyring of signer854   * @param collectionId ID of collection855   * @param label extra label for log856   * @example disableNesting(aliceKeyring, 10);857   * @returns ```true``` if extrinsic success, otherwise ```false```858   */859  async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {860    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label);861  }862863  /**864   * Sets onchain properties to the collection.865   * 866   * @param signer keyring of signer867   * @param collectionId ID of collection868   * @param properties array of property objects869   * @param label extra label for log870   * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);871   * @returns ```true``` if extrinsic success, otherwise ```false```872   */873  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {874    if(typeof label === 'undefined') label = `collection #${collectionId}`;875    const result = await this.helper.executeExtrinsic(876      signer,877      'api.tx.unique.setCollectionProperties', [collectionId, properties],878      true, `Unable to set collection properties for ${label}`,879    );880881    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);882  }883884  /**885   * Deletes onchain properties from the collection.886   * 887   * @param signer keyring of signer888   * @param collectionId ID of collection889   * @param propertyKeys array of property keys to delete890   * @param label891   * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);892   * @returns ```true``` if extrinsic success, otherwise ```false```893   */894  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {895    if(typeof label === 'undefined') label = `collection #${collectionId}`;896    const result = await this.helper.executeExtrinsic(897      signer,898      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],899      true, `Unable to delete collection properties for ${label}`,900    );901902    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);903  }904905  /**906   * Changes the owner of the token.907   * 908   * @param signer keyring of signer909   * @param collectionId ID of collection910   * @param tokenId ID of token911   * @param addressObj address of a new owner912   * @param amount amount of tokens to be transfered. For NFT must be set to 1n913   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})914   * @returns true if the token success, otherwise false915   */916  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {917    const result = await this.helper.executeExtrinsic(918      signer,919      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],920      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,921    );922923    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);924  }925926  /**927   * 928   * Change ownership of a token(s) on behalf of the owner. 929   * 930   * @param signer keyring of signer931   * @param collectionId ID of collection932   * @param tokenId ID of token933   * @param fromAddressObj address on behalf of which the token will be sent934   * @param toAddressObj new token owner935   * @param amount amount of tokens to be transfered. For NFT must be set to 1n936   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})937   * @returns true if the token success, otherwise false938   */939  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {940    const result = await this.helper.executeExtrinsic(941      signer,942      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],943      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,944    );945    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);946  }947948  /**949   * 950   * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.951   * 952   * @param signer keyring of signer953   * @param collectionId ID of collection954   * @param tokenId ID of token955   * @param label 956   * @param amount amount of tokens to be burned. For NFT must be set to 1n957   * @example burnToken(aliceKeyring, 10, 5);958   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```959   */960  async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{961    success: boolean,962    token: number | null963  }> {964    if(typeof label === 'undefined') label = `collection #${collectionId}`;965    const burnResult = await this.helper.executeExtrinsic(966      signer,967      'api.tx.unique.burnItem', [collectionId, tokenId, amount],968      true, `Unable to burn token for ${label}`,969    );970    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);971    if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');972    return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};973  }974975  /**976   * Destroys a concrete instance of NFT on behalf of the owner977   * 978   * @param signer keyring of signer979   * @param collectionId ID of collection980   * @param fromAddressObj address on behalf of which the token will be burnt981   * @param tokenId ID of token982   * @param label 983   * @param amount amount of tokens to be burned. For NFT must be set to 1n984   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})985   * @returns ```true``` if extrinsic success, otherwise ```false```986   */987  async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {988    if(typeof label === 'undefined') label = `collection #${collectionId}`;989    const burnResult = await this.helper.executeExtrinsic(990      signer,991      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],992      true, `Unable to burn token from for ${label}`,993    );994    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);995    return burnedTokens.success && burnedTokens.tokens.length > 0;996  }997998  /**999   * Set, change, or remove approved address to transfer the ownership of the NFT.1000   * 1001   * @param signer keyring of signer1002   * @param collectionId ID of collection1003   * @param tokenId ID of token1004   * @param toAddressObj 1005   * @param label 1006   * @param amount amount of token to be approved. For NFT must be set to 1n1007   * @returns ```true``` if extrinsic success, otherwise ```false```1008   */1009  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {1010    if(typeof label === 'undefined') label = `collection #${collectionId}`;1011    const approveResult = await this.helper.executeExtrinsic(1012      signer, 1013      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1014      true, `Unable to approve token for ${label}`,1015    );10161017    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);1018  }10191020  /**1021   * Get the amount of token pieces approved to transfer1022   * @param collectionId ID of collection1023   * @param tokenId ID of token1024   * @param toAccountObj 1025   * @param fromAccountObj1026   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1027   * @returns number of approved to transfer pieces1028   */1029  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1030    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1031  }10321033  /**1034   * Get the last created token id1035   * @param collectionId ID of collection1036   * @example getLastTokenId(10);1037   * @returns id of the last created token1038   */1039  async getLastTokenId(collectionId: number): Promise<number> {1040    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1041  }10421043  /**1044   * Check if token exists1045   * @param collectionId ID of collection1046   * @param tokenId ID of token1047   * @example isTokenExists(10, 20);1048   * @returns true if the token exists, otherwise false1049   */1050  async isTokenExists(collectionId: number, tokenId: number): Promise<boolean> {1051    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1052  }1053}10541055class NFTnRFT extends CollectionGroup {1056  /**1057   * Get tokens owned by account1058   * 1059   * @param collectionId ID of collection1060   * @param addressObj tokens owner1061   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1062   * @returns array of token ids owned by account1063   */1064  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1065    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1066  }10671068  /**1069   * Get token data1070   * @param collectionId ID of collection1071   * @param tokenId ID of token1072   * @param blockHashAt 1073   * @param propertyKeys1074   * @example getToken(10, 5);1075   * @returns human readable token data 1076   */1077  async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{1078    properties: IProperty[];1079    owner: ICrossAccountId;1080    normalizedOwner: ICrossAccountId;1081  }| null> {1082    let tokenData;1083    if(typeof blockHashAt === 'undefined') {1084      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1085    }1086    else {1087      if(typeof propertyKeys === 'undefined') {1088        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1089        if(!collection) return null;1090        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1091      }1092      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1093    }1094    tokenData = tokenData.toHuman();1095    if (tokenData === null || tokenData.owner === null) return null;1096    const owner = {} as any;1097    for (const key of Object.keys(tokenData.owner)) {1098      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];1099    }1100    tokenData.normalizedOwner = crossAccountIdFromLower(owner);1101    return tokenData;1102  }11031104  /**1105   * Set permissions to change token properties1106   * @param signer keyring of signer1107   * @param collectionId ID of collection1108   * @param permissions permissions to change a property by the collection owner or admin1109   * @param label 1110   * @example setTokenPropertyPermissions(1111   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1112   * )1113   * @returns true if extrinsic success otherwise false1114   */1115  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {1116    if(typeof label === 'undefined') label = `collection #${collectionId}`;1117    const result = await this.helper.executeExtrinsic(1118      signer,1119      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1120      true, `Unable to set token property permissions for ${label}`,1121    );11221123    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);1124  }11251126  /**1127   * Set token properties1128   * @param signer keyring of signer1129   * @param collectionId ID of collection1130   * @param tokenId ID of token1131   * @param properties 1132   * @param label 1133   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1134   * @returns ```true``` if extrinsic success, otherwise ```false```1135   */1136  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {1137    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1138    const result = await this.helper.executeExtrinsic(1139      signer,1140      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1141      true, `Unable to set token properties for ${label}`,1142    );11431144    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);1145  }11461147  /**1148   * Delete the provided properties of a token1149   * @param signer keyring of signer1150   * @param collectionId ID of collection1151   * @param tokenId ID of token1152   * @param propertyKeys property keys to be deleted 1153   * @param label 1154   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1155   * @returns ```true``` if extrinsic success, otherwise ```false```1156   */1157  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {1158    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1159    const result = await this.helper.executeExtrinsic(1160      signer,1161      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1162      true, `Unable to delete token properties for ${label}`,1163    );11641165    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);1166  }11671168  /**1169   * Mint new collection1170   * @param signer keyring of signer1171   * @param collectionOptions basic collection options and properties 1172   * @param mode NFT or RFT type of a collection1173   * @param errorLabel 1174   * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1175   * @returns object of the created collection1176   */1177  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {1178    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1179    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1180    for (const key of ['name', 'description', 'tokenPrefix']) {1181      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);1182    }1183    const creationResult = await this.helper.executeExtrinsic(1184      signer,1185      'api.tx.unique.createCollectionEx', [collectionOptions],1186      true, errorLabel,1187    );1188    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1189  }11901191  getCollectionObject(collectionId: number): any {1192    return null;1193  }11941195  getTokenObject(collectionId: number, tokenId: number): any {1196    return null;1197  }1198}119912001201class NFTGroup extends NFTnRFT {1202  /**1203   * Get collection object1204   * @param collectionId ID of collection1205   * @example getCollectionObject(2);1206   * @returns instance of UniqueNFTCollection1207   */1208  getCollectionObject(collectionId: number): UniqueNFTCollection {1209    return new UniqueNFTCollection(collectionId, this.helper);1210  }12111212  /**1213   * Get token object1214   * @param collectionId ID of collection1215   * @param tokenId ID of token1216   * @example getTokenObject(10, 5);1217   * @returns instance of UniqueNFTToken1218   */1219  getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {1220    return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));1221  }12221223  /**1224   * Get token's owner1225   * @param collectionId ID of collection1226   * @param tokenId ID of token1227   * @param blockHashAt 1228   * @example getTokenOwner(10, 5);1229   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1230   */1231  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {1232    let owner;1233    if (typeof blockHashAt === 'undefined') {1234      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1235    } else {1236      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1237    }1238    return crossAccountIdFromLower(owner.toJSON());1239  }12401241  /**1242   * Is token approved to transfer1243   * @param collectionId ID of collection1244   * @param tokenId ID of token1245   * @param toAccountObj address to be approved1246   * @returns ```true``` if extrinsic success, otherwise ```false```1247   */1248  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1249    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1250  }12511252  /**1253   * Changes the owner of the token.1254   * 1255   * @param signer keyring of signer1256   * @param collectionId ID of collection1257   * @param tokenId ID of token1258   * @param addressObj address of a new owner1259   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1260   * @returns ```true``` if extrinsic success, otherwise ```false```1261   */1262  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1263    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1264  }12651266  /**1267   * 1268   * Change ownership of a NFT on behalf of the owner. 1269   * 1270   * @param signer keyring of signer1271   * @param collectionId ID of collection1272   * @param tokenId ID of token1273   * @param fromAddressObj address on behalf of which the token will be sent1274   * @param toAddressObj new token owner1275   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1276   * @returns ```true``` if extrinsic success, otherwise ```false```1277   */1278  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1279    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1280  }12811282  /**1283   * Recursively find the address that owns the token1284   * @param collectionId ID of collection1285   * @param tokenId ID of token1286   * @param blockHashAt 1287   * @example getTokenTopmostOwner(10, 5);1288   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1289   */1290  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {1291    let owner;1292    if (typeof blockHashAt === 'undefined') {1293      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1294    } else {1295      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1296    }12971298    if (owner === null) return null;12991300    owner = owner.toHuman();13011302    return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;1303  }13041305  /**1306   * Get tokens nested in the provided token1307   * @param collectionId ID of collection1308   * @param tokenId ID of token1309   * @param blockHashAt 1310   * @example getTokenChildren(10, 5);1311   * @returns tokens whose depth of nesting is <= 5 1312   */1313  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1314    let children;1315    if(typeof blockHashAt === 'undefined') {1316      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1317    } else {1318      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1319    }13201321    return children.toJSON().map((x: any) => {1322      return {collectionId: x.collection, tokenId: x.token};1323    });1324  }13251326  /**1327   * Nest one token into another1328   * @param signer keyring of signer1329   * @param tokenObj token to be nested1330   * @param rootTokenObj token to be parent1331   * @param label 1332   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1333   * @returns ```true``` if extrinsic success, otherwise ```false```1334   */1335  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {1336    const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1337    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1338    if(!result) {1339      throw Error(`Unable to nest token for ${label}`);1340    }1341    return result;1342  }13431344  /**1345   * Remove token from nested state1346   * @param signer keyring of signer1347   * @param tokenObj token to unnest1348   * @param rootTokenObj parent of a token1349   * @param toAddressObj address of a new token owner 1350   * @param label 1351   * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1352   * @returns ```true``` if extrinsic success, otherwise ```false```1353   */1354  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {1355    const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1356    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1357    if(!result) {1358      throw Error(`Unable to unnest token for ${label}`);1359    }1360    return result;1361  }13621363  /**1364   * Mint new collection1365   * @param signer keyring of signer1366   * @param collectionOptions Collection options1367   * @param label 1368   * @example 1369   * mintCollection(aliceKeyring, {1370   *   name: 'New',1371   *   description: 'New collection',1372   *   tokenPrefix: 'NEW',1373   * })1374   * @returns object of the created collection1375   */1376  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {1377    return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;1378  }13791380  /**1381   * Mint new token1382   * @param signer keyring of signer1383   * @param data token data1384   * @param label 1385   * @returns created token object1386   */1387  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {1388    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1389    const creationResult = await this.helper.executeExtrinsic(1390      signer,1391      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1392        nft: {1393          properties: data.properties,1394        },1395      }],1396      true, `Unable to mint NFT token for ${label}`,1397    );1398    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1399    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1400    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1401    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1402  }14031404  /**1405   * Mint multiple NFT tokens1406   * @param signer keyring of signer1407   * @param collectionId ID of collection1408   * @param tokens array of tokens with owner and properties1409   * @param label 1410   * @example 1411   * mintMultipleTokens(aliceKeyring, 10, [{1412   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1413   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1414   *   },{1415   *     owner: {Ethereum: "0x9F0583DbB855d..."},1416   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1417   * }]);1418   * @returns ```true``` if extrinsic success, otherwise ```false```1419   */1420  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1421    if(typeof label === 'undefined') label = `collection #${collectionId}`;1422    const creationResult = await this.helper.executeExtrinsic(1423      signer,1424      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1425      true, `Unable to mint NFT tokens for ${label}`,1426    );1427    const collection = this.getCollectionObject(collectionId);1428    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1429  }14301431  /**1432   * Mint multiple NFT tokens with one owner1433   * @param signer keyring of signer1434   * @param collectionId ID of collection1435   * @param owner tokens owner1436   * @param tokens array of tokens with owner and properties1437   * @param label 1438   * @example1439   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1440   *   properties: [{1441   *   key: "gender",1442   *   value: "female",1443   *  },{1444   *   key: "age",1445   *   value: "33",1446   *  }],1447   * }]);1448   * @returns array of newly created tokens1449   */1450  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1451    if(typeof label === 'undefined') label = `collection #${collectionId}`;1452    const rawTokens = [];1453    for (const token of tokens) {1454      const raw = {NFT: {properties: token.properties}};1455      rawTokens.push(raw);1456    }1457    const creationResult = await this.helper.executeExtrinsic(1458      signer,1459      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1460      true, `Unable to mint NFT tokens for ${label}`,1461    );1462    const collection = this.getCollectionObject(collectionId);1463    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1464  }14651466  /**1467   * Destroys a concrete instance of NFT.1468   * @param signer keyring of signer1469   * @param collectionId ID of collection1470   * @param tokenId ID of token1471   * @param label 1472   * @example burnToken(aliceKeyring, 10, 5);1473   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1474   */1475  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {1476    return await super.burnToken(signer, collectionId, tokenId, label, 1n);1477  }14781479  /**1480   * Set, change, or remove approved address to transfer the ownership of the NFT.1481   * 1482   * @param signer keyring of signer1483   * @param collectionId ID of collection1484   * @param tokenId ID of token1485   * @param toAddressObj address to approve1486   * @param label 1487   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1488   * @returns ```true``` if extrinsic success, otherwise ```false```1489   */1490  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {1491    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, 1n);1492  }1493}149414951496class RFTGroup extends NFTnRFT {1497  /**1498   * Get collection object1499   * @param collectionId ID of collection1500   * @example getCollectionObject(2);1501   * @returns instance of UniqueRFTCollection1502   */1503  getCollectionObject(collectionId: number): UniqueRFTCollection {1504    return new UniqueRFTCollection(collectionId, this.helper);1505  }15061507  /**1508   * Get token object1509   * @param collectionId ID of collection1510   * @param tokenId ID of token1511   * @example getTokenObject(10, 5);1512   * @returns instance of UniqueNFTToken1513   */1514  getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {1515    return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));1516  }15171518  /**1519   * Get top 10 token owners with the largest number of pieces 1520   * @param collectionId ID of collection1521   * @param tokenId ID of token1522   * @example getTokenTop10Owners(10, 5);1523   * @returns array of top 10 owners1524   */1525  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {1526    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);1527  }15281529  /**1530   * Get number of pieces owned by address1531   * @param collectionId ID of collection1532   * @param tokenId ID of token1533   * @param addressObj address token owner1534   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1535   * @returns number of pieces ownerd by address1536   */1537  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1538    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1539  }15401541  /**1542   * Transfer pieces of token to another address1543   * @param signer keyring of signer1544   * @param collectionId ID of collection1545   * @param tokenId ID of token1546   * @param addressObj address of a new owner1547   * @param amount number of pieces to be transfered1548   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1549   * @returns ```true``` if extrinsic success, otherwise ```false```1550   */1551  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {1552    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1553  }15541555  /**1556   * Change ownership of some pieces of RFT on behalf of the owner. 1557   * @param signer keyring of signer1558   * @param collectionId ID of collection1559   * @param tokenId ID of token1560   * @param fromAddressObj address on behalf of which the token will be sent1561   * @param toAddressObj new token owner1562   * @param amount number of pieces to be transfered1563   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1564   * @returns ```true``` if extrinsic success, otherwise ```false```1565   */1566  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {1567    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1568  }15691570  /**1571   * Mint new collection1572   * @param signer keyring of signer1573   * @param collectionOptions Collection options1574   * @param label 1575   * @example1576   * mintCollection(aliceKeyring, {1577   *   name: 'New',1578   *   description: 'New collection',1579   *   tokenPrefix: 'NEW',1580   * })1581   * @returns object of the created collection1582   */1583  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {1584    return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;1585  }15861587  /**1588   * Mint new token1589   * @param signer keyring of signer1590   * @param data token data1591   * @param label 1592   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1593   * @returns created token object1594   */1595  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {1596    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1597    const creationResult = await this.helper.executeExtrinsic(1598      signer,1599      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1600        refungible: {1601          pieces: data.pieces,1602          properties: data.properties,1603        },1604      }],1605      true, `Unable to mint RFT token for ${label}`,1606    );1607    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1608    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1609    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1610    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1611  }16121613  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1614    throw Error('Not implemented');1615    if(typeof label === 'undefined') label = `collection #${collectionId}`;1616    const creationResult = await this.helper.executeExtrinsic(1617      signer,1618      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1619      true, `Unable to mint RFT tokens for ${label}`,1620    );1621    const collection = this.getCollectionObject(collectionId);1622    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1623  }16241625  /**1626   * Mint multiple RFT tokens with one owner1627   * @param signer keyring of signer1628   * @param collectionId ID of collection1629   * @param owner tokens owner1630   * @param tokens array of tokens with properties and pieces1631   * @param label 1632   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1633   * @returns array of newly created RFT tokens1634   */1635  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1636    if(typeof label === 'undefined') label = `collection #${collectionId}`;1637    const rawTokens = [];1638    for (const token of tokens) {1639      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1640      rawTokens.push(raw);1641    }1642    const creationResult = await this.helper.executeExtrinsic(1643      signer,1644      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1645      true, `Unable to mint RFT tokens for ${label}`,1646    );1647    const collection = this.getCollectionObject(collectionId);1648    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1649  }16501651  /**1652   * Destroys a concrete instance of RFT.1653   * @param signer keyring of signer1654   * @param collectionId ID of collection1655   * @param tokenId ID of token1656   * @param label 1657   * @param amount number of pieces to be burnt1658   * @example burnToken(aliceKeyring, 10, 5);1659   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1660   */1661  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {1662    return await super.burnToken(signer, collectionId, tokenId, label, amount);1663  }16641665  /**1666   * Set, change, or remove approved address to transfer the ownership of the RFT.1667   * 1668   * @param signer keyring of signer1669   * @param collectionId ID of collection1670   * @param tokenId ID of token1671   * @param toAddressObj address to approve1672   * @param label 1673   * @param amount number of pieces to be approved1674   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1675   * @returns true if the token success, otherwise false1676   */1677  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {1678    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1679  }16801681  /**1682   * Get total number of pieces1683   * @param collectionId ID of collection1684   * @param tokenId ID of token1685   * @example getTokenTotalPieces(10, 5);1686   * @returns number of pieces1687   */1688  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1689    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1690  }16911692  /**1693   * Change number of token pieces. Signer must be the owner of all token pieces.1694   * @param signer keyring of signer1695   * @param collectionId ID of collection1696   * @param tokenId ID of token1697   * @param amount new number of pieces1698   * @param label 1699   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1700   * @returns true if the repartion was success, otherwise false1701   */1702  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {1703    if(typeof label === 'undefined') label = `collection #${collectionId}`;1704    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1705    const repartitionResult = await this.helper.executeExtrinsic(1706      signer,1707      'api.tx.unique.repartition', [collectionId, tokenId, amount],1708      true, `Unable to repartition RFT token for ${label}`,1709    );1710    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);1711    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);1712  }1713}171417151716class FTGroup extends CollectionGroup {1717  /**1718   * Get collection object1719   * @param collectionId ID of collection1720   * @example getCollectionObject(2);1721   * @returns instance of UniqueFTCollection1722   */1723  getCollectionObject(collectionId: number): UniqueFTCollection {1724    return new UniqueFTCollection(collectionId, this.helper);1725  }17261727  /**1728   * Mint new fungible collection1729   * @param signer keyring of signer1730   * @param collectionOptions Collection options1731   * @param decimalPoints number of token decimals 1732   * @param errorLabel 1733   * @example1734   * mintCollection(aliceKeyring, {1735   *   name: 'New',1736   *   description: 'New collection',1737   *   tokenPrefix: 'NEW',1738   * }, 18)1739   * @returns newly created fungible collection1740   */1741  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {1742    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1743    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1744    collectionOptions.mode = {fungible: decimalPoints};1745    for (const key of ['name', 'description', 'tokenPrefix']) {1746      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);1747    }1748    const creationResult = await this.helper.executeExtrinsic(1749      signer,1750      'api.tx.unique.createCollectionEx', [collectionOptions],1751      true, errorLabel,1752    );1753    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1754  }17551756  /**1757   * Mint tokens1758   * @param signer keyring of signer1759   * @param collectionId ID of collection1760   * @param owner address owner of new tokens1761   * @param amount amount of tokens to be meanted1762   * @param label 1763   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1764   * @returns ```true``` if extrinsic success, otherwise ```false``` 1765   */1766  async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {1767    if(typeof label === 'undefined') label = `collection #${collectionId}`;1768    const creationResult = await this.helper.executeExtrinsic(1769      signer,1770      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1771        fungible: {1772          value: amount,1773        },1774      }],1775      true, `Unable to mint fungible tokens for ${label}`,1776    );1777    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1778  }17791780  /**1781   * Mint multiple Fungible tokens with one owner1782   * @param signer keyring of signer1783   * @param collectionId ID of collection1784   * @param owner tokens owner1785   * @param tokens array of tokens with properties and pieces1786   * @param label 1787   * @returns ```true``` if extrinsic success, otherwise ```false``` 1788   */1789  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {1790    if(typeof label === 'undefined') label = `collection #${collectionId}`;1791    const rawTokens = [];1792    for (const token of tokens) {1793      const raw = {Fungible: {Value: token.value}};1794      rawTokens.push(raw);1795    }1796    const creationResult = await this.helper.executeExtrinsic(1797      signer,1798      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1799      true, `Unable to mint RFT tokens for ${label}`,1800    );1801    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1802  }18031804  /**1805   * Get the top 10 owners with the largest balance for the Fungible collection 1806   * @param collectionId ID of collection1807   * @example getTop10Owners(10);1808   * @returns array of ```ICrossAccountId```1809   */1810  async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {1811    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);1812  }18131814  /**1815   * Get account balance1816   * @param collectionId ID of collection1817   * @param addressObj address of owner1818   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})1819   * @returns amount of fungible tokens owned by address1820   */1821  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1822    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1823  }18241825  /**1826   * Transfer tokens to address1827   * @param signer keyring of signer1828   * @param collectionId ID of collection1829   * @param toAddressObj address recepient1830   * @param amount amount of tokens to be sent1831   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1832   * @returns ```true``` if extrinsic success, otherwise ```false``` 1833   */1834  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {1835    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1836  }18371838  /**1839   * Transfer some tokens on behalf of the owner.1840   * @param signer keyring of signer1841   * @param collectionId ID of collection1842   * @param fromAddressObj address on behalf of which tokens will be sent1843   * @param toAddressObj address where token to be sent1844   * @param amount number of tokens to be sent1845   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);1846   * @returns ```true``` if extrinsic success, otherwise ```false``` 1847   */1848  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {1849    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1850  }18511852  /**1853   * Destroy some amount of tokens1854   * @param signer keyring of signer1855   * @param collectionId ID of collection1856   * @param amount amount of tokens to be destroyed1857   * @param label 1858   * @example burnTokens(aliceKeyring, 10, 1000n);1859   * @returns ```true``` if extrinsic success, otherwise ```false``` 1860   */1861  async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {1862    return (await super.burnToken(signer, collectionId, 0, label, amount)).success;1863  }18641865  /**1866   * Burn some tokens on behalf of the owner.1867   * @param signer keyring of signer1868   * @param collectionId ID of collection1869   * @param fromAddressObj address on behalf of which tokens will be burnt1870   * @param amount amount of tokens to be burnt1871   * @param label 1872   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1873   * @returns ```true``` if extrinsic success, otherwise ```false``` 1874   */1875  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {1876    return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);1877  }18781879  /**1880   * Get total collection supply1881   * @param collectionId 1882   * @returns 1883   */1884  async getTotalPieces(collectionId: number): Promise<bigint> {1885    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();1886  }18871888  /**1889   * Set, change, or remove approved address to transfer tokens.1890   * 1891   * @param signer keyring of signer1892   * @param collectionId ID of collection1893   * @param toAddressObj address to be approved1894   * @param amount amount of tokens to be approved1895   * @param label 1896   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)1897   * @returns ```true``` if extrinsic success, otherwise ```false``` 1898   */1899  async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {1900    return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);1901  }19021903  /**1904   * Get amount of fungible tokens approved to transfer1905   * @param collectionId ID of collection1906   * @param fromAddressObj owner of tokens1907   * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner1908   * @returns number of tokens approved for the transfer1909   */1910  async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {1911    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);1912  }1913}191419151916class ChainGroup extends HelperGroup {1917  /**1918   * Get system properties of a chain1919   * @example getChainProperties();1920   * @returns ss58Format, token decimals, and token symbol1921   */1922  getChainProperties(): IChainProperties {1923    const properties = (this.helper.api as any).registry.getChainProperties().toJSON();1924    return {1925      ss58Format: properties.ss58Format.toJSON(),1926      tokenDecimals: properties.tokenDecimals.toJSON(),1927      tokenSymbol: properties.tokenSymbol.toJSON(),1928    };1929  }19301931  /**1932   * Get chain header1933   * @example getLatestBlockNumber();1934   * @returns the number of the last block1935   */1936  async getLatestBlockNumber(): Promise<number> {1937    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();1938  }19391940  /**1941   * Get block hash by block number1942   * @param blockNumber number of block1943   * @example getBlockHashByNumber(12345);1944   * @returns hash of a block1945   */1946  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {1947    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();1948    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;1949    return blockHash;1950  }19511952  /**1953   * Get account nonce1954   * @param address substrate address1955   * @example getNonce("5GrwvaEF5zXb26Fz...");1956   * @returns number, account's nonce1957   */1958  async getNonce(address: TSubstrateAccount): Promise<number> {1959    return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber();1960  }1961}196219631964class BalanceGroup extends HelperGroup {1965  /**1966   * Representation of the native token in the smallest unit1967   * @example getOneTokenNominal()1968   * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.1969   */1970  getOneTokenNominal(): bigint {1971    const chainProperties = this.helper.chain.getChainProperties();1972    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);1973  }19741975  /**1976   * Get substrate address balance1977   * @param address substrate address1978   * @example getSubstrate("5GrwvaEF5zXb26Fz...")1979   * @returns amount of tokens on address1980   */1981  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {1982    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();1983  }19841985  /**1986   * Get ethereum address balance1987   * @param address ethereum address1988   * @example getEthereum("0x9F0583DbB855d...")1989   * @returns amount of tokens on address1990   */1991  async getEthereum(address: TEthereumAccount): Promise<bigint> {1992    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();1993  }19941995  /**1996   * Transfer tokens to substrate address1997   * @param signer keyring of signer1998   * @param address substrate address of a recepient1999   * @param amount amount of tokens to be transfered2000   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2001   * @returns ```true``` if extrinsic success, otherwise ```false```2002   */2003  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2004    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}`);20052006    let transfer = {from: null, to: null, amount: 0n} as any;2007    result.result.events.forEach(({event: {data, method, section}}) => {2008      if ((section === 'balances') && (method === 'Transfer')) {2009        transfer = {2010          from: this.helper.address.normalizeSubstrate(data[0]),2011          to: this.helper.address.normalizeSubstrate(data[1]),2012          amount: BigInt(data[2]),2013        };2014      }2015    });2016    let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;2017    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;2018    isSuccess = isSuccess && BigInt(amount) === transfer.amount;2019    return isSuccess;2020  }2021}202220232024class AddressGroup extends HelperGroup {2025  /**2026   * Normalizes the address to the specified ss58 format, by default ```42```.2027   * @param address substrate address2028   * @param ss58Format format for address conversion, by default ```42```2029   * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2030   * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2031   */2032  normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2033    return this.helper.util.normalizeSubstrateAddress(address, ss58Format);2034  }20352036  /**2037   * Get address in the connected chain format2038   * @param address substrate address2039   * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2040   * @returns address in chain format2041   */2042  async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise<TSubstrateAccount> {2043    const info = this.helper.chain.getChainProperties();2044    return encodeAddress(decodeAddress(address), info.ss58Format);2045  }20462047  /**2048   * Get substrate mirror of an ethereum address2049   * @param ethAddress ethereum address2050   * @param toChainFormat false for normalized account2051   * @example ethToSubstrate('0x9F0583DbB855d...')2052   * @returns substrate mirror of a provided ethereum address2053   */2054  async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise<TSubstrateAccount> {2055    if(!toChainFormat) return evmToAddress(ethAddress);2056    const info = this.helper.chain.getChainProperties();2057    return evmToAddress(ethAddress, info.ss58Format);2058  }20592060  /**2061   * Get ethereum mirror of a substrate address2062   * @param subAddress substrate account2063   * @example substrateToEth("5DnSF6RRjwteE3BrC...")2064   * @returns ethereum mirror of a provided substrate address2065   */2066  substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2067    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join(''));2068  }2069}207020712072export class UniqueHelper extends ChainHelperBase {2073  chain: ChainGroup;2074  balance: BalanceGroup;2075  address: AddressGroup;2076  collection: CollectionGroup;2077  nft: NFTGroup;2078  rft: RFTGroup;2079  ft: FTGroup;20802081  constructor(logger?: ILogger) {2082    super(logger);2083    this.chain = new ChainGroup(this);2084    this.balance = new BalanceGroup(this);2085    this.address = new AddressGroup(this);2086    this.collection = new CollectionGroup(this);2087    this.nft = new NFTGroup(this);2088    this.rft = new RFTGroup(this);2089    this.ft = new FTGroup(this);2090  }  2091}209220932094class UniqueCollectionBase {2095  helper: UniqueHelper;2096  collectionId: number;20972098  constructor(collectionId: number, uniqueHelper: UniqueHelper) {2099    this.collectionId = collectionId;2100    this.helper = uniqueHelper;2101  }21022103  async getData() {2104    return await this.helper.collection.getData(this.collectionId);2105  }21062107  async getLastTokenId() {2108    return await this.helper.collection.getLastTokenId(this.collectionId);2109  }21102111  async isTokenExists(tokenId: number) {2112    return await this.helper.collection.isTokenExists(this.collectionId, tokenId);2113  }21142115  async getAdmins() {2116    return await this.helper.collection.getAdmins(this.collectionId);2117  }21182119  async getEffectiveLimits() {2120    return await this.helper.collection.getEffectiveLimits(this.collectionId);2121  }21222123  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) {2124    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label);2125  }21262127  async confirmSponsorship(signer: TSigner, label?: string) {2128    return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label);2129  }21302131  async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) {2132    return await this.helper.collection.setLimits(signer, this.collectionId, limits, label);2133  }21342135  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) {2136    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label);2137  }21382139  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2140    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label);2141  }21422143  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2144    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label);2145  }21462147  async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2148    return await this.helper.collection.setProperties(signer, this.collectionId, properties, label);2149  }21502151  async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2152    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label);2153  }21542155  async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2156    return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2157  }21582159  async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) {2160    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label);2161  }21622163  async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) {2164    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label);2165  }21662167  async disableNesting(signer: TSigner, label?: string) {2168    return await this.helper.collection.disableNesting(signer, this.collectionId, label);2169  }21702171  async burn(signer: TSigner, label?: string) {2172    return await this.helper.collection.burn(signer, this.collectionId, label);2173  }2174}217521762177class UniqueNFTCollection extends UniqueCollectionBase {2178  getTokenObject(tokenId: number) {2179    return new UniqueNFTToken(tokenId, this);2180  }21812182  async getTokensByAddress(addressObj: ICrossAccountId) {2183    return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);2184  }21852186  async getToken(tokenId: number, blockHashAt?: string) {2187    return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt);2188  }21892190  async getTokenOwner(tokenId: number, blockHashAt?: string) {2191    return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);2192  }21932194  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {2195    return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);2196  }21972198  async getTokenChildren(tokenId: number, blockHashAt?: string) {2199    return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2200  }22012202  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2203    return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2204  }22052206  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2207    return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);2208  }22092210  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {2211    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label);2212  }22132214  async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {2215    return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);2216  }22172218  async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) {2219    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label);2220  }22212222  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) {2223    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label);2224  }22252226  async burnToken(signer: TSigner, tokenId: number, label?: string) {2227    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label);2228  }22292230  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2231    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2232  }22332234  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2235    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2236  }22372238  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2239    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2240  }22412242  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) {2243    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label);2244  }22452246  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2247    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label);2248  }2249}225022512252class UniqueRFTCollection extends UniqueCollectionBase {2253  getTokenObject(tokenId: number) {2254    return new UniqueRFTToken(tokenId, this);2255  }22562257  async getTokensByAddress(addressObj: ICrossAccountId) {2258    return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2259  }22602261  async getTop10TokenOwners(tokenId: number) {2262    return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);2263  }22642265  async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {2266    return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);2267  }22682269  async getTokenTotalPieces(tokenId: number) {2270    return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2271  }22722273  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) {2274    return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2275  }22762277  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2278    return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);2279  }22802281  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2282    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount);2283  }22842285  async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2286    return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);2287  }22882289  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) {2290    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label);2291  }22922293  async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) {2294    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label);2295  }22962297  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) {2298    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label);2299  }23002301  async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) {2302    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount);2303  }23042305  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2306    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2307  }23082309  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2310    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2311  }23122313  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2314    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2315  }2316}231723182319class UniqueFTCollection extends UniqueCollectionBase {2320  async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) {2321    return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label);2322  }23232324  async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) {2325    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label);2326  }23272328  async getBalance(addressObj: ICrossAccountId) {2329    return await this.helper.ft.getBalance(this.collectionId, addressObj);2330  }23312332  async getTop10Owners() {2333    return await this.helper.ft.getTop10Owners(this.collectionId);2334  }23352336  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) {2337    return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);2338  }23392340  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {2341    return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);2342  }23432344  async burnTokens(signer: TSigner, amount: bigint, label?: string) {2345    return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label);2346  }23472348  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) {2349    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label);2350  }23512352  async getTotalPieces() {2353    return await this.helper.ft.getTotalPieces(this.collectionId);2354  }23552356  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2357    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label);2358  }23592360  async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2361    return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);2362  }2363}236423652366class UniqueTokenBase implements IToken {2367  collection: UniqueNFTCollection | UniqueRFTCollection;2368  collectionId: number;2369  tokenId: number;23702371  constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {2372    this.collection = collection;2373    this.collectionId = collection.collectionId;2374    this.tokenId = tokenId;2375  }23762377  async getNextSponsored(addressObj: ICrossAccountId) {2378    return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2379  }23802381  async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2382    return await this.collection.setTokenProperties(signer, this.tokenId, properties, label);2383  }23842385  async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2386    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label);2387  }2388}238923902391class UniqueNFTToken extends UniqueTokenBase {2392  collection: UniqueNFTCollection;23932394  constructor(tokenId: number, collection: UniqueNFTCollection) {2395    super(tokenId, collection);2396    this.collection = collection;2397  }23982399  async getData(blockHashAt?: string) {2400    return await this.collection.getToken(this.tokenId, blockHashAt);2401  }24022403  async getOwner(blockHashAt?: string) {2404    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);2405  }24062407  async getTopmostOwner(blockHashAt?: string) {2408    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);2409  }24102411  async getChildren(blockHashAt?: string) {2412    return await this.collection.getTokenChildren(this.tokenId, blockHashAt);2413  }24142415  async nest(signer: TSigner, toTokenObj: IToken, label?: string) {2416    return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label);2417  }24182419  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2420    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label);2421  }24222423  async transfer(signer: TSigner, addressObj: ICrossAccountId) {2424    return await this.collection.transferToken(signer, this.tokenId, addressObj);2425  }24262427  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2428    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);2429  }24302431  async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) {2432    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label);2433  }24342435  async isApproved(toAddressObj: ICrossAccountId) {2436    return await this.collection.isTokenApproved(this.tokenId, toAddressObj);2437  }24382439  async burn(signer: TSigner, label?: string) {2440    return await this.collection.burnToken(signer, this.tokenId, label);2441  }2442}24432444class UniqueRFTToken extends UniqueTokenBase {2445  collection: UniqueRFTCollection;24462447  constructor(tokenId: number, collection: UniqueRFTCollection) {2448    super(tokenId, collection);2449    this.collection = collection;2450  }24512452  async getTop10Owners() {2453    return await this.collection.getTop10TokenOwners(this.tokenId);2454  }24552456  async getBalance(addressObj: ICrossAccountId) {2457    return await this.collection.getTokenBalance(this.tokenId, addressObj);2458  }24592460  async getTotalPieces() {2461    return await this.collection.getTokenTotalPieces(this.tokenId);2462  }24632464  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) {2465    return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);2466  }24672468  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2469    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);2470  }24712472  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2473    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label);2474  }24752476  async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {2477    return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);2478  }24792480  async repartition(signer: TSigner, amount: bigint, label?: string) {2481    return await this.collection.repartitionToken(signer, this.tokenId, amount, label);2482  }24832484  async burn(signer: TSigner, amount=100n, label?: string) {2485    return await this.collection.burnToken(signer, this.tokenId, amount, label);2486  }2487}