git.delta.rocks / unique-network / refs/commits / 76975d1a4b93

difftreelog

source

tests/src/util/playgrounds/unique.ts96.1 KiBsourcehistory
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 bool true on success670   */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 bool true on success691   */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 bool true on success711   */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 bool true on success740   */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 bool true on success761   */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 bool true on success782   */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 bool true on success803   */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, TODO);823   * @returns bool true on success824   */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, TODO);844   * @returns bool true on success845   */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 bool true on success858   */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   * @example871   * setProperties(aliceKeyring, 10, TODO)872   * @returns bool true on success873   */874  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {875    if(typeof label === 'undefined') label = `collection #${collectionId}`;876    const result = await this.helper.executeExtrinsic(877      signer,878      'api.tx.unique.setCollectionProperties', [collectionId, properties],879      true, `Unable to set collection properties for ${label}`,880    );881882    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);883  }884885  /**886   * Deletes onchain properties from the collection.887   * 888   * @param signer keyring of signer889   * @param collectionId ID of collection890   * @param propertyKeys array of property keys to delete891   * @param label892   * @example deleteProperties(aliceKeyring, 10, TODO)893   * @returns 894   */895  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {896    if(typeof label === 'undefined') label = `collection #${collectionId}`;897    const result = await this.helper.executeExtrinsic(898      signer,899      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],900      true, `Unable to delete collection properties for ${label}`,901    );902903    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);904  }905906  /**907   * Changes the owner of the token.908   * 909   * @param signer keyring of signer910   * @param collectionId ID of collection911   * @param tokenId ID of token912   * @param addressObj address of a new owner913   * @param amount amount of tokens to be transfered. For NFT must be set to 1n914   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})915   * @returns true if the token success, otherwise false916   */917  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {918    const result = await this.helper.executeExtrinsic(919      signer,920      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],921      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,922    );923924    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);925  }926927  /**928   * 929   * Change ownership of a NFT on behalf of the owner. 930   * 931   * @param signer keyring of signer932   * @param collectionId ID of collection933   * @param tokenId ID of token934   * @param fromAddressObj address on behalf of which the token will be sent935   * @param toAddressObj new token owner936   * @param amount amount of tokens to be transfered. For NFT must be set to 1n937   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})938   * @returns true if the token success, otherwise false939   */940  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {941    const result = await this.helper.executeExtrinsic(942      signer,943      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],944      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,945    );946    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);947  }948949  /**950   * 951   * Destroys a concrete instance of NFT.952   * 953   * @param signer keyring of signer954   * @param collectionId ID of collection955   * @param tokenId ID of token956   * @param label 957   * @param amount amount of tokens to be burned. For NFT must be set to 1n958   * @example burnToken(aliceKeyring, 10, 5);959   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```960   */961  async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{962    success: boolean,963    token: number | null964  }> {965    if(typeof label === 'undefined') label = `collection #${collectionId}`;966    const burnResult = await this.helper.executeExtrinsic(967      signer,968      'api.tx.unique.burnItem', [collectionId, tokenId, amount],969      true, `Unable to burn token for ${label}`,970    );971    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);972    if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');973    return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};974  }975976  /**977   * Destroys a concrete instance of NFT on behalf of the owner978   * 979   * @param signer keyring of signer980   * @param collectionId ID of collection981   * @param fromAddressObj address on behalf of which the token will be burnt982   * @param tokenId ID of token983   * @param label 984   * @param amount amount of tokens to be burned. For NFT must be set to 1n985   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})986   * @returns ```true``` if extrinsic success. Otherwise ```false```987   */988  async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {989    if(typeof label === 'undefined') label = `collection #${collectionId}`;990    const burnResult = await this.helper.executeExtrinsic(991      signer,992      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],993      true, `Unable to burn token from for ${label}`,994    );995    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);996    return burnedTokens.success && burnedTokens.tokens.length > 0;997  }998999  /**1000   * Set, change, or remove approved address to transfer the ownership of the NFT.1001   * 1002   * @param signer keyring of signer1003   * @param collectionId ID of collection1004   * @param tokenId ID of token1005   * @param toAddressObj 1006   * @param label 1007   * @param amount amount of token to be approved. For NFT must be set to 1n1008   * @returns ```true``` if extrinsic success. Otherwise ```false```1009   */1010  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {1011    if(typeof label === 'undefined') label = `collection #${collectionId}`;1012    const approveResult = await this.helper.executeExtrinsic(1013      signer, 1014      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1015      true, `Unable to approve token for ${label}`,1016    );10171018    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);1019  }10201021  /**1022   * TODO1023   * @param collectionId ID of collection1024   * @param tokenId ID of token1025   * @param toAccountObj 1026   * @param fromAccountObj1027   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1028   * @returns number of approved to transfer pieces1029   */1030  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1031    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1032  }10331034  /**1035   * Get the last created token id1036   * @param collectionId ID of collection1037   * @example getLastTokenId(10);1038   * @returns id of the last created token1039   */1040  async getLastTokenId(collectionId: number): Promise<number> {1041    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1042  }10431044  /**1045   * Check if token exists1046   * @param collectionId ID of collection1047   * @param tokenId ID of token1048   * @example isTokenExists(10, 20);1049   * @returns true if the token exists, otherwise false1050   */1051  async isTokenExists(collectionId: number, tokenId: number): Promise<boolean> {1052    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1053  }1054}10551056class NFTnRFT extends CollectionGroup {1057  /**1058   * Get tokens owned by account1059   * 1060   * @param collectionId ID of collection1061   * @param addressObj tokens owner1062   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1063   * @returns array of token ids owned by account TODO for RFT?1064   */1065  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1066    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1067  }10681069  /**1070   * Get token data1071   * @param collectionId ID of collection1072   * @param tokenId ID of token1073   * @param blockHashAt 1074   * @param propertyKeys TODO1075   * @example getToken(10, 5);1076   * @returns human readable token data 1077   */1078  async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{1079    properties: IProperty[];1080    owner: ICrossAccountId;1081    normalizedOwner: ICrossAccountId;1082  }| null> {1083    let tokenData;1084    if(typeof blockHashAt === 'undefined') {1085      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1086    }1087    else {1088      if(typeof propertyKeys === 'undefined') {1089        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1090        if(!collection) return null;1091        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1092      }1093      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1094    }1095    tokenData = tokenData.toHuman();1096    if (tokenData === null || tokenData.owner === null) return null;1097    const owner = {} as any;1098    for (const key of Object.keys(tokenData.owner)) {1099      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];1100    }1101    tokenData.normalizedOwner = crossAccountIdFromLower(owner);1102    return tokenData;1103  }11041105  /**1106   * Set permissions to change token properties1107   * @param signer keyring of signer1108   * @param collectionId ID of collection1109   * @param permissions permissions to change a property by the collection owner or admin1110   * @param label 1111   * @example setTokenPropertyPermissions(1112   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1113   * )1114   * @returns true if extrinsic success otherwise false1115   */1116  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {1117    if(typeof label === 'undefined') label = `collection #${collectionId}`;1118    const result = await this.helper.executeExtrinsic(1119      signer,1120      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1121      true, `Unable to set token property permissions for ${label}`,1122    );11231124    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);1125  }11261127  /**1128   * Set token properties1129   * @param signer keyring of signer1130   * @param collectionId ID of collection1131   * @param tokenId ID of token1132   * @param properties 1133   * @param label 1134   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1135   * @returns true if extrinsic success, otherwise false1136   */1137  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {1138    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1139    const result = await this.helper.executeExtrinsic(1140      signer,1141      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1142      true, `Unable to set token properties for ${label}`,1143    );11441145    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);1146  }11471148  /**1149   * Delete the provided properties of a token1150   * @param signer keyring of signer1151   * @param collectionId ID of collection1152   * @param tokenId ID of token1153   * @param propertyKeys property keys to be deleted 1154   * @param label 1155   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1156   * @returns true if extrinsic success, otherwise false1157   */1158  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {1159    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1160    const result = await this.helper.executeExtrinsic(1161      signer,1162      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1163      true, `Unable to delete token properties for ${label}`,1164    );11651166    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);1167  }11681169  /**1170   * Mint new collection1171   * @param signer keyring of signer1172   * @param collectionOptions TODO1173   * @param mode NFT or RFT type of a collection1174   * @param errorLabel 1175   * @example mintCollection(aliceKeyring, TODO, "NFT")1176   * @returns object of the created collection1177   */1178  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {1179    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1180    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1181    for (const key of ['name', 'description', 'tokenPrefix']) {1182      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);1183    }1184    const creationResult = await this.helper.executeExtrinsic(1185      signer,1186      'api.tx.unique.createCollectionEx', [collectionOptions],1187      true, errorLabel,1188    );1189    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1190  }11911192  getCollectionObject(collectionId: number): any {1193    return null;1194  }11951196  getTokenObject(collectionId: number, tokenId: number): any {1197    return null;1198  }1199}120012011202class NFTGroup extends NFTnRFT {1203  /**1204   * Get collection object1205   * @param collectionId ID of collection1206   * @example getCollectionObject(2);1207   * @returns instance of UniqueNFTCollection1208   */1209  getCollectionObject(collectionId: number): UniqueNFTCollection {1210    return new UniqueNFTCollection(collectionId, this.helper);1211  }12121213  /**1214   * Get token object1215   * @param collectionId ID of collection1216   * @param tokenId ID of token1217   * @example getTokenObject(10, 5);1218   * @returns instance of UniqueNFTToken1219   */1220  getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {1221    return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));1222  }12231224  /**1225   * Get token's owner1226   * @param collectionId ID of collection1227   * @param tokenId ID of token1228   * @param blockHashAt 1229   * @example getTokenOwner(10, 5);1230   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1231   */1232  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {1233    let owner;1234    if (typeof blockHashAt === 'undefined') {1235      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1236    } else {1237      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1238    }1239    return crossAccountIdFromLower(owner.toJSON());1240  }12411242  /**1243   * Is token approved to transfer1244   * @param collectionId ID of collection1245   * @param tokenId ID of token1246   * @param toAccountObj TODO1247   * @returns TODO 1248   */1249  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1250    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1251  }12521253  /**1254   * Changes the owner of the token.1255   * 1256   * @param signer keyring of signer1257   * @param collectionId ID of collection1258   * @param tokenId ID of token1259   * @param addressObj address of a new owner1260   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1261   * @returns true if extrinsic success, otherwise false1262   */1263  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1264    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1265  }12661267  /**1268   * 1269   * Change ownership of a NFT on behalf of the owner. 1270   * 1271   * @param signer keyring of signer1272   * @param collectionId ID of collection1273   * @param tokenId ID of token1274   * @param fromAddressObj address on behalf of which the token will be sent1275   * @param toAddressObj new token owner1276   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1277   * @returns true if extrinsic success, otherwise false1278   */1279  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1280    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1281  }12821283  /**1284   * Recursively find the address that owns the token1285   * @param collectionId ID of collection1286   * @param tokenId ID of token1287   * @param blockHashAt 1288   * @example getTokenTopmostOwner(10, 5);1289   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1290   */1291  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {1292    let owner;1293    if (typeof blockHashAt === 'undefined') {1294      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1295    } else {1296      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1297    }12981299    if (owner === null) return null;13001301    owner = owner.toHuman();13021303    return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;1304  }13051306  /**1307   * Get tokens nested in the provided token1308   * @param collectionId ID of collection1309   * @param tokenId ID of token1310   * @param blockHashAt 1311   * @example getTokenChildren(10, 5);1312   * @returns tokens whose depth of nesting is <= 5 1313   */1314  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1315    let children;1316    if(typeof blockHashAt === 'undefined') {1317      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1318    } else {1319      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1320    }13211322    return children.toJSON().map((x: any) => {1323      return {collectionId: x.collection, tokenId: x.token};1324    });1325  }13261327  /**1328   * Nest one token into another1329   * @param signer keyring of signer1330   * @param tokenObj token to be nested1331   * @param rootTokenObj token to be parent1332   * @param label 1333   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1334   * @returns true if extrinsic success, otherwise false1335   */1336  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {1337    const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1338    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1339    if(!result) {1340      throw Error(`Unable to nest token for ${label}`);1341    }1342    return result;1343  }13441345  /**1346   * Remove token from nested state1347   * @param signer keyring of signer1348   * @param tokenObj token to unnest1349   * @param rootTokenObj parent of a token1350   * @param toAddressObj address of a new token owner 1351   * @param label 1352   * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1353   * @returns true if extrinsic success, otherwise false1354   */1355  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {1356    const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1357    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1358    if(!result) {1359      throw Error(`Unable to unnest token for ${label}`);1360    }1361    return result;1362  }13631364  /**1365   * Mint new collection1366   * @param signer keyring of signer1367   * @param collectionOptions Collection options1368   * @param label 1369   * @example 1370   * mintCollection(aliceKeyring, {1371   *   name: 'New',1372   *   description: 'New collection',1373   *   tokenPrefix: 'NEW',1374   * })1375   * @returns object of the created collection1376   */1377  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {1378    return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;1379  }13801381  /**1382   * Mint new token1383   * @param signer keyring of signer1384   * @param data token data1385   * @param label 1386   * @returns created token object1387   */1388  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {1389    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1390    const creationResult = await this.helper.executeExtrinsic(1391      signer,1392      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1393        nft: {1394          properties: data.properties,1395        },1396      }],1397      true, `Unable to mint NFT token for ${label}`,1398    );1399    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1400    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1401    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1402    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1403  }14041405  /**1406   * Mint multiple NFT tokens1407   * @param signer keyring of signer1408   * @param collectionId ID of collection1409   * @param tokens array of tokens with owner and properties1410   * @param label 1411   * @example 1412   * mintMultipleTokens(aliceKeyring, 10, [{1413   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1414   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1415   *   },{1416   *     owner: {Ethereum: "0x9F0583DbB855d..."},1417   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1418   * }]);1419   * @returns true if extrinsic success, otherwise false1420   */1421  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1422    if(typeof label === 'undefined') label = `collection #${collectionId}`;1423    const creationResult = await this.helper.executeExtrinsic(1424      signer,1425      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1426      true, `Unable to mint NFT tokens for ${label}`,1427    );1428    const collection = this.getCollectionObject(collectionId);1429    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1430  }14311432  /**1433   * Mint multiple NFT tokens with one owner1434   * @param signer keyring of signer1435   * @param collectionId ID of collection1436   * @param owner tokens owner1437   * @param tokens array of tokens with owner and properties1438   * @param label 1439   * @example1440   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1441   *   properties: [{1442   *   key: "gender",1443   *   value: "female",1444   *  },{1445   *   key: "age",1446   *   value: "33",1447   *  }],1448   * }]);1449   * @returns array of newly created tokens1450   */1451  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1452    if(typeof label === 'undefined') label = `collection #${collectionId}`;1453    const rawTokens = [];1454    for (const token of tokens) {1455      const raw = {NFT: {properties: token.properties}};1456      rawTokens.push(raw);1457    }1458    const creationResult = await this.helper.executeExtrinsic(1459      signer,1460      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1461      true, `Unable to mint NFT tokens for ${label}`,1462    );1463    const collection = this.getCollectionObject(collectionId);1464    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1465  }14661467  /**1468   * Destroys a concrete instance of NFT.1469   * @param signer keyring of signer1470   * @param collectionId ID of collection1471   * @param tokenId ID of token1472   * @param label 1473   * @example burnToken(aliceKeyring, 10, 5);1474   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1475   */1476  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {1477    return await super.burnToken(signer, collectionId, tokenId, label, 1n);1478  }14791480  /**1481   * Set, change, or remove approved address to transfer the ownership of the NFT.1482   * 1483   * @param signer keyring of signer1484   * @param collectionId ID of collection1485   * @param tokenId ID of token1486   * @param toAddressObj address to approve1487   * @param label 1488   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1489   * @returns ```true``` if extrinsic success. Otherwise ```false```1490   */1491  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {1492    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, 1n);1493  }1494}149514961497class RFTGroup extends NFTnRFT {1498  /**1499   * Get collection object1500   * @param collectionId ID of collection1501   * @example getCollectionObject(2);1502   * @returns instance of UniqueNFTCollection1503   */1504  getCollectionObject(collectionId: number): UniqueRFTCollection {1505    return new UniqueRFTCollection(collectionId, this.helper);1506  }15071508  /**1509   * Get token object1510   * @param collectionId ID of collection1511   * @param tokenId ID of token1512   * @example getTokenObject(10, 5);1513   * @returns instance of UniqueNFTToken1514   */1515  getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {1516    return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));1517  }15181519  /**1520   * Get top 10 token owners with the largest number of pieces 1521   * @param collectionId ID of collection1522   * @param tokenId ID of token1523   * @example getTokenTop10Owners(10, 5);1524   * @returns array of top 10 owners1525   */1526  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {1527    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);1528  }15291530  /**1531   * Get number of pieces owned by address1532   * @param collectionId ID of collection1533   * @param tokenId ID of token1534   * @param addressObj address token owner1535   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1536   * @returns number of pieces ownerd by address1537   */1538  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1539    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1540  }15411542  /**1543   * Transfer pieces of token to another address1544   * @param signer keyring of signer1545   * @param collectionId ID of collection1546   * @param tokenId ID of token1547   * @param addressObj address of a new owner1548   * @param amount number of pieces to be transfered1549   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1550   * @returns true if extrinsic success, otherwise false1551   */1552  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {1553    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1554  }15551556  /**1557   * Change ownership of some pieces of RFT on behalf of the owner. 1558   * @param signer keyring of signer1559   * @param collectionId ID of collection1560   * @param tokenId ID of token1561   * @param fromAddressObj address on behalf of which the token will be sent1562   * @param toAddressObj new token owner1563   * @param amount number of pieces to be transfered1564   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1565   * @returns true if extrinsic success, otherwise false1566   */1567  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {1568    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1569  }15701571  /**1572   * Mint new collection1573   * @param signer keyring of signer1574   * @param collectionOptions Collection options1575   * @param label 1576   * @example1577   * mintCollection(aliceKeyring, {1578   *   name: 'New',1579   *   description: 'New collection',1580   *   tokenPrefix: 'NEW',1581   * })1582   * @returns object of the created collection1583   */1584  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {1585    return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;1586  }15871588  /**1589   * Mint new token1590   * @param signer keyring of signer1591   * @param data token data1592   * @param label 1593   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1594   * @returns created token object1595   */1596  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {1597    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1598    const creationResult = await this.helper.executeExtrinsic(1599      signer,1600      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1601        refungible: {1602          pieces: data.pieces,1603          properties: data.properties,1604        },1605      }],1606      true, `Unable to mint RFT token for ${label}`,1607    );1608    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1609    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1610    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1611    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1612  }16131614  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1615    throw Error('Not implemented');1616    if(typeof label === 'undefined') label = `collection #${collectionId}`;1617    const creationResult = await this.helper.executeExtrinsic(1618      signer,1619      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1620      true, `Unable to mint RFT tokens for ${label}`,1621    );1622    const collection = this.getCollectionObject(collectionId);1623    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1624  }16251626  /**1627   * Mint multiple RFT tokens with one owner1628   * @param signer keyring of signer1629   * @param collectionId ID of collection1630   * @param owner tokens owner1631   * @param tokens array of tokens with properties and pieces1632   * @param label 1633   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1634   * @returns array of newly created RFT tokens1635   */1636  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1637    if(typeof label === 'undefined') label = `collection #${collectionId}`;1638    const rawTokens = [];1639    for (const token of tokens) {1640      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1641      rawTokens.push(raw);1642    }1643    const creationResult = await this.helper.executeExtrinsic(1644      signer,1645      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1646      true, `Unable to mint RFT tokens for ${label}`,1647    );1648    const collection = this.getCollectionObject(collectionId);1649    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1650  }16511652  /**1653   * Destroys a concrete instance of RFT.1654   * @param signer keyring of signer1655   * @param collectionId ID of collection1656   * @param tokenId ID of token1657   * @param label 1658   * @param amount number of pieces to be burnt1659   * @example burnToken(aliceKeyring, 10, 5);1660   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1661   */1662  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {1663    return await super.burnToken(signer, collectionId, tokenId, label, amount);1664  }16651666  /**1667   * Set, change, or remove approved address to transfer the ownership of the RFT.1668   * 1669   * @param signer keyring of signer1670   * @param collectionId ID of collection1671   * @param tokenId ID of token1672   * @param toAddressObj address to approve1673   * @param label 1674   * @param amount number of pieces to be approved1675   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1676   * @returns true if the token success, otherwise false1677   */1678  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {1679    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1680  }16811682  /**1683   * Get total number of pieces1684   * @param collectionId ID of collection1685   * @param tokenId ID of token1686   * @example getTokenTotalPieces(10, 5);1687   * @returns number of pieces1688   */1689  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1690    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1691  }16921693  /**1694   * Change number of token pieces. Signer must be the owner of all token pieces.1695   * @param signer keyring of signer1696   * @param collectionId ID of collection1697   * @param tokenId ID of token1698   * @param amount new number of pieces1699   * @param label 1700   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1701   * @returns true if the repartion was success, otherwise false1702   */1703  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {1704    if(typeof label === 'undefined') label = `collection #${collectionId}`;1705    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1706    const repartitionResult = await this.helper.executeExtrinsic(1707      signer,1708      'api.tx.unique.repartition', [collectionId, tokenId, amount],1709      true, `Unable to repartition RFT token for ${label}`,1710    );1711    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);1712    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);1713  }1714}171517161717class FTGroup extends CollectionGroup {1718  /**1719   * Get collection object1720   * @param collectionId ID of collection1721   * @example getCollectionObject(2);1722   * @returns instance of UniqueNFTCollection1723   */1724  getCollectionObject(collectionId: number): UniqueFTCollection {1725    return new UniqueFTCollection(collectionId, this.helper);1726  }17271728  /**1729   * Mint new fungible collection1730   * @param signer keyring of signer1731   * @param collectionOptions Collection options1732   * @param decimalPoints number of token decimals 1733   * @param errorLabel 1734   * @example1735   * mintCollection(aliceKeyring, {1736   *   name: 'New',1737   *   description: 'New collection',1738   *   tokenPrefix: 'NEW',1739   * }, 18)1740   * @returns newly created fungible collection1741   */1742  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {1743    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1744    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1745    collectionOptions.mode = {fungible: decimalPoints};1746    for (const key of ['name', 'description', 'tokenPrefix']) {1747      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);1748    }1749    const creationResult = await this.helper.executeExtrinsic(1750      signer,1751      'api.tx.unique.createCollectionEx', [collectionOptions],1752      true, errorLabel,1753    );1754    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1755  }17561757  /**1758   * Mint tokens1759   * @param signer keyring of signer1760   * @param collectionId ID of collection1761   * @param owner address owner of new tokens1762   * @param amount amount of tokens to be meanted1763   * @param label 1764   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1765   * @returns ```true``` if extrinsic success. Otherwise ```false``` 1766   */1767  async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {1768    if(typeof label === 'undefined') label = `collection #${collectionId}`;1769    const creationResult = await this.helper.executeExtrinsic(1770      signer,1771      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1772        fungible: {1773          value: amount,1774        },1775      }],1776      true, `Unable to mint fungible tokens for ${label}`,1777    );1778    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1779  }17801781  /**1782   * Mint multiple RFT tokens with one owner1783   * @param signer keyring of signer1784   * @param collectionId ID of collection1785   * @param owner tokens owner1786   * @param tokens array of tokens with properties and pieces1787   * @param label 1788   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1789   * @returns array of newly created RFT tokens1790   */17911792  /**1793   * Mint multiple Fungible tokens with one owner TODO For what??1794   * @param signer keyring of signer1795   * @param collectionId ID of collection1796   * @param owner tokens owner1797   * @param tokens array of tokens with properties and pieces1798   * @param label 1799   * @returns 1800   */1801  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {1802    if(typeof label === 'undefined') label = `collection #${collectionId}`;1803    const rawTokens = [];1804    for (const token of tokens) {1805      const raw = {Fungible: {Value: token.value}};1806      rawTokens.push(raw);1807    }1808    const creationResult = await this.helper.executeExtrinsic(1809      signer,1810      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1811      true, `Unable to mint RFT tokens for ${label}`,1812    );1813    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1814  }18151816  /**1817   * Get top 10 token owners1818   * @param collectionId ID of collection1819   * @example getTop10Owners(10);1820   * @returns array of ```ICrossAccountId```1821   */1822  async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {1823    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);1824  }18251826  /**1827   * Get account balance1828   * @param collectionId ID of collection1829   * @param addressObj address of owner1830   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})1831   * @returns amount of fungible tokens owned by address1832   */1833  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1834    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1835  }18361837  /**1838   * Transfer tokens to address1839   * @param signer keyring of signer1840   * @param collectionId ID of collection1841   * @param toAddressObj address recepient1842   * @param amount amount of tokens to be sent1843   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1844   * @returns ```true``` if extrinsic success. Otherwise ```false``` 1845   */1846  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {1847    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1848  }18491850  /**1851   * Transfer some tokens on behalf of the owner.1852   * @param signer keyring of signer1853   * @param collectionId ID of collection1854   * @param fromAddressObj address on behalf of which tokens will be sent1855   * @param toAddressObj address where token to be sent1856   * @param amount number of tokens to be sent1857   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);1858   * @returns ```true``` if extrinsic success. Otherwise ```false``` 1859   */1860  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {1861    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1862  }18631864  /**1865   * Destroy some amount of tokens1866   * @param signer keyring of signer1867   * @param collectionId ID of collection1868   * @param amount amount of tokens to be destroyed1869   * @param label 1870   * @example burnTokens(aliceKeyring, 10, 1000n);1871   * @returns ```true``` if extrinsic success. Otherwise ```false``` 1872   */1873  async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {1874    return (await super.burnToken(signer, collectionId, 0, label, amount)).success;1875  }18761877  /**1878   * Burn some tokens on behalf of the owner.1879   * @param signer keyring of signer1880   * @param collectionId ID of collection1881   * @param fromAddressObj address on behalf of which tokens will be burnt1882   * @param amount amount of tokens to be burnt1883   * @param label 1884   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1885   * @returns ```true``` if extrinsic success. Otherwise ```false``` 1886   */1887  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {1888    return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);1889  }18901891  /**1892   * TODO for what?1893   * @param collectionId 1894   * @returns 1895   */1896  async getTotalPieces(collectionId: number): Promise<bigint> {1897    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();1898  }18991900  /**1901   * Set, change, or remove approved address to transfer tokens.1902   * 1903   * @param signer keyring of signer1904   * @param collectionId ID of collection1905   * @param toAddressObj address to be approved1906   * @param amount amount of tokens to be approved1907   * @param label 1908   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)1909   * @returns ```true``` if extrinsic success. Otherwise ```false``` 1910   */1911  async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {1912    return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);1913  }19141915  /**1916   * TODO why pieces??1917   * @param collectionId 1918   * @param fromAddressObj 1919   * @param toAddressObj 1920   * @returns 1921   */1922  async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {1923    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);1924  }1925}192619271928class ChainGroup extends HelperGroup {1929  /**1930   * Get system properties of a chain1931   * @example getChainProperties();1932   * @returns ss58Format, token decimals, and token symbol1933   */1934  getChainProperties(): IChainProperties {1935    const properties = (this.helper.api as any).registry.getChainProperties().toJSON();1936    return {1937      ss58Format: properties.ss58Format.toJSON(),1938      tokenDecimals: properties.tokenDecimals.toJSON(),1939      tokenSymbol: properties.tokenSymbol.toJSON(),1940    };1941  }19421943  /**1944   * Get chain header1945   * @example getLatestBlockNumber();1946   * @returns the number of the last block1947   */1948  async getLatestBlockNumber(): Promise<number> {1949    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();1950  }19511952  /**1953   * Get block hash by block number1954   * @param blockNumber number of block1955   * @example getBlockHashByNumber(12345);1956   * @returns hash of a block1957   */1958  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {1959    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();1960    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;1961    return blockHash;1962  }19631964  /**1965   * Get account nonce1966   * @param address substrate address1967   * @example getNonce("5GrwvaEF5zXb26Fz...");1968   * @returns number, account's nonce1969   */1970  async getNonce(address: TSubstrateAccount): Promise<number> {1971    return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber();1972  }1973}197419751976class BalanceGroup extends HelperGroup {1977  getOneTokenNominal(): bigint {1978    const chainProperties = this.helper.chain.getChainProperties();1979    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);1980  }19811982  /**1983   * Get substrate address balance1984   * @param address substrate address1985   * @example getSubstrate("5GrwvaEF5zXb26Fz...")1986   * @returns amount of tokens on address1987   */1988  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {1989    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();1990  }19911992  /**1993   * Get ethereum address balance1994   * @param address ethereum address1995   * @example getEthereum("0x9F0583DbB855d...")1996   * @returns amount of tokens on address1997   */1998  async getEthereum(address: TEthereumAccount): Promise<bigint> {1999    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2000  }20012002  /**2003   * Transfer tokens to substrate address2004   * @param signer keyring of signer2005   * @param address substrate address of a recepient2006   * @param amount amount of tokens to be transfered2007   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2008   * @returns true if extrinsic success, otherwise false2009   */2010  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2011    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}`);20122013    let transfer = {from: null, to: null, amount: 0n} as any;2014    result.result.events.forEach(({event: {data, method, section}}) => {2015      if ((section === 'balances') && (method === 'Transfer')) {2016        transfer = {2017          from: this.helper.address.normalizeSubstrate(data[0]),2018          to: this.helper.address.normalizeSubstrate(data[1]),2019          amount: BigInt(data[2]),2020        };2021      }2022    });2023    let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;2024    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;2025    isSuccess = isSuccess && BigInt(amount) === transfer.amount;2026    return isSuccess;2027  }2028}202920302031class AddressGroup extends HelperGroup {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}