git.delta.rocks / unique-network / refs/commits / 8b0ce93028c8

difftreelog

source

tests/src/util/playgrounds/unique.ts135.5 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034/* eslint-disable @typescript-eslint/no-var-requires */5/* eslint-disable function-call-argument-newline */6/* eslint-disable no-prototype-builtins */78import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';11import {IKeyringPair} from '@polkadot/types/types';12import {hexToU8a} from '@polkadot/util/hex';13import {u8aConcat} from '@polkadot/util/u8a';14import {15  IApiListeners,16  IBlock,17  IEvent,18  IChainProperties,19  ICollectionCreationOptions,20  ICollectionLimits,21  ICollectionPermissions,22  ICrossAccountId,23  ICrossAccountIdLower,24  ILogger,25  INestingPermissions,26  IProperty,27  IStakingInfo,28  ISchedulerOptions,29  ISubstrateBalance,30  IToken,31  ITokenPropertyPermission,32  ITransactionResult,33  IUniqueHelperLog,34  TApiAllowedListeners,35  TEthereumAccount,36  TSigner,37  TSubstrateAccount,38  TNetworks,39  IForeignAssetMetadata,40  AcalaAssetMetadata,41  MoonbeamAssetInfo,42  DemocracyStandardAccountVote,43  IEthCrossAccountId,44} from './types';45import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';46import type {Vec} from '@polkadot/types-codec';47import {FrameSystemEventRecord} from '@polkadot/types/lookup';4849export class CrossAccountId implements ICrossAccountId {50  Substrate?: TSubstrateAccount;51  Ethereum?: TEthereumAccount;5253  constructor(account: ICrossAccountId) {54    if (account.Substrate) this.Substrate = account.Substrate;55    if (account.Ethereum) this.Ethereum = account.Ethereum;56  }5758  static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {59    switch (domain) {60      case 'Substrate': return new CrossAccountId({Substrate: account.address});61      case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();62    }63  }6465  static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {66    return new CrossAccountId({Substrate: address.substrate, Ethereum: address.ethereum});67  }6869  static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {70    return encodeAddress(decodeAddress(address), ss58Format);71  }7273  static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {74    return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});75  }7677  withNormalizedSubstrate(ss58Format = 42): CrossAccountId {78    if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);79    return this;80  }8182  static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {83    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));84  }8586  toEthereum(): CrossAccountId {87    if (this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});88    return this;89  }9091  static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {92    return evmToAddress(address, ss58Format);93  }9495  toSubstrate(ss58Format?: number): CrossAccountId {96    if (this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});97    return this;98  }99100  toLowerCase(): CrossAccountId {101    if (this.Substrate) this.Substrate = this.Substrate.toLowerCase();102    if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();103    return this;104  }105}106107const nesting = {108  toChecksumAddress(address: string): string {109    if (typeof address === 'undefined') return '';110111    if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);112113    address = address.toLowerCase().replace(/^0x/i,'');114    const addressHash = keccakAsHex(address).replace(/^0x/i,'');115    const checksumAddress = ['0x'];116117    for (let i = 0; i < address.length; i++) {118      // If ith character is 8 to f then make it uppercase119      if (parseInt(addressHash[i], 16) > 7) {120        checksumAddress.push(address[i].toUpperCase());121      } else {122        checksumAddress.push(address[i]);123      }124    }125    return checksumAddress.join('');126  },127  tokenIdToAddress(collectionId: number, tokenId: number) {128    return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8, '0')}${tokenId.toString(16).padStart(8, '0')}`);129  },130};131132class UniqueUtil {133  static transactionStatus = {134    NOT_READY: 'NotReady',135    FAIL: 'Fail',136    SUCCESS: 'Success',137  };138139  static chainLogType = {140    EXTRINSIC: 'extrinsic',141    RPC: 'rpc',142  };143144  static getTokenAccount(token: IToken): CrossAccountId {145    return new CrossAccountId({Ethereum: this.getTokenAddress(token)});146  }147148  static getTokenAddress(token: IToken): string {149    return nesting.tokenIdToAddress(token.collectionId, token.tokenId);150  }151152  static getDefaultLogger(): ILogger {153    return {154      log(msg: any, level = 'INFO') {155        console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));156      },157      level: {158        ERROR: 'ERROR',159        WARNING: 'WARNING',160        INFO: 'INFO',161      },162    };163  }164165  static vec2str(arr: string[] | number[]) {166    return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');167  }168169  static str2vec(string: string) {170    if (typeof string !== 'string') return string;171    return Array.from(string).map(x => x.charCodeAt(0));172  }173174  static fromSeed(seed: string, ss58Format = 42) {175    const keyring = new Keyring({type: 'sr25519', ss58Format});176    return keyring.addFromUri(seed);177  }178179  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {180    if (creationResult.status !== this.transactionStatus.SUCCESS) {181      throw Error('Unable to create collection!');182    }183184    let collectionId = null;185    creationResult.result.events.forEach(({event: {data, method, section}}) => {186      if ((section === 'common') && (method === 'CollectionCreated')) {187        collectionId = parseInt(data[0].toString(), 10);188      }189    });190191    if (collectionId === null) {192      throw Error('No CollectionCreated event was found!');193    }194195    return collectionId;196  }197198  static extractTokensFromCreationResult(creationResult: ITransactionResult): {199    success: boolean,200    tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],201  } {202    if (creationResult.status !== this.transactionStatus.SUCCESS) {203      throw Error('Unable to create tokens!');204    }205    let success = false;206    const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];207    creationResult.result.events.forEach(({event: {data, method, section}}) => {208      if (method === 'ExtrinsicSuccess') {209        success = true;210      } else if ((section === 'common') && (method === 'ItemCreated')) {211        tokens.push({212          collectionId: parseInt(data[0].toString(), 10),213          tokenId: parseInt(data[1].toString(), 10),214          owner: data[2].toHuman(),215          amount: data[3].toBigInt(),216        });217      }218    });219    return {success, tokens};220  }221222  static extractTokensFromBurnResult(burnResult: ITransactionResult): {223    success: boolean,224    tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],225  } {226    if (burnResult.status !== this.transactionStatus.SUCCESS) {227      throw Error('Unable to burn tokens!');228    }229    let success = false;230    const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];231    burnResult.result.events.forEach(({event: {data, method, section}}) => {232      if (method === 'ExtrinsicSuccess') {233        success = true;234      } else if ((section === 'common') && (method === 'ItemDestroyed')) {235        tokens.push({236          collectionId: parseInt(data[0].toString(), 10),237          tokenId: parseInt(data[1].toString(), 10),238          owner: data[2].toHuman(),239          amount: data[3].toBigInt(),240        });241      }242    });243    return {success, tokens};244  }245246  static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {247    let eventId = null;248    events.forEach(({event: {data, method, section}}) => {249      if ((section === expectedSection) && (method === expectedMethod)) {250        eventId = parseInt(data[0].toString(), 10);251      }252    });253254    if (eventId === null) {255      throw Error(`No ${expectedMethod} event was found!`);256    }257    return eventId === collectionId;258  }259260  static isTokenTransferSuccess(events: {event: IEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {261    const normalizeAddress = (address: string | ICrossAccountId) => {262      if(typeof address === 'string') return address;263      const obj = {} as any;264      Object.keys(address).forEach(k => {265        obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];266      });267      if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);268      if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();269      return address;270    };271    let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;272    events.forEach(({event: {data, method, section}}) => {273      if ((section === 'common') && (method === 'Transfer')) {274        const hData = (data as any).toJSON();275        transfer = {276          collectionId: hData[0],277          tokenId: hData[1],278          from: normalizeAddress(hData[2]),279          to: normalizeAddress(hData[3]),280          amount: BigInt(hData[4]),281        };282      }283    });284    let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;285    isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);286    isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);287    isSuccess = isSuccess && amount === transfer.amount;288    return isSuccess;289  }290291  static bigIntToDecimals(number: bigint, decimals = 18) {292    const numberStr = number.toString();293    const dotPos = numberStr.length - decimals;294295    if (dotPos <= 0) {296      return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;297    } else {298      const intPart = numberStr.substring(0, dotPos);299      const fractPart = numberStr.substring(dotPos);300      return intPart + '.' + fractPart;301    }302  }303}304305class UniqueEventHelper {306  private static extractIndex(index: any): [number, number] | string {307    if(index.toRawType() === '[u8;2]') return [index[0], index[1]];308    return index.toJSON();309  }310311  private static extractSub(data: any, subTypes: any): {[key: string]: any} {312    let obj: any = {};313    let index = 0;314315    if (data.entries) {316      for(const [key, value] of data.entries()) {317        obj[key] = this.extractData(value, subTypes[index]);318        index++;319      }320    } else obj = data.toJSON();321322    return obj;323  }324325  private static extractData(data: any, type: any): any {326    if(!type) return data.toHuman();327    if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();328    if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();329    if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);330    return data.toHuman();331  }332333  public static extractEvents(events: {event: any, phase: any}[]): IEvent[] {334    const parsedEvents: IEvent[] = [];335336    events.forEach((record) => {337      const {event, phase} = record;338      const types = event.typeDef;339340      const eventData: IEvent = {341        section: event.section.toString(),342        method: event.method.toString(),343        index: this.extractIndex(event.index),344        data: [],345        phase: phase.toJSON(),346      };347348      event.data.forEach((val: any, index: number) => {349        eventData.data.push(this.extractData(val, types[index]));350      });351352      parsedEvents.push(eventData);353    });354355    return parsedEvents;356  }357}358359export class ChainHelperBase {360  helperBase: any;361362  transactionStatus = UniqueUtil.transactionStatus;363  chainLogType = UniqueUtil.chainLogType;364  util: typeof UniqueUtil;365  eventHelper: typeof UniqueEventHelper;366  logger: ILogger;367  api: ApiPromise | null;368  forcedNetwork: TNetworks | null;369  network: TNetworks | null;370  chainLog: IUniqueHelperLog[];371  children: ChainHelperBase[];372  address: AddressGroup;373  chain: ChainGroup;374375  constructor(logger?: ILogger, helperBase?: any) {376    this.helperBase = helperBase;377378    this.util = UniqueUtil;379    this.eventHelper = UniqueEventHelper;380    if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();381    this.logger = logger;382    this.api = null;383    this.forcedNetwork = null;384    this.network = null;385    this.chainLog = [];386    this.children = [];387    this.address = new AddressGroup(this);388    this.chain = new ChainGroup(this);389  }390391  clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {392    Object.setPrototypeOf(helperCls.prototype, this);393    const newHelper = new helperCls(this.logger, options);394395    newHelper.api = this.api;396    newHelper.network = this.network;397    newHelper.forceNetwork = this.forceNetwork;398399    this.children.push(newHelper);400401    return newHelper;402  }403404  getApi(): ApiPromise {405    if(this.api === null) throw Error('API not initialized');406    return this.api;407  }408409  async subscribeEvents(expectedEvents: {section: string, names: string[]}[]) {410    const collectedEvents: IEvent[] = [];411    const unsubscribe = await this.getApi().query.system.events((events: Vec<FrameSystemEventRecord>) => {412      const ievents = this.eventHelper.extractEvents(events);413      ievents.forEach((event) => {414        expectedEvents.forEach((e => {415          if (event.section === e.section && e.names.includes(event.method)) {416            collectedEvents.push(event);417          }418        }));419      });420    });421    return {unsubscribe: unsubscribe as any, collectedEvents};422  }423424  clearChainLog(): void {425    this.chainLog = [];426  }427428  forceNetwork(value: TNetworks): void {429    this.forcedNetwork = value;430  }431432  async connect(wsEndpoint: string, listeners?: IApiListeners) {433    if (this.api !== null) throw Error('Already connected');434    const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);435    this.api = api;436    this.network = network;437  }438439  async disconnect() {440    for (const child of this.children) {441      child.clearApi();442    }443444    if (this.api === null) return;445    await this.api.disconnect();446    this.clearApi();447  }448449  clearApi() {450    this.api = null;451    this.network = null;452  }453454  static async detectNetwork(api: ApiPromise): Promise<TNetworks> {455    const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;456    const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];457458    if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;459460    if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;461    return 'opal';462  }463464  static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {465    const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});466    await api.isReady;467468    const network = await this.detectNetwork(api);469470    await api.disconnect();471472    return network;473  }474475  static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{476    api: ApiPromise;477    network: TNetworks;478  }> {479    if(typeof network === 'undefined' || network === null) network = 'opal';480    const supportedRPC = {481      opal: {482        unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,483      },484      quartz: {485        unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,486      },487      unique: {488        unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,489      },490      rococo: {},491      westend: {},492      moonbeam: {},493      moonriver: {},494      acala: {},495      karura: {},496      westmint: {},497    };498    if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);499    const rpc = supportedRPC[network];500501    // TODO: investigate how to replace rpc in runtime502    // api._rpcCore.addUserInterfaces(rpc);503504    const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});505506    await api.isReadyOrError;507508    if (typeof listeners === 'undefined') listeners = {};509    for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {510      if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;511      api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);512    }513514    return {api, network};515  }516517  getTransactionStatus(data: {events: {event: IEvent}[], status: any}) {518    const {events, status} = data;519    if (status.isReady) {520      return this.transactionStatus.NOT_READY;521    }522    if (status.isBroadcast) {523      return this.transactionStatus.NOT_READY;524    }525    if (status.isInBlock || status.isFinalized) {526      const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');527      if (errors.length > 0) {528        return this.transactionStatus.FAIL;529      }530      if (events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {531        return this.transactionStatus.SUCCESS;532      }533    }534535    return this.transactionStatus.FAIL;536  }537538  signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {539    const sign = (callback: any) => {540      if(options !== null) return transaction.signAndSend(sender, options, callback);541      return transaction.signAndSend(sender, callback);542    };543    // eslint-disable-next-line no-async-promise-executor544    return new Promise(async (resolve, reject) => {545      try {546        const unsub = await sign((result: any) => {547          const status = this.getTransactionStatus(result);548549          if (status === this.transactionStatus.SUCCESS) {550            this.logger.log(`${label} successful`);551            unsub();552            resolve({result, status});553          } else if (status === this.transactionStatus.FAIL) {554            let moduleError = null;555556            if (result.hasOwnProperty('dispatchError')) {557              const dispatchError = result['dispatchError'];558559              if (dispatchError) {560                if (dispatchError.isModule) {561                  const modErr = dispatchError.asModule;562                  const errorMeta = dispatchError.registry.findMetaError(modErr);563564                  moduleError = `${errorMeta.section}.${errorMeta.name}`;565                } else {566                  moduleError = dispatchError.toHuman();567                }568              } else {569                this.logger.log(result, this.logger.level.ERROR);570              }571            }572573            this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);574            unsub();575            reject({status, moduleError, result});576          }577        });578      } catch (e) {579        this.logger.log(e, this.logger.level.ERROR);580        reject(e);581      }582    });583  }584585  async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {586    const api = this.getApi();587    const signingInfo = await api.derive.tx.signingInfo(signer.address);588589    // We need to sign the tx because590    // unsigned transactions does not have an inclusion fee591    tx.sign(signer, {592      blockHash: api.genesisHash,593      genesisHash: api.genesisHash,594      runtimeVersion: api.runtimeVersion,595      nonce: signingInfo.nonce,596    });597598    if (len === null) {599      return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;600    } else {601      return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;602    }603  }604605  constructApiCall(apiCall: string, params: any[]) {606    if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);607    let call = this.getApi() as any;608    for(const part of apiCall.slice(4).split('.')) {609      call = call[part];610    }611    return call(...params);612  }613614  async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {615    if(this.api === null) throw Error('API not initialized');616    if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);617618    const startTime = (new Date()).getTime();619    let result: ITransactionResult;620    let events: IEvent[] = [];621    try {622      result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;623      events = this.eventHelper.extractEvents(result.result.events);624    }625    catch(e) {626      if(!(e as object).hasOwnProperty('status')) throw e;627      result = e as ITransactionResult;628    }629630    const endTime = (new Date()).getTime();631632    const log = {633      executedAt: endTime,634      executionTime: endTime - startTime,635      type: this.chainLogType.EXTRINSIC,636      status: result.status,637      call: extrinsic,638      signer: this.getSignerAddress(sender),639      params,640    } as IUniqueHelperLog;641642    if(result.status !== this.transactionStatus.SUCCESS) {643      if (result.moduleError) log.moduleError = result.moduleError;644      else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;645    }646    if(events.length > 0) log.events = events;647648    this.chainLog.push(log);649650    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {651      if (result.moduleError) throw Error(`${result.moduleError}`);652      else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));653    }654    return result;655  }656657  async callRpc(rpc: string, params?: any[]) {658    if(typeof params === 'undefined') params = [];659    if(this.api === null) throw Error('API not initialized');660    if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);661662    const startTime = (new Date()).getTime();663    let result;664    let error = null;665    const log = {666      type: this.chainLogType.RPC,667      call: rpc,668      params,669    } as IUniqueHelperLog;670671    try {672      result = await this.constructApiCall(rpc, params);673    }674    catch(e) {675      error = e;676    }677678    const endTime = (new Date()).getTime();679680    log.executedAt = endTime;681    log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';682    log.executionTime = endTime - startTime;683684    this.chainLog.push(log);685686    if(error !== null) throw error;687688    return result;689  }690691  getSignerAddress(signer: IKeyringPair | string): string {692    if(typeof signer === 'string') return signer;693    return signer.address;694  }695696  fetchAllPalletNames(): string[] {697    if(this.api === null) throw Error('API not initialized');698    return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());699  }700701  fetchMissingPalletNames(requiredPallets: string[]): string[] {702    const palletNames = this.fetchAllPalletNames();703    return requiredPallets.filter(p => !palletNames.includes(p));704  }705}706707708class HelperGroup<T extends ChainHelperBase> {709  helper: T;710711  constructor(uniqueHelper: T) {712    this.helper = uniqueHelper;713  }714}715716717class CollectionGroup extends HelperGroup<UniqueHelper> {718  /**719 * Get number of blocks when sponsored transaction is available.720 *721 * @param collectionId ID of collection722 * @param tokenId ID of token723 * @param addressObj address for which the sponsorship is checked724 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});725 * @returns number of blocks or null if sponsorship hasn't been set726 */727  async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {728    return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();729  }730731  /**732   * Get the number of created collections.733   *734   * @returns number of created collections735   */736  async getTotalCount(): Promise<number> {737    return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();738  }739740  /**741   * Get information about the collection with additional data,742   * including the number of tokens it contains, its administrators,743   * the normalized address of the collection's owner, and decoded name and description.744   *745   * @param collectionId ID of collection746   * @example await getData(2)747   * @returns collection information object748   */749  async getData(collectionId: number): Promise<{750    id: number;751    name: string;752    description: string;753    tokensCount: number;754    admins: CrossAccountId[];755    normalizedOwner: TSubstrateAccount;756    raw: any757  } | null> {758    const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);759    const humanCollection = collection.toHuman(), collectionData = {760      id: collectionId, name: null, description: null, tokensCount: 0, admins: [],761      raw: humanCollection,762    } as any, jsonCollection = collection.toJSON();763    if (humanCollection === null) return null;764    collectionData.raw.limits = jsonCollection.limits;765    collectionData.raw.permissions = jsonCollection.permissions;766    collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);767    for (const key of ['name', 'description']) {768      collectionData[key] = this.helper.util.vec2str(humanCollection[key]);769    }770771    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))772      ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)773      : 0;774    collectionData.admins = await this.getAdmins(collectionId);775776    return collectionData;777  }778779  /**780   * Get the addresses of the collection's administrators, optionally normalized.781   *782   * @param collectionId ID of collection783   * @param normalize whether to normalize the addresses to the default ss58 format784   * @example await getAdmins(1)785   * @returns array of administrators786   */787  async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {788    const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();789790    return normalize791      ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())792      : admins;793  }794795  /**796   * Get the addresses added to the collection allow-list, optionally normalized.797   * @param collectionId ID of collection798   * @param normalize whether to normalize the addresses to the default ss58 format799   * @example await getAllowList(1)800   * @returns array of allow-listed addresses801   */802  async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {803    const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();804    return normalize805      ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())806      : allowListed;807  }808809  /**810   * Get the effective limits of the collection instead of null for default values811   *812   * @param collectionId ID of collection813   * @example await getEffectiveLimits(2)814   * @returns object of collection limits815   */816  async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {817    return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();818  }819820  /**821   * Burns the collection if the signer has sufficient permissions and collection is empty.822   *823   * @param signer keyring of signer824   * @param collectionId ID of collection825   * @example await helper.collection.burn(aliceKeyring, 3);826   * @returns ```true``` if extrinsic success, otherwise ```false```827   */828  async burn(signer: TSigner, collectionId: number): Promise<boolean> {829    const result = await this.helper.executeExtrinsic(830      signer,831      'api.tx.unique.destroyCollection', [collectionId],832      true,833    );834835    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');836  }837838  /**839   * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.840   *841   * @param signer keyring of signer842   * @param collectionId ID of collection843   * @param sponsorAddress Sponsor substrate address844   * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")845   * @returns ```true``` if extrinsic success, otherwise ```false```846   */847  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {848    const result = await this.helper.executeExtrinsic(849      signer,850      'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],851      true,852    );853854    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorSet');855  }856857  /**858   * Confirms consent to sponsor the collection on behalf of the signer.859   *860   * @param signer keyring of signer861   * @param collectionId ID of collection862   * @example confirmSponsorship(aliceKeyring, 10)863   * @returns ```true``` if extrinsic success, otherwise ```false```864   */865  async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {866    const result = await this.helper.executeExtrinsic(867      signer,868      'api.tx.unique.confirmSponsorship', [collectionId],869      true,870    );871872    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'SponsorshipConfirmed');873  }874875  /**876   * Removes the sponsor of a collection, regardless if it consented or not.877   *878   * @param signer keyring of signer879   * @param collectionId ID of collection880   * @example removeSponsor(aliceKeyring, 10)881   * @returns ```true``` if extrinsic success, otherwise ```false```882   */883  async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {884    const result = await this.helper.executeExtrinsic(885      signer,886      'api.tx.unique.removeCollectionSponsor', [collectionId],887      true,888    );889890    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorRemoved');891  }892893  /**894   * Sets the limits of the collection. At least one limit must be specified for a correct call.895   *896   * @param signer keyring of signer897   * @param collectionId ID of collection898   * @param limits collection limits object899   * @example900   * await setLimits(901   *   aliceKeyring,902   *   10,903   *   {904   *     sponsorTransferTimeout: 0,905   *     ownerCanDestroy: false906   *   }907   * )908   * @returns ```true``` if extrinsic success, otherwise ```false```909   */910  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {911    const result = await this.helper.executeExtrinsic(912      signer,913      'api.tx.unique.setCollectionLimits', [collectionId, limits],914      true,915    );916917    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionLimitSet');918  }919920  /**921   * Changes the owner of the collection to the new Substrate address.922   *923   * @param signer keyring of signer924   * @param collectionId ID of collection925   * @param ownerAddress substrate address of new owner926   * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")927   * @returns ```true``` if extrinsic success, otherwise ```false```928   */929  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {930    const result = await this.helper.executeExtrinsic(931      signer,932      'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],933      true,934    );935936    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionOwnerChanged');937  }938939  /**940   * Adds a collection administrator.941   *942   * @param signer keyring of signer943   * @param collectionId ID of collection944   * @param adminAddressObj Administrator address (substrate or ethereum)945   * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})946   * @returns ```true``` if extrinsic success, otherwise ```false```947   */948  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {949    const result = await this.helper.executeExtrinsic(950      signer,951      'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],952      true,953    );954955    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminAdded');956  }957958  /**959   * Removes a collection administrator.960   *961   * @param signer keyring of signer962   * @param collectionId ID of collection963   * @param adminAddressObj Administrator address (substrate or ethereum)964   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})965   * @returns ```true``` if extrinsic success, otherwise ```false```966   */967  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {968    const result = await this.helper.executeExtrinsic(969      signer,970      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],971      true,972    );973974    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminRemoved');975  }976977  /**978   * Check if user is in allow list.979   *980   * @param collectionId ID of collection981   * @param user Account to check982   * @example await getAdmins(1)983   * @returns is user in allow list984   */985  async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {986    return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();987  }988989  /**990   * Adds an address to allow list991   * @param signer keyring of signer992   * @param collectionId ID of collection993   * @param addressObj address to add to the allow list994   * @returns ```true``` if extrinsic success, otherwise ```false```995   */996  async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {997    const result = await this.helper.executeExtrinsic(998      signer,999      'api.tx.unique.addToAllowList', [collectionId, addressObj],1000      true,1001    );10021003    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressAdded');1004  }10051006  /**1007   * Removes an address from allow list1008   *1009   * @param signer keyring of signer1010   * @param collectionId ID of collection1011   * @param addressObj address to remove from the allow list1012   * @returns ```true``` if extrinsic success, otherwise ```false```1013   */1014  async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1015    const result = await this.helper.executeExtrinsic(1016      signer,1017      'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1018      true,1019    );10201021    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressRemoved');1022  }10231024  /**1025   * Sets onchain permissions for selected collection.1026   *1027   * @param signer keyring of signer1028   * @param collectionId ID of collection1029   * @param permissions collection permissions object1030   * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1031   * @returns ```true``` if extrinsic success, otherwise ```false```1032   */1033  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1034    const result = await this.helper.executeExtrinsic(1035      signer,1036      'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1037      true,1038    );10391040    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPermissionSet');1041  }10421043  /**1044   * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1045   *1046   * @param signer keyring of signer1047   * @param collectionId ID of collection1048   * @param permissions nesting permissions object1049   * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1050   * @returns ```true``` if extrinsic success, otherwise ```false```1051   */1052  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1053    return await this.setPermissions(signer, collectionId, {nesting: permissions});1054  }10551056  /**1057   * Disables nesting for selected collection.1058   *1059   * @param signer keyring of signer1060   * @param collectionId ID of collection1061   * @example disableNesting(aliceKeyring, 10);1062   * @returns ```true``` if extrinsic success, otherwise ```false```1063   */1064  async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1065    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1066  }10671068  /**1069   * Sets onchain properties to the collection.1070   *1071   * @param signer keyring of signer1072   * @param collectionId ID of collection1073   * @param properties array of property objects1074   * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1075   * @returns ```true``` if extrinsic success, otherwise ```false```1076   */1077  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1078    const result = await this.helper.executeExtrinsic(1079      signer,1080      'api.tx.unique.setCollectionProperties', [collectionId, properties],1081      true,1082    );10831084    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1085  }10861087  /**1088   * Get collection properties.1089   *1090   * @param collectionId ID of collection1091   * @param propertyKeys optionally filter the returned properties to only these keys1092   * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1093   * @returns array of key-value pairs1094   */1095  async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1096    return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1097  }10981099  async getCollectionOptions(collectionId: number) {1100    return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1101  }11021103  /**1104   * Deletes onchain properties from the collection.1105   *1106   * @param signer keyring of signer1107   * @param collectionId ID of collection1108   * @param propertyKeys array of property keys to delete1109   * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1110   * @returns ```true``` if extrinsic success, otherwise ```false```1111   */1112  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1113    const result = await this.helper.executeExtrinsic(1114      signer,1115      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1116      true,1117    );11181119    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1120  }11211122  /**1123   * Changes the owner of the token.1124   *1125   * @param signer keyring of signer1126   * @param collectionId ID of collection1127   * @param tokenId ID of token1128   * @param addressObj address of a new owner1129   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1130   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1131   * @returns true if the token success, otherwise false1132   */1133  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1134    const result = await this.helper.executeExtrinsic(1135      signer,1136      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1137      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1138    );11391140    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1141  }11421143  /**1144   *1145   * Change ownership of a token(s) on behalf of the owner.1146   *1147   * @param signer keyring of signer1148   * @param collectionId ID of collection1149   * @param tokenId ID of token1150   * @param fromAddressObj address on behalf of which the token will be sent1151   * @param toAddressObj new token owner1152   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1153   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1154   * @returns true if the token success, otherwise false1155   */1156  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1157    const result = await this.helper.executeExtrinsic(1158      signer,1159      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1160      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1161    );1162    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1163  }11641165  /**1166   *1167   * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1168   *1169   * @param signer keyring of signer1170   * @param collectionId ID of collection1171   * @param tokenId ID of token1172   * @param amount amount of tokens to be burned. For NFT must be set to 1n1173   * @example burnToken(aliceKeyring, 10, 5);1174   * @returns ```true``` if the extrinsic is successful, otherwise ```false```1175   */1176  async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1177    const burnResult = await this.helper.executeExtrinsic(1178      signer,1179      'api.tx.unique.burnItem', [collectionId, tokenId, amount],1180      true, // `Unable to burn token for ${label}`,1181    );1182    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1183    if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1184    return burnedTokens.success;1185  }11861187  /**1188   * Destroys a concrete instance of NFT on behalf of the owner1189   *1190   * @param signer keyring of signer1191   * @param collectionId ID of collection1192   * @param tokenId ID of token1193   * @param fromAddressObj address on behalf of which the token will be burnt1194   * @param amount amount of tokens to be burned. For NFT must be set to 1n1195   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1196   * @returns ```true``` if extrinsic success, otherwise ```false```1197   */1198  async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1199    const burnResult = await this.helper.executeExtrinsic(1200      signer,1201      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1202      true, // `Unable to burn token from for ${label}`,1203    );1204    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1205    return burnedTokens.success && burnedTokens.tokens.length > 0;1206  }12071208  /**1209   * Set, change, or remove approved address to transfer the ownership of the NFT.1210   *1211   * @param signer keyring of signer1212   * @param collectionId ID of collection1213   * @param tokenId ID of token1214   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1215   * @param amount amount of token to be approved. For NFT must be set to 1n1216   * @returns ```true``` if extrinsic success, otherwise ```false```1217   */1218  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1219    const approveResult = await this.helper.executeExtrinsic(1220      signer,1221      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1222      true, // `Unable to approve token for ${label}`,1223    );12241225    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1226  }12271228  /**1229   * Get the amount of token pieces approved to transfer or burn. Normally 0.1230   *1231   * @param collectionId ID of collection1232   * @param tokenId ID of token1233   * @param toAccountObj address which is approved to use token pieces1234   * @param fromAccountObj address which may have allowed the use of its owned tokens1235   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1236   * @returns number of approved to transfer pieces1237   */1238  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1239    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1240  }12411242  /**1243   * Get the last created token ID in a collection1244   *1245   * @param collectionId ID of collection1246   * @example getLastTokenId(10);1247   * @returns id of the last created token1248   */1249  async getLastTokenId(collectionId: number): Promise<number> {1250    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1251  }12521253  /**1254   * Check if token exists1255   *1256   * @param collectionId ID of collection1257   * @param tokenId ID of token1258   * @example doesTokenExist(10, 20);1259   * @returns true if the token exists, otherwise false1260   */1261  async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1262    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1263  }1264}12651266class NFTnRFT extends CollectionGroup {1267  /**1268   * Get tokens owned by account1269   *1270   * @param collectionId ID of collection1271   * @param addressObj tokens owner1272   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1273   * @returns array of token ids owned by account1274   */1275  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1276    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1277  }12781279  /**1280   * Get token data1281   *1282   * @param collectionId ID of collection1283   * @param tokenId ID of token1284   * @param propertyKeys optionally filter the token properties to only these keys1285   * @param blockHashAt optionally query the data at some block with this hash1286   * @example getToken(10, 5);1287   * @returns human readable token data1288   */1289  async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1290    properties: IProperty[];1291    owner: CrossAccountId;1292    normalizedOwner: CrossAccountId;1293  }| null> {1294    let tokenData;1295    if(typeof blockHashAt === 'undefined') {1296      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1297    }1298    else {1299      if(propertyKeys.length == 0) {1300        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1301        if(!collection) return null;1302        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1303      }1304      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1305    }1306    tokenData = tokenData.toHuman();1307    if (tokenData === null || tokenData.owner === null) return null;1308    const owner = {} as any;1309    for (const key of Object.keys(tokenData.owner)) {1310      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1311        ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1312        : tokenData.owner[key];1313    }1314    tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1315    return tokenData;1316  }13171318  /**1319   * Set permissions to change token properties1320   *1321   * @param signer keyring of signer1322   * @param collectionId ID of collection1323   * @param permissions permissions to change a property by the collection admin or token owner1324   * @example setTokenPropertyPermissions(1325   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1326   * )1327   * @returns true if extrinsic success otherwise false1328   */1329  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1330    const result = await this.helper.executeExtrinsic(1331      signer,1332      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1333      true,1334    );13351336    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1337  }13381339  /**1340   * Get token property permissions.1341   *1342   * @param collectionId ID of collection1343   * @param propertyKeys optionally filter the returned property permissions to only these keys1344   * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1345   * @returns array of key-permission pairs1346   */1347  async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1348    return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1349  }13501351  /**1352   * Set token properties1353   *1354   * @param signer keyring of signer1355   * @param collectionId ID of collection1356   * @param tokenId ID of token1357   * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1358   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1359   * @returns ```true``` if extrinsic success, otherwise ```false```1360   */1361  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1362    const result = await this.helper.executeExtrinsic(1363      signer,1364      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1365      true,1366    );13671368    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1369  }13701371  /**1372   * Get properties, metadata assigned to a token.1373   *1374   * @param collectionId ID of collection1375   * @param tokenId ID of token1376   * @param propertyKeys optionally filter the returned properties to only these keys1377   * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1378   * @returns array of key-value pairs1379   */1380  async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1381    return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1382  }13831384  /**1385   * Delete the provided properties of a token1386   * @param signer keyring of signer1387   * @param collectionId ID of collection1388   * @param tokenId ID of token1389   * @param propertyKeys property keys to be deleted1390   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1391   * @returns ```true``` if extrinsic success, otherwise ```false```1392   */1393  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1394    const result = await this.helper.executeExtrinsic(1395      signer,1396      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1397      true,1398    );13991400    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1401  }14021403  /**1404   * Mint new collection1405   *1406   * @param signer keyring of signer1407   * @param collectionOptions basic collection options and properties1408   * @param mode NFT or RFT type of a collection1409   * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1410   * @returns object of the created collection1411   */1412  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1413    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1414    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1415    for (const key of ['name', 'description', 'tokenPrefix']) {1416      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);1417    }1418    const creationResult = await this.helper.executeExtrinsic(1419      signer,1420      'api.tx.unique.createCollectionEx', [collectionOptions],1421      true, // errorLabel,1422    );1423    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1424  }14251426  getCollectionObject(_collectionId: number): any {1427    return null;1428  }14291430  getTokenObject(_collectionId: number, _tokenId: number): any {1431    return null;1432  }14331434  /**1435   * Tells whether the given `owner` approves the `operator`.1436   * @param collectionId ID of collection1437   * @param owner owner address1438   * @param operator operator addrees1439   * @returns true if operator is enabled1440   */1441  async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1442    return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1443  }14441445  /** Sets or unsets the approval of a given operator.1446   *  The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1447   *  @param operator Operator1448   *  @param approved Should operator status be granted or revoked?1449   *  @returns ```true``` if extrinsic success, otherwise ```false```1450   */1451  async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1452    const result = await this.helper.executeExtrinsic(1453      signer,1454      'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1455      true,1456    );1457    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1458  }1459}146014611462class NFTGroup extends NFTnRFT {1463  /**1464   * Get collection object1465   * @param collectionId ID of collection1466   * @example getCollectionObject(2);1467   * @returns instance of UniqueNFTCollection1468   */1469  getCollectionObject(collectionId: number): UniqueNFTCollection {1470    return new UniqueNFTCollection(collectionId, this.helper);1471  }14721473  /**1474   * Get token object1475   * @param collectionId ID of collection1476   * @param tokenId ID of token1477   * @example getTokenObject(10, 5);1478   * @returns instance of UniqueNFTToken1479   */1480  getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1481    return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1482  }14831484  /**1485   * Get token's owner1486   * @param collectionId ID of collection1487   * @param tokenId ID of token1488   * @param blockHashAt optionally query the data at the block with this hash1489   * @example getTokenOwner(10, 5);1490   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1491   */1492  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1493    let owner;1494    if (typeof blockHashAt === 'undefined') {1495      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1496    } else {1497      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1498    }1499    return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1500  }15011502  /**1503   * Is token approved to transfer1504   * @param collectionId ID of collection1505   * @param tokenId ID of token1506   * @param toAccountObj address to be approved1507   * @returns ```true``` if extrinsic success, otherwise ```false```1508   */1509  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1510    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1511  }15121513  /**1514   * Changes the owner of the token.1515   *1516   * @param signer keyring of signer1517   * @param collectionId ID of collection1518   * @param tokenId ID of token1519   * @param addressObj address of a new owner1520   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1521   * @returns ```true``` if extrinsic success, otherwise ```false```1522   */1523  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1524    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1525  }15261527  /**1528   *1529   * Change ownership of a NFT on behalf of the owner.1530   *1531   * @param signer keyring of signer1532   * @param collectionId ID of collection1533   * @param tokenId ID of token1534   * @param fromAddressObj address on behalf of which the token will be sent1535   * @param toAddressObj new token owner1536   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1537   * @returns ```true``` if extrinsic success, otherwise ```false```1538   */1539  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1540    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1541  }15421543  /**1544   * Recursively find the address that owns the token1545   * @param collectionId ID of collection1546   * @param tokenId ID of token1547   * @param blockHashAt1548   * @example getTokenTopmostOwner(10, 5);1549   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1550   */1551  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1552    let owner;1553    if (typeof blockHashAt === 'undefined') {1554      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1555    } else {1556      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1557    }15581559    if (owner === null) return null;15601561    return owner.toHuman();1562  }15631564  /**1565   * Get tokens nested in the provided token1566   * @param collectionId ID of collection1567   * @param tokenId ID of token1568   * @param blockHashAt optionally query the data at the block with this hash1569   * @example getTokenChildren(10, 5);1570   * @returns tokens whose depth of nesting is <= 51571   */1572  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1573    let children;1574    if(typeof blockHashAt === 'undefined') {1575      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1576    } else {1577      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1578    }15791580    return children.toJSON().map((x: any) => {1581      return {collectionId: x.collection, tokenId: x.token};1582    });1583  }15841585  /**1586   * Nest one token into another1587   * @param signer keyring of signer1588   * @param tokenObj token to be nested1589   * @param rootTokenObj token to be parent1590   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1591   * @returns ```true``` if extrinsic success, otherwise ```false```1592   */1593  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1594    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1595    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1596    if(!result) {1597      throw Error('Unable to nest token!');1598    }1599    return result;1600  }16011602  /**1603   * Remove token from nested state1604   * @param signer keyring of signer1605   * @param tokenObj token to unnest1606   * @param rootTokenObj parent of a token1607   * @param toAddressObj address of a new token owner1608   * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1609   * @returns ```true``` if extrinsic success, otherwise ```false```1610   */1611  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1612    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1613    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1614    if(!result) {1615      throw Error('Unable to unnest token!');1616    }1617    return result;1618  }16191620  /**1621   * Mint new collection1622   * @param signer keyring of signer1623   * @param collectionOptions Collection options1624   * @example1625   * mintCollection(aliceKeyring, {1626   *   name: 'New',1627   *   description: 'New collection',1628   *   tokenPrefix: 'NEW',1629   * })1630   * @returns object of the created collection1631   */1632  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1633    return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1634  }16351636  /**1637   * Mint new token1638   * @param signer keyring of signer1639   * @param data token data1640   * @returns created token object1641   */1642  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1643    const creationResult = await this.helper.executeExtrinsic(1644      signer,1645      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1646        nft: {1647          properties: data.properties,1648        },1649      }],1650      true,1651    );1652    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1653    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1654    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1655    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1656  }16571658  /**1659   * Mint multiple NFT tokens1660   * @param signer keyring of signer1661   * @param collectionId ID of collection1662   * @param tokens array of tokens with owner and properties1663   * @example1664   * mintMultipleTokens(aliceKeyring, 10, [{1665   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1666   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1667   *   },{1668   *     owner: {Ethereum: "0x9F0583DbB855d..."},1669   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1670   * }]);1671   * @returns ```true``` if extrinsic success, otherwise ```false```1672   */1673  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1674    const creationResult = await this.helper.executeExtrinsic(1675      signer,1676      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1677      true,1678    );1679    const collection = this.getCollectionObject(collectionId);1680    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1681  }16821683  /**1684   * Mint multiple NFT tokens with one owner1685   * @param signer keyring of signer1686   * @param collectionId ID of collection1687   * @param owner tokens owner1688   * @param tokens array of tokens with owner and properties1689   * @example1690   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1691   *   properties: [{1692   *   key: "gender",1693   *   value: "female",1694   *  },{1695   *   key: "age",1696   *   value: "33",1697   *  }],1698   * }]);1699   * @returns array of newly created tokens1700   */1701  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1702    const rawTokens = [];1703    for (const token of tokens) {1704      const raw = {NFT: {properties: token.properties}};1705      rawTokens.push(raw);1706    }1707    const creationResult = await this.helper.executeExtrinsic(1708      signer,1709      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1710      true,1711    );1712    const collection = this.getCollectionObject(collectionId);1713    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1714  }17151716  /**1717   * Set, change, or remove approved address to transfer the ownership of the NFT.1718   *1719   * @param signer keyring of signer1720   * @param collectionId ID of collection1721   * @param tokenId ID of token1722   * @param toAddressObj address to approve1723   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1724   * @returns ```true``` if extrinsic success, otherwise ```false```1725   */1726  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId) {1727    return super.approveToken(signer, collectionId, tokenId, toAddressObj, 1n);1728  }1729}173017311732class RFTGroup extends NFTnRFT {1733  /**1734   * Get collection object1735   * @param collectionId ID of collection1736   * @example getCollectionObject(2);1737   * @returns instance of UniqueRFTCollection1738   */1739  getCollectionObject(collectionId: number): UniqueRFTCollection {1740    return new UniqueRFTCollection(collectionId, this.helper);1741  }17421743  /**1744   * Get token object1745   * @param collectionId ID of collection1746   * @param tokenId ID of token1747   * @example getTokenObject(10, 5);1748   * @returns instance of UniqueNFTToken1749   */1750  getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1751    return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1752  }17531754  /**1755   * Get top 10 token owners with the largest number of pieces1756   * @param collectionId ID of collection1757   * @param tokenId ID of token1758   * @example getTokenTop10Owners(10, 5);1759   * @returns array of top 10 owners1760   */1761  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1762    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1763  }17641765  /**1766   * Get number of pieces owned by address1767   * @param collectionId ID of collection1768   * @param tokenId ID of token1769   * @param addressObj address token owner1770   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1771   * @returns number of pieces ownerd by address1772   */1773  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1774    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1775  }17761777  /**1778   * Transfer pieces of token to another address1779   * @param signer keyring of signer1780   * @param collectionId ID of collection1781   * @param tokenId ID of token1782   * @param addressObj address of a new owner1783   * @param amount number of pieces to be transfered1784   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1785   * @returns ```true``` if extrinsic success, otherwise ```false```1786   */1787  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1788    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1789  }17901791  /**1792   * Change ownership of some pieces of RFT on behalf of the owner.1793   * @param signer keyring of signer1794   * @param collectionId ID of collection1795   * @param tokenId ID of token1796   * @param fromAddressObj address on behalf of which the token will be sent1797   * @param toAddressObj new token owner1798   * @param amount number of pieces to be transfered1799   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1800   * @returns ```true``` if extrinsic success, otherwise ```false```1801   */1802  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1803    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1804  }18051806  /**1807   * Mint new collection1808   * @param signer keyring of signer1809   * @param collectionOptions Collection options1810   * @example1811   * mintCollection(aliceKeyring, {1812   *   name: 'New',1813   *   description: 'New collection',1814   *   tokenPrefix: 'NEW',1815   * })1816   * @returns object of the created collection1817   */1818  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1819    return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1820  }18211822  /**1823   * Mint new token1824   * @param signer keyring of signer1825   * @param data token data1826   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1827   * @returns created token object1828   */1829  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1830    const creationResult = await this.helper.executeExtrinsic(1831      signer,1832      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1833        refungible: {1834          pieces: data.pieces,1835          properties: data.properties,1836        },1837      }],1838      true,1839    );1840    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1841    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1842    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1843    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1844  }18451846  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1847    throw Error('Not implemented');1848    const creationResult = await this.helper.executeExtrinsic(1849      signer,1850      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1851      true, // `Unable to mint RFT tokens for ${label}`,1852    );1853    const collection = this.getCollectionObject(collectionId);1854    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1855  }18561857  /**1858   * Mint multiple RFT tokens with one owner1859   * @param signer keyring of signer1860   * @param collectionId ID of collection1861   * @param owner tokens owner1862   * @param tokens array of tokens with properties and pieces1863   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1864   * @returns array of newly created RFT tokens1865   */1866  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1867    const rawTokens = [];1868    for (const token of tokens) {1869      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1870      rawTokens.push(raw);1871    }1872    const creationResult = await this.helper.executeExtrinsic(1873      signer,1874      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1875      true,1876    );1877    const collection = this.getCollectionObject(collectionId);1878    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1879  }18801881  /**1882   * Destroys a concrete instance of RFT.1883   * @param signer keyring of signer1884   * @param collectionId ID of collection1885   * @param tokenId ID of token1886   * @param amount number of pieces to be burnt1887   * @example burnToken(aliceKeyring, 10, 5);1888   * @returns ```true``` if the extrinsic is successful, otherwise ```false```1889   */1890  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1891    return await super.burnToken(signer, collectionId, tokenId, amount);1892  }18931894  /**1895   * Destroys a concrete instance of RFT on behalf of the owner.1896   * @param signer keyring of signer1897   * @param collectionId ID of collection1898   * @param tokenId ID of token1899   * @param fromAddressObj address on behalf of which the token will be burnt1900   * @param amount number of pieces to be burnt1901   * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1902   * @returns ```true``` if extrinsic success, otherwise ```false```1903   */1904  async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1905    return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1906  }19071908  /**1909   * Set, change, or remove approved address to transfer the ownership of the RFT.1910   *1911   * @param signer keyring of signer1912   * @param collectionId ID of collection1913   * @param tokenId ID of token1914   * @param toAddressObj address to approve1915   * @param amount number of pieces to be approved1916   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1917   * @returns true if the token success, otherwise false1918   */1919  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1920    return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1921  }19221923  /**1924   * Get total number of pieces1925   * @param collectionId ID of collection1926   * @param tokenId ID of token1927   * @example getTokenTotalPieces(10, 5);1928   * @returns number of pieces1929   */1930  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1931    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1932  }19331934  /**1935   * Change number of token pieces. Signer must be the owner of all token pieces.1936   * @param signer keyring of signer1937   * @param collectionId ID of collection1938   * @param tokenId ID of token1939   * @param amount new number of pieces1940   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1941   * @returns true if the repartion was success, otherwise false1942   */1943  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {1944    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1945    const repartitionResult = await this.helper.executeExtrinsic(1946      signer,1947      'api.tx.unique.repartition', [collectionId, tokenId, amount],1948      true,1949    );1950    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');1951    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');1952  }1953}195419551956class FTGroup extends CollectionGroup {1957  /**1958   * Get collection object1959   * @param collectionId ID of collection1960   * @example getCollectionObject(2);1961   * @returns instance of UniqueFTCollection1962   */1963  getCollectionObject(collectionId: number): UniqueFTCollection {1964    return new UniqueFTCollection(collectionId, this.helper);1965  }19661967  /**1968   * Mint new fungible collection1969   * @param signer keyring of signer1970   * @param collectionOptions Collection options1971   * @param decimalPoints number of token decimals1972   * @example1973   * mintCollection(aliceKeyring, {1974   *   name: 'New',1975   *   description: 'New collection',1976   *   tokenPrefix: 'NEW',1977   * }, 18)1978   * @returns newly created fungible collection1979   */1980  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1981    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1982    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1983    collectionOptions.mode = {fungible: decimalPoints};1984    for (const key of ['name', 'description', 'tokenPrefix']) {1985      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);1986    }1987    const creationResult = await this.helper.executeExtrinsic(1988      signer,1989      'api.tx.unique.createCollectionEx', [collectionOptions],1990      true,1991    );1992    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1993  }19941995  /**1996   * Mint tokens1997   * @param signer keyring of signer1998   * @param collectionId ID of collection1999   * @param owner address owner of new tokens2000   * @param amount amount of tokens to be meanted2001   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);2002   * @returns ```true``` if extrinsic success, otherwise ```false```2003   */2004  async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {2005    const creationResult = await this.helper.executeExtrinsic(2006      signer,2007      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {2008        fungible: {2009          value: amount,2010        },2011      }],2012      true, // `Unable to mint fungible tokens for ${label}`,2013    );2014    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2015  }20162017  /**2018   * Mint multiple Fungible tokens with one owner2019   * @param signer keyring of signer2020   * @param collectionId ID of collection2021   * @param owner tokens owner2022   * @param tokens array of tokens with properties and pieces2023   * @returns ```true``` if extrinsic success, otherwise ```false```2024   */2025  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: {value: bigint}[], owner: ICrossAccountId): Promise<boolean> {2026    const rawTokens = [];2027    for (const token of tokens) {2028      const raw = {Fungible: {Value: token.value}};2029      rawTokens.push(raw);2030    }2031    const creationResult = await this.helper.executeExtrinsic(2032      signer,2033      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2034      true,2035    );2036    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2037  }20382039  /**2040   * Get the top 10 owners with the largest balance for the Fungible collection2041   * @param collectionId ID of collection2042   * @example getTop10Owners(10);2043   * @returns array of ```ICrossAccountId```2044   */2045  async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2046    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2047  }20482049  /**2050   * Get account balance2051   * @param collectionId ID of collection2052   * @param addressObj address of owner2053   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2054   * @returns amount of fungible tokens owned by address2055   */2056  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2057    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2058  }20592060  /**2061   * Transfer tokens to address2062   * @param signer keyring of signer2063   * @param collectionId ID of collection2064   * @param toAddressObj address recipient2065   * @param amount amount of tokens to be sent2066   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2067   * @returns ```true``` if extrinsic success, otherwise ```false```2068   */2069  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2070    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2071  }20722073  /**2074   * Transfer some tokens on behalf of the owner.2075   * @param signer keyring of signer2076   * @param collectionId ID of collection2077   * @param fromAddressObj address on behalf of which tokens will be sent2078   * @param toAddressObj address where token to be sent2079   * @param amount number of tokens to be sent2080   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2081   * @returns ```true``` if extrinsic success, otherwise ```false```2082   */2083  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {2084    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2085  }20862087  /**2088   * Destroy some amount of tokens2089   * @param signer keyring of signer2090   * @param collectionId ID of collection2091   * @param amount amount of tokens to be destroyed2092   * @example burnTokens(aliceKeyring, 10, 1000n);2093   * @returns ```true``` if extrinsic success, otherwise ```false```2094   */2095  async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise<boolean> {2096    return await super.burnToken(signer, collectionId, 0, amount);2097  }20982099  /**2100   * Burn some tokens on behalf of the owner.2101   * @param signer keyring of signer2102   * @param collectionId ID of collection2103   * @param fromAddressObj address on behalf of which tokens will be burnt2104   * @param amount amount of tokens to be burnt2105   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2106   * @returns ```true``` if extrinsic success, otherwise ```false```2107   */2108  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {2109    return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2110  }21112112  /**2113   * Get total collection supply2114   * @param collectionId2115   * @returns2116   */2117  async getTotalPieces(collectionId: number): Promise<bigint> {2118    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2119  }21202121  /**2122   * Set, change, or remove approved address to transfer tokens.2123   *2124   * @param signer keyring of signer2125   * @param collectionId ID of collection2126   * @param toAddressObj address to be approved2127   * @param amount amount of tokens to be approved2128   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2129   * @returns ```true``` if extrinsic success, otherwise ```false```2130   */2131  approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2132    return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2133  }21342135  /**2136   * Get amount of fungible tokens approved to transfer2137   * @param collectionId ID of collection2138   * @param fromAddressObj owner of tokens2139   * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2140   * @returns number of tokens approved for the transfer2141   */2142  getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2143    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2144  }2145}214621472148class ChainGroup extends HelperGroup<ChainHelperBase> {2149  /**2150   * Get system properties of a chain2151   * @example getChainProperties();2152   * @returns ss58Format, token decimals, and token symbol2153   */2154  getChainProperties(): IChainProperties {2155    const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2156    return {2157      ss58Format: properties.ss58Format.toJSON(),2158      tokenDecimals: properties.tokenDecimals.toJSON(),2159      tokenSymbol: properties.tokenSymbol.toJSON(),2160    };2161  }21622163  /**2164   * Get chain header2165   * @example getLatestBlockNumber();2166   * @returns the number of the last block2167   */2168  async getLatestBlockNumber(): Promise<number> {2169    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2170  }21712172  /**2173   * Get block hash by block number2174   * @param blockNumber number of block2175   * @example getBlockHashByNumber(12345);2176   * @returns hash of a block2177   */2178  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2179    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2180    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2181    return blockHash;2182  }21832184  // TODO add docs2185  async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2186    const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2187    if (!blockHash) return null;2188    return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2189  }21902191  /**2192   * Get account nonce2193   * @param address substrate address2194   * @example getNonce("5GrwvaEF5zXb26Fz...");2195   * @returns number, account's nonce2196   */2197  async getNonce(address: TSubstrateAccount): Promise<number> {2198    return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2199  }2200}22012202class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2203  /**2204 * Get substrate address balance2205 * @param address substrate address2206 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2207 * @returns amount of tokens on address2208 */2209  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2210    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2211  }22122213  /**2214   * Transfer tokens to substrate address2215   * @param signer keyring of signer2216   * @param address substrate address of a recipient2217   * @param amount amount of tokens to be transfered2218   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2219   * @returns ```true``` if extrinsic success, otherwise ```false```2220   */2221  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2222    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}`*/);22232224    let transfer = {from: null, to: null, amount: 0n} as any;2225    result.result.events.forEach(({event: {data, method, section}}) => {2226      if ((section === 'balances') && (method === 'Transfer')) {2227        transfer = {2228          from: this.helper.address.normalizeSubstrate(data[0]),2229          to: this.helper.address.normalizeSubstrate(data[1]),2230          amount: BigInt(data[2]),2231        };2232      }2233    });2234    const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2235      && this.helper.address.normalizeSubstrate(address) === transfer.to2236      && BigInt(amount) === transfer.amount;2237    return isSuccess;2238  }22392240  /**2241   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2242   * @param address substrate address2243   * @returns2244   */2245  async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2246    const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2247    return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2248  }2249}22502251class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2252  /**2253   * Get ethereum address balance2254   * @param address ethereum address2255   * @example getEthereum("0x9F0583DbB855d...")2256   * @returns amount of tokens on address2257   */2258  async getEthereum(address: TEthereumAccount): Promise<bigint> {2259    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2260  }22612262  /**2263   * Transfer tokens to address2264   * @param signer keyring of signer2265   * @param address Ethereum address of a recipient2266   * @param amount amount of tokens to be transfered2267   * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2268   * @returns ```true``` if extrinsic success, otherwise ```false```2269   */2270  async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2271    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);22722273    let transfer = {from: null, to: null, amount: 0n} as any;2274    result.result.events.forEach(({event: {data, method, section}}) => {2275      if ((section === 'balances') && (method === 'Transfer')) {2276        transfer = {2277          from: data[0].toString(),2278          to: data[1].toString(),2279          amount: BigInt(data[2]),2280        };2281      }2282    });2283    const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2284      && address === transfer.to2285      && BigInt(amount) === transfer.amount;2286    return isSuccess;2287  }2288}22892290class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2291  subBalanceGroup: SubstrateBalanceGroup<T>;2292  ethBalanceGroup: EthereumBalanceGroup<T>;22932294  constructor(helper: T) {2295    super(helper);2296    this.subBalanceGroup = new SubstrateBalanceGroup(helper);2297    this.ethBalanceGroup = new EthereumBalanceGroup(helper);2298  }22992300  getCollectionCreationPrice(): bigint {2301    return 2n * this.getOneTokenNominal();2302  }2303  /**2304   * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2305   * @example getOneTokenNominal()2306   * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2307   */2308  getOneTokenNominal(): bigint {2309    const chainProperties = this.helper.chain.getChainProperties();2310    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2311  }23122313  /**2314   * Get substrate address balance2315   * @param address substrate address2316   * @example getSubstrate("5GrwvaEF5zXb26Fz...")2317   * @returns amount of tokens on address2318   */2319  getSubstrate(address: TSubstrateAccount): Promise<bigint> {2320    return this.subBalanceGroup.getSubstrate(address);2321  }23222323  /**2324   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2325   * @param address substrate address2326   * @returns2327   */2328  getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2329    return this.subBalanceGroup.getSubstrateFull(address);2330  }23312332  /**2333   * Get ethereum address balance2334   * @param address ethereum address2335   * @example getEthereum("0x9F0583DbB855d...")2336   * @returns amount of tokens on address2337   */2338  getEthereum(address: TEthereumAccount): Promise<bigint> {2339    return this.ethBalanceGroup.getEthereum(address);2340  }23412342  /**2343   * Transfer tokens to substrate address2344   * @param signer keyring of signer2345   * @param address substrate address of a recipient2346   * @param amount amount of tokens to be transfered2347   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2348   * @returns ```true``` if extrinsic success, otherwise ```false```2349   */2350  transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2351    return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2352  }23532354  async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2355    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);23562357    let transfer = {from: null, to: null, amount: 0n} as any;2358    result.result.events.forEach(({event: {data, method, section}}) => {2359      if ((section === 'balances') && (method === 'Transfer')) {2360        transfer = {2361          from: this.helper.address.normalizeSubstrate(data[0]),2362          to: this.helper.address.normalizeSubstrate(data[1]),2363          amount: BigInt(data[2]),2364        };2365      }2366    });2367    let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2368    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2369    isSuccess = isSuccess && BigInt(amount) === transfer.amount;2370    return isSuccess;2371  }2372}23732374class AddressGroup extends HelperGroup<ChainHelperBase> {2375  /**2376   * Normalizes the address to the specified ss58 format, by default ```42```.2377   * @param address substrate address2378   * @param ss58Format format for address conversion, by default ```42```2379   * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2380   * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2381   */2382  normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2383    return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2384  }23852386  /**2387   * Get address in the connected chain format2388   * @param address substrate address2389   * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2390   * @returns address in chain format2391   */2392  normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2393    return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2394  }23952396  /**2397   * Get substrate mirror of an ethereum address2398   * @param ethAddress ethereum address2399   * @param toChainFormat false for normalized account2400   * @example ethToSubstrate('0x9F0583DbB855d...')2401   * @returns substrate mirror of a provided ethereum address2402   */2403  ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): TSubstrateAccount {2404    return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2405  }24062407  /**2408   * Get ethereum mirror of a substrate address2409   * @param subAddress substrate account2410   * @example substrateToEth("5DnSF6RRjwteE3BrC...")2411   * @returns ethereum mirror of a provided substrate address2412   */2413  substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2414    return CrossAccountId.translateSubToEth(subAddress);2415  }24162417  /**2418   * Encode key to substrate address2419   * @param key key for encoding address2420   * @param ss58Format prefix for encoding to the address of the corresponding network2421   * @returns encoded substrate address2422   */2423  encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format = 42): string {2424    const u8a :Uint8Array = typeof key === 'string'2425      ? hexToU8a(key)2426      : typeof key === 'bigint'2427        ? hexToU8a(key.toString(16))2428        : key;2429  2430    if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2431      throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`);2432    }2433  2434    const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2435    if (!allowedDecodedLengths.includes(u8a.length)) {2436      throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2437    }2438  2439    const u8aPrefix = ss58Format < 642440      ? new Uint8Array([ss58Format])2441      : new Uint8Array([2442        ((ss58Format & 0xfc) >> 2) | 0x40,2443        (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2444      ]);24452446    const input = u8aConcat(u8aPrefix, u8a);2447  2448    return base58Encode(u8aConcat(2449      input,2450      blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2451    ));2452  }24532454  /**2455   * Restore substrate address from bigint representation2456   * @param number decimal representation of substrate address2457   * @returns substrate address2458   */2459  restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2460    if (this.helper.api === null) {2461      throw 'Not connected';2462    }2463    const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2464    if (res === undefined || res === null) {2465      throw 'Restore address error';2466    }2467    return res.toString();2468  }24692470  /**2471   * Convert etherium cross account id to substrate cross account id2472   * @param ethCrossAccount etherium cross account2473   * @returns substrate cross account id2474   */2475  convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2476    if (ethCrossAccount.sub === '0') {2477      return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2478    }2479    2480    const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2481    return {Substrate: ss58};2482  }24832484  paraSiblingSovereignAccount(paraid: number) {2485    // We are getting a *sibling* parachain sovereign account,2486    // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2487    const siblingPrefix = '0x7369626c';24882489    const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2490    const suffix = '000000000000000000000000000000000000000000000000';24912492    return siblingPrefix + encodedParaId + suffix;2493  }2494}24952496class StakingGroup extends HelperGroup<UniqueHelper> {2497  /**2498   * Stake tokens for App Promotion2499   * @param signer keyring of signer2500   * @param amountToStake amount of tokens to stake2501   * @param label extra label for log2502   * @returns2503   */2504  async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2505    if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2506    const _stakeResult = await this.helper.executeExtrinsic(2507      signer, 'api.tx.appPromotion.stake',2508      [amountToStake], true,2509    );2510    // TODO extract info from stakeResult2511    return true;2512  }25132514  /**2515   * Unstake tokens for App Promotion2516   * @param signer keyring of signer2517   * @param amountToUnstake amount of tokens to unstake2518   * @param label extra label for log2519   * @returns block number where balances will be unlocked2520   */2521  async unstake(signer: TSigner, label?: string): Promise<number> {2522    if(typeof label === 'undefined') label = `${signer.address}`;2523    const _unstakeResult = await this.helper.executeExtrinsic(2524      signer, 'api.tx.appPromotion.unstake',2525      [], true,2526    );2527    // TODO extract block number fron events2528    return 1;2529  }25302531  /**2532   * Get total staked amount for address2533   * @param address substrate or ethereum address2534   * @returns total staked amount2535   */2536  async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2537    if (address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2538    return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2539  }25402541  /**2542   * Get total staked per block2543   * @param address substrate or ethereum address2544   * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2545   */2546  async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2547    const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2548    return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => {2549      return {2550        block: block.toBigInt(),2551        amount: amount.toBigInt(),2552      };2553    });2554  }25552556  /**2557   * Get total pending unstake amount for address2558   * @param address substrate or ethereum address2559   * @returns total pending unstake amount2560   */2561  async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2562    return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2563  }25642565  /**2566   * Get pending unstake amount per block for address2567   * @param address substrate or ethereum address2568   * @returns array of pending stakes. `block` – the number of the block in which the unstake was made. `amount` - the number of tokens unstaked in the block2569   */2570  async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2571    const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2572    const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => {2573      return {2574        block: block.toBigInt(),2575        amount: amount.toBigInt(),2576      };2577    });2578    return result;2579  }2580}25812582class SchedulerGroup extends HelperGroup<UniqueHelper> {2583  constructor(helper: UniqueHelper) {2584    super(helper);2585  }25862587  cancelScheduled(signer: TSigner, scheduledId: string) {2588    return this.helper.executeExtrinsic(2589      signer,2590      'api.tx.scheduler.cancelNamed',2591      [scheduledId],2592      true,2593    );2594  }25952596  changePriority(signer: TSigner, scheduledId: string, priority: number) {2597    return this.helper.executeExtrinsic(2598      signer,2599      'api.tx.scheduler.changeNamedPriority',2600      [scheduledId, priority],2601      true,2602    );2603  }26042605  scheduleAt<T extends UniqueHelper>(2606    executionBlockNumber: number,2607    options: ISchedulerOptions = {},2608  ) {2609    return this.schedule<T>('schedule', executionBlockNumber, options);2610  }26112612  scheduleAfter<T extends UniqueHelper>(2613    blocksBeforeExecution: number,2614    options: ISchedulerOptions = {},2615  ) {2616    return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2617  }26182619  schedule<T extends UniqueHelper>(2620    scheduleFn: 'schedule' | 'scheduleAfter',2621    blocksNum: number,2622    options: ISchedulerOptions = {},2623  ) {2624    // eslint-disable-next-line @typescript-eslint/naming-convention2625    const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2626    return this.helper.clone(ScheduledHelperType, {2627      scheduleFn,2628      blocksNum,2629      options,2630    }) as T;2631  }2632}26332634class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {2635  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2636    await this.helper.executeExtrinsic(2637      signer,2638      'api.tx.foreignAssets.registerForeignAsset',2639      [ownerAddress, location, metadata],2640      true,2641    );2642  }26432644  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {2645    await this.helper.executeExtrinsic(2646      signer,2647      'api.tx.foreignAssets.updateForeignAsset',2648      [foreignAssetId, location, metadata],2649      true,2650    );2651  }2652}26532654class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {2655  palletName: string;26562657  constructor(helper: T, palletName: string) {2658    super(helper);26592660    this.palletName = palletName;2661  }26622663  async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {2664    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);2665  }2666}26672668class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2669  async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {2670    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);2671  }26722673  async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {2674    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);2675  }26762677  async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {2678    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);2679  }2680}26812682class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2683  async accounts(address: string, currencyId: any) {2684    const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;2685    return BigInt(free);2686  }2687}26882689class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {2690  async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {2691    await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);2692  }26932694  async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {2695    await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);2696  }26972698  async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {2699    await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);2700  }27012702  async account(assetId: string | number, address: string) {2703    const accountAsset = (2704      await this.helper.callRpc('api.query.assets.account', [assetId, address])2705    ).toJSON()! as any;27062707    if (accountAsset !== null) {2708      return BigInt(accountAsset['balance']);2709    } else {2710      return null;2711    }2712  }2713}27142715class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {2716  async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {2717    await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);2718  }2719}27202721class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {2722  makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {2723    const apiPrefix = 'api.tx.assetManager.';27242725    const registerTx = this.helper.constructApiCall(2726      apiPrefix + 'registerForeignAsset',2727      [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],2728    );27292730    const setUnitsTx = this.helper.constructApiCall(2731      apiPrefix + 'setAssetUnitsPerSecond',2732      [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],2733    );27342735    const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);2736    const encodedProposal = batchCall?.method.toHex() || '';2737    return encodedProposal;2738  }27392740  async assetTypeId(location: any) {2741    return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);2742  }2743}27442745class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {2746  async notePreimage(signer: TSigner, encodedProposal: string) {2747    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);2748  }27492750  externalProposeMajority(proposalHash: string) {2751    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);2752  }27532754  fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {2755    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);2756  }27572758  async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {2759    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);2760  }2761}27622763class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {2764  collective: string;27652766  constructor(helper: MoonbeamHelper, collective: string) {2767    super(helper);27682769    this.collective = collective;2770  }27712772  async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {2773    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);2774  }27752776  async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {2777    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);2778  }27792780  async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {2781    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);2782  }27832784  async proposalCount() {2785    return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));2786  }2787}27882789export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;2790export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;27912792export class UniqueHelper extends ChainHelperBase {2793  balance: BalanceGroup<UniqueHelper>;2794  collection: CollectionGroup;2795  nft: NFTGroup;2796  rft: RFTGroup;2797  ft: FTGroup;2798  staking: StakingGroup;2799  scheduler: SchedulerGroup;2800  foreignAssets: ForeignAssetsGroup;2801  xcm: XcmGroup<UniqueHelper>;2802  xTokens: XTokensGroup<UniqueHelper>;2803  tokens: TokensGroup<UniqueHelper>;28042805  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2806    super(logger, options.helperBase ?? UniqueHelper);28072808    this.balance = new BalanceGroup(this);2809    this.collection = new CollectionGroup(this);2810    this.nft = new NFTGroup(this);2811    this.rft = new RFTGroup(this);2812    this.ft = new FTGroup(this);2813    this.staking = new StakingGroup(this);2814    this.scheduler = new SchedulerGroup(this);2815    this.foreignAssets = new ForeignAssetsGroup(this);2816    this.xcm = new XcmGroup(this, 'polkadotXcm');2817    this.xTokens = new XTokensGroup(this);2818    this.tokens = new TokensGroup(this);2819  }28202821  getSudo<T extends UniqueHelper>() {2822    // eslint-disable-next-line @typescript-eslint/naming-convention2823    const SudoHelperType = SudoHelper(this.helperBase);2824    return this.clone(SudoHelperType) as T;2825  }2826}28272828export class XcmChainHelper extends ChainHelperBase {2829  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {2830    const wsProvider = new WsProvider(wsEndpoint);2831    this.api = new ApiPromise({2832      provider: wsProvider,2833    });2834    await this.api.isReadyOrError;2835    this.network = await UniqueHelper.detectNetwork(this.api);2836  }2837}28382839export class RelayHelper extends XcmChainHelper {2840  xcm: XcmGroup<RelayHelper>;28412842  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2843    super(logger, options.helperBase ?? RelayHelper);28442845    this.xcm = new XcmGroup(this, 'xcmPallet');2846  }2847}28482849export class WestmintHelper extends XcmChainHelper {2850  balance: SubstrateBalanceGroup<WestmintHelper>;2851  xcm: XcmGroup<WestmintHelper>;2852  assets: AssetsGroup<WestmintHelper>;2853  xTokens: XTokensGroup<WestmintHelper>;28542855  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2856    super(logger, options.helperBase ?? WestmintHelper);28572858    this.balance = new SubstrateBalanceGroup(this);2859    this.xcm = new XcmGroup(this, 'polkadotXcm');2860    this.assets = new AssetsGroup(this);2861    this.xTokens = new XTokensGroup(this);2862  }2863}28642865export class MoonbeamHelper extends XcmChainHelper {2866  balance: EthereumBalanceGroup<MoonbeamHelper>;2867  assetManager: MoonbeamAssetManagerGroup;2868  assets: AssetsGroup<MoonbeamHelper>;2869  xTokens: XTokensGroup<MoonbeamHelper>;2870  democracy: MoonbeamDemocracyGroup;2871  collective: {2872    council: MoonbeamCollectiveGroup,2873    techCommittee: MoonbeamCollectiveGroup,2874  };28752876  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2877    super(logger, options.helperBase ?? MoonbeamHelper);28782879    this.balance = new EthereumBalanceGroup(this);2880    this.assetManager = new MoonbeamAssetManagerGroup(this);2881    this.assets = new AssetsGroup(this);2882    this.xTokens = new XTokensGroup(this);2883    this.democracy = new MoonbeamDemocracyGroup(this);2884    this.collective = {2885      council: new MoonbeamCollectiveGroup(this, 'councilCollective'),2886      techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),2887    };2888  }2889}28902891export class AcalaHelper extends XcmChainHelper {2892  balance: SubstrateBalanceGroup<AcalaHelper>;2893  assetRegistry: AcalaAssetRegistryGroup;2894  xTokens: XTokensGroup<AcalaHelper>;2895  tokens: TokensGroup<AcalaHelper>;28962897  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2898    super(logger, options.helperBase ?? AcalaHelper);28992900    this.balance = new SubstrateBalanceGroup(this);2901    this.assetRegistry = new AcalaAssetRegistryGroup(this);2902    this.xTokens = new XTokensGroup(this);2903    this.tokens = new TokensGroup(this);2904  }29052906  getSudo<T extends AcalaHelper>() {2907    // eslint-disable-next-line @typescript-eslint/naming-convention2908    const SudoHelperType = SudoHelper(this.helperBase);2909    return this.clone(SudoHelperType) as T;2910  }2911}29122913// eslint-disable-next-line @typescript-eslint/naming-convention2914function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2915  return class extends Base {2916    scheduleFn: 'schedule' | 'scheduleAfter';2917    blocksNum: number;2918    options: ISchedulerOptions;29192920    constructor(...args: any[]) {2921      const logger = args[0] as ILogger;2922      const options = args[1] as {2923        scheduleFn: 'schedule' | 'scheduleAfter',2924        blocksNum: number,2925        options: ISchedulerOptions2926      };29272928      super(logger);29292930      this.scheduleFn = options.scheduleFn;2931      this.blocksNum = options.blocksNum;2932      this.options = options.options;2933    }29342935    executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {2936      const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);2937      2938      const mandatorySchedArgs = [2939        this.blocksNum,2940        this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,2941        this.options.priority ?? null,2942        scheduledTx,2943      ];2944      2945      let schedArgs;2946      let scheduleFn;29472948      if (this.options.scheduledId) {2949        schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];29502951        if (this.scheduleFn == 'schedule') {2952          scheduleFn = 'scheduleNamed';2953        } else if (this.scheduleFn == 'scheduleAfter') {2954          scheduleFn = 'scheduleNamedAfter';2955        }2956      } else {2957        schedArgs = mandatorySchedArgs;2958        scheduleFn = this.scheduleFn;2959      }29602961      const extrinsic = 'api.tx.scheduler.' +  scheduleFn;29622963      return super.executeExtrinsic(2964        sender,2965        extrinsic,2966        schedArgs,2967        expectSuccess,2968      );2969    }2970  };2971}29722973// eslint-disable-next-line @typescript-eslint/naming-convention2974function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {2975  return class extends Base {2976    constructor(...args: any[]) {2977      super(...args);2978    }29792980    executeExtrinsic (2981      sender: IKeyringPair,2982      extrinsic: string,2983      params: any[],2984      expectSuccess?: boolean,2985    ): Promise<ITransactionResult> {2986      const call = this.constructApiCall(extrinsic, params);2987      return super.executeExtrinsic(2988        sender,2989        'api.tx.sudo.sudo',2990        [call],2991        expectSuccess,2992      );2993    }2994  };2995}29962997export class UniqueBaseCollection {2998  helper: UniqueHelper;2999  collectionId: number;30003001  constructor(collectionId: number, uniqueHelper: UniqueHelper) {3002    this.collectionId = collectionId;3003    this.helper = uniqueHelper;3004  }30053006  async getData() {3007    return await this.helper.collection.getData(this.collectionId);3008  }30093010  async getLastTokenId() {3011    return await this.helper.collection.getLastTokenId(this.collectionId);3012  }30133014  async doesTokenExist(tokenId: number) {3015    return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);3016  }30173018  async getAdmins() {3019    return await this.helper.collection.getAdmins(this.collectionId);3020  }30213022  async getAllowList() {3023    return await this.helper.collection.getAllowList(this.collectionId);3024  }30253026  async getEffectiveLimits() {3027    return await this.helper.collection.getEffectiveLimits(this.collectionId);3028  }30293030  async getProperties(propertyKeys?: string[] | null) {3031    return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3032  }30333034  async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3035    return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3036  }30373038  async getOptions() {3039    return await this.helper.collection.getCollectionOptions(this.collectionId);3040  }30413042  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {3043    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);3044  }30453046  async confirmSponsorship(signer: TSigner) {3047    return await this.helper.collection.confirmSponsorship(signer, this.collectionId);3048  }30493050  async removeSponsor(signer: TSigner) {3051    return await this.helper.collection.removeSponsor(signer, this.collectionId);3052  }30533054  async setLimits(signer: TSigner, limits: ICollectionLimits) {3055    return await this.helper.collection.setLimits(signer, this.collectionId, limits);3056  }30573058  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {3059    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);3060  }30613062  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3063    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);3064  }30653066  async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {3067    return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);3068  }30693070  async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {3071    return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);3072  }30733074  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3075    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);3076  }30773078  async setProperties(signer: TSigner, properties: IProperty[]) {3079    return await this.helper.collection.setProperties(signer, this.collectionId, properties);3080  }30813082  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3083    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);3084  }30853086  async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {3087    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);3088  }30893090  async enableNesting(signer: TSigner, permissions: INestingPermissions) {3091    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);3092  }30933094  async disableNesting(signer: TSigner) {3095    return await this.helper.collection.disableNesting(signer, this.collectionId);3096  }30973098  async burn(signer: TSigner) {3099    return await this.helper.collection.burn(signer, this.collectionId);3100  }31013102  scheduleAt<T extends UniqueHelper>(3103    executionBlockNumber: number,3104    options: ISchedulerOptions = {},3105  ) {3106    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3107    return new UniqueBaseCollection(this.collectionId, scheduledHelper);3108  }31093110  scheduleAfter<T extends UniqueHelper>(3111    blocksBeforeExecution: number,3112    options: ISchedulerOptions = {},3113  ) {3114    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3115    return new UniqueBaseCollection(this.collectionId, scheduledHelper);3116  }31173118  getSudo<T extends UniqueHelper>() {3119    return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());3120  }3121}312231233124export class UniqueNFTCollection extends UniqueBaseCollection {3125  getTokenObject(tokenId: number) {3126    return new UniqueNFToken(tokenId, this);3127  }31283129  async getTokensByAddress(addressObj: ICrossAccountId) {3130    return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);3131  }31323133  async getToken(tokenId: number, blockHashAt?: string) {3134    return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);3135  }31363137  async getTokenOwner(tokenId: number, blockHashAt?: string) {3138    return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3139  }31403141  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3142    return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3143  }31443145  async getTokenChildren(tokenId: number, blockHashAt?: string) {3146    return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);3147  }31483149  async getPropertyPermissions(propertyKeys: string[] | null = null) {3150    return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);3151  }31523153  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3154    return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3155  }31563157  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {3158    return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);3159  }31603161  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3162    return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);3163  }31643165  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {3166    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);3167  }31683169  async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {3170    return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);3171  }31723173  async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3174    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});3175  }31763177  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) {3178    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);3179  }31803181  async burnToken(signer: TSigner, tokenId: number) {3182    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);3183  }31843185  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {3186    return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);3187  }31883189  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3190    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);3191  }31923193  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3194    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3195  }31963197  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3198    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3199  }32003201  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3202    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3203  }32043205  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3206    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3207  }32083209  scheduleAt<T extends UniqueHelper>(3210    executionBlockNumber: number,3211    options: ISchedulerOptions = {},3212  ) {3213    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3214    return new UniqueNFTCollection(this.collectionId, scheduledHelper);3215  }32163217  scheduleAfter<T extends UniqueHelper>(3218    blocksBeforeExecution: number,3219    options: ISchedulerOptions = {},3220  ) {3221    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3222    return new UniqueNFTCollection(this.collectionId, scheduledHelper);3223  }32243225  getSudo<T extends UniqueHelper>() {3226    return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());3227  }3228}322932303231export class UniqueRFTCollection extends UniqueBaseCollection {3232  getTokenObject(tokenId: number) {3233    return new UniqueRFToken(tokenId, this);3234  }32353236  async getToken(tokenId: number, blockHashAt?: string) {3237    return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3238  }32393240  async getTokensByAddress(addressObj: ICrossAccountId) {3241    return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3242  }32433244  async getTop10TokenOwners(tokenId: number) {3245    return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3246  }32473248  async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3249    return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3250  }32513252  async getTokenTotalPieces(tokenId: number) {3253    return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3254  }32553256  async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3257    return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3258  }32593260  async getPropertyPermissions(propertyKeys: string[] | null = null) {3261    return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3262  }32633264  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3265    return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3266  }32673268  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {3269    return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3270  }32713272  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3273    return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3274  }32753276  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {3277    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3278  }32793280  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3281    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3282  }32833284  async mintToken(signer: TSigner, pieces=1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3285    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3286  }32873288  async mintMultipleTokens(signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) {3289    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3290  }32913292  async burnToken(signer: TSigner, tokenId: number, amount=1n) {3293    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3294  }32953296  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId,  amount=1n) {3297    return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3298  }32993300  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3301    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3302  }33033304  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3305    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3306  }33073308  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3309    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3310  }33113312  scheduleAt<T extends UniqueHelper>(3313    executionBlockNumber: number,3314    options: ISchedulerOptions = {},3315  ) {3316    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3317    return new UniqueRFTCollection(this.collectionId, scheduledHelper);3318  }33193320  scheduleAfter<T extends UniqueHelper>(3321    blocksBeforeExecution: number,3322    options: ISchedulerOptions = {},3323  ) {3324    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3325    return new UniqueRFTCollection(this.collectionId, scheduledHelper);3326  }33273328  getSudo<T extends UniqueHelper>() {3329    return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());3330  }3331}333233333334export class UniqueFTCollection extends UniqueBaseCollection {3335  async getBalance(addressObj: ICrossAccountId) {3336    return await this.helper.ft.getBalance(this.collectionId, addressObj);3337  }33383339  async getTotalPieces() {3340    return await this.helper.ft.getTotalPieces(this.collectionId);3341  }33423343  async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3344    return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3345  }33463347  async getTop10Owners() {3348    return await this.helper.ft.getTop10Owners(this.collectionId);3349  }33503351  async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {3352    return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3353  }33543355  async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {3356    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3357  }33583359  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3360    return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3361  }33623363  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3364    return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3365  }33663367  async burnTokens(signer: TSigner, amount=1n) {3368    return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3369  }33703371  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3372    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3373  }33743375  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3376    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3377  }33783379  scheduleAt<T extends UniqueHelper>(3380    executionBlockNumber: number,3381    options: ISchedulerOptions = {},3382  ) {3383    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3384    return new UniqueFTCollection(this.collectionId, scheduledHelper);3385  }33863387  scheduleAfter<T extends UniqueHelper>(3388    blocksBeforeExecution: number,3389    options: ISchedulerOptions = {},3390  ) {3391    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3392    return new UniqueFTCollection(this.collectionId, scheduledHelper);3393  }33943395  getSudo<T extends UniqueHelper>() {3396    return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());3397  }3398}339934003401export class UniqueBaseToken {3402  collection: UniqueNFTCollection | UniqueRFTCollection;3403  collectionId: number;3404  tokenId: number;34053406  constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3407    this.collection = collection;3408    this.collectionId = collection.collectionId;3409    this.tokenId = tokenId;3410  }34113412  async getNextSponsored(addressObj: ICrossAccountId) {3413    return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3414  }34153416  async getProperties(propertyKeys?: string[] | null) {3417    return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3418  }34193420  async setProperties(signer: TSigner, properties: IProperty[]) {3421    return await this.collection.setTokenProperties(signer, this.tokenId, properties);3422  }34233424  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3425    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3426  }34273428  async doesExist() {3429    return await this.collection.doesTokenExist(this.tokenId);3430  }34313432  nestingAccount() {3433    return this.collection.helper.util.getTokenAccount(this);3434  }34353436  scheduleAt<T extends UniqueHelper>(3437    executionBlockNumber: number,3438    options: ISchedulerOptions = {},3439  ) {3440    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3441    return new UniqueBaseToken(this.tokenId, scheduledCollection);3442  }34433444  scheduleAfter<T extends UniqueHelper>(3445    blocksBeforeExecution: number,3446    options: ISchedulerOptions = {},3447  ) {3448    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3449    return new UniqueBaseToken(this.tokenId, scheduledCollection);3450  }34513452  getSudo<T extends UniqueHelper>() {3453    return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());3454  }3455}345634573458export class UniqueNFToken extends UniqueBaseToken {3459  collection: UniqueNFTCollection;34603461  constructor(tokenId: number, collection: UniqueNFTCollection) {3462    super(tokenId, collection);3463    this.collection = collection;3464  }34653466  async getData(blockHashAt?: string) {3467    return await this.collection.getToken(this.tokenId, blockHashAt);3468  }34693470  async getOwner(blockHashAt?: string) {3471    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3472  }34733474  async getTopmostOwner(blockHashAt?: string) {3475    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3476  }34773478  async getChildren(blockHashAt?: string) {3479    return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3480  }34813482  async nest(signer: TSigner, toTokenObj: IToken) {3483    return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3484  }34853486  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3487    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3488  }34893490  async transfer(signer: TSigner, addressObj: ICrossAccountId) {3491    return await this.collection.transferToken(signer, this.tokenId, addressObj);3492  }34933494  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3495    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3496  }34973498  async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3499    return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3500  }35013502  async isApproved(toAddressObj: ICrossAccountId) {3503    return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3504  }35053506  async burn(signer: TSigner) {3507    return await this.collection.burnToken(signer, this.tokenId);3508  }35093510  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3511    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3512  }35133514  scheduleAt<T extends UniqueHelper>(3515    executionBlockNumber: number,3516    options: ISchedulerOptions = {},3517  ) {3518    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3519    return new UniqueNFToken(this.tokenId, scheduledCollection);3520  }35213522  scheduleAfter<T extends UniqueHelper>(3523    blocksBeforeExecution: number,3524    options: ISchedulerOptions = {},3525  ) {3526    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3527    return new UniqueNFToken(this.tokenId, scheduledCollection);3528  }35293530  getSudo<T extends UniqueHelper>() {3531    return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());3532  }3533}35343535export class UniqueRFToken extends UniqueBaseToken {3536  collection: UniqueRFTCollection;35373538  constructor(tokenId: number, collection: UniqueRFTCollection) {3539    super(tokenId, collection);3540    this.collection = collection;3541  }35423543  async getData(blockHashAt?: string) {3544    return await this.collection.getToken(this.tokenId, blockHashAt);3545  }35463547  async getTop10Owners() {3548    return await this.collection.getTop10TokenOwners(this.tokenId);3549  }35503551  async getBalance(addressObj: ICrossAccountId) {3552    return await this.collection.getTokenBalance(this.tokenId, addressObj);3553  }35543555  async getTotalPieces() {3556    return await this.collection.getTokenTotalPieces(this.tokenId);3557  }35583559  async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3560    return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3561  }35623563  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {3564    return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3565  }35663567  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3568    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3569  }35703571  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3572    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3573  }35743575  async repartition(signer: TSigner, amount: bigint) {3576    return await this.collection.repartitionToken(signer, this.tokenId, amount);3577  }35783579  async burn(signer: TSigner, amount=1n) {3580    return await this.collection.burnToken(signer, this.tokenId, amount);3581  }35823583  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3584    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3585  }35863587  scheduleAt<T extends UniqueHelper>(3588    executionBlockNumber: number,3589    options: ISchedulerOptions = {},3590  ) {3591    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3592    return new UniqueRFToken(this.tokenId, scheduledCollection);3593  }35943595  scheduleAfter<T extends UniqueHelper>(3596    blocksBeforeExecution: number,3597    options: ISchedulerOptions = {},3598  ) {3599    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3600    return new UniqueRFToken(this.tokenId, scheduledCollection);3601  }36023603  getSudo<T extends UniqueHelper>() {3604    return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());3605  }3606}