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

difftreelog

source

tests/src/util/playgrounds/unique.ts135.6 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 toHuman(data: any) {326    return data && data.toHuman ? data.toHuman() : `${data}`;327  }328329  private static extractData(data: any, type: any): any {330    if(!type) return this.toHuman(data);331    if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();332    if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();333    if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);334    return this.toHuman(data);335  }336337  public static extractEvents(events: {event: any, phase: any}[]): IEvent[] {338    const parsedEvents: IEvent[] = [];339340    events.forEach((record) => {341      const {event, phase} = record;342      const types = event.typeDef;343344      const eventData: IEvent = {345        section: event.section.toString(),346        method: event.method.toString(),347        index: this.extractIndex(event.index),348        data: [],349        phase: phase.toJSON(),350      };351352      event.data.forEach((val: any, index: number) => {353        eventData.data.push(this.extractData(val, types[index]));354      });355356      parsedEvents.push(eventData);357    });358359    return parsedEvents;360  }361}362363export class ChainHelperBase {364  helperBase: any;365366  transactionStatus = UniqueUtil.transactionStatus;367  chainLogType = UniqueUtil.chainLogType;368  util: typeof UniqueUtil;369  eventHelper: typeof UniqueEventHelper;370  logger: ILogger;371  api: ApiPromise | null;372  forcedNetwork: TNetworks | null;373  network: TNetworks | null;374  chainLog: IUniqueHelperLog[];375  children: ChainHelperBase[];376  address: AddressGroup;377  chain: ChainGroup;378379  constructor(logger?: ILogger, helperBase?: any) {380    this.helperBase = helperBase;381382    this.util = UniqueUtil;383    this.eventHelper = UniqueEventHelper;384    if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();385    this.logger = logger;386    this.api = null;387    this.forcedNetwork = null;388    this.network = null;389    this.chainLog = [];390    this.children = [];391    this.address = new AddressGroup(this);392    this.chain = new ChainGroup(this);393  }394395  clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {396    Object.setPrototypeOf(helperCls.prototype, this);397    const newHelper = new helperCls(this.logger, options);398399    newHelper.api = this.api;400    newHelper.network = this.network;401    newHelper.forceNetwork = this.forceNetwork;402403    this.children.push(newHelper);404405    return newHelper;406  }407408  getApi(): ApiPromise {409    if(this.api === null) throw Error('API not initialized');410    return this.api;411  }412413  async subscribeEvents(expectedEvents: {section: string, names: string[]}[]) {414    const collectedEvents: IEvent[] = [];415    const unsubscribe = await this.getApi().query.system.events((events: Vec<FrameSystemEventRecord>) => {416      const ievents = this.eventHelper.extractEvents(events);417      ievents.forEach((event) => {418        expectedEvents.forEach((e => {419          if (event.section === e.section && e.names.includes(event.method)) {420            collectedEvents.push(event);421          }422        }));423      });424    });425    return {unsubscribe: unsubscribe as any, collectedEvents};426  }427428  clearChainLog(): void {429    this.chainLog = [];430  }431432  forceNetwork(value: TNetworks): void {433    this.forcedNetwork = value;434  }435436  async connect(wsEndpoint: string, listeners?: IApiListeners) {437    if (this.api !== null) throw Error('Already connected');438    const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);439    this.api = api;440    this.network = network;441  }442443  async disconnect() {444    for (const child of this.children) {445      child.clearApi();446    }447448    if (this.api === null) return;449    await this.api.disconnect();450    this.clearApi();451  }452453  clearApi() {454    this.api = null;455    this.network = null;456  }457458  static async detectNetwork(api: ApiPromise): Promise<TNetworks> {459    const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;460    const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];461462    if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;463464    if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;465    return 'opal';466  }467468  static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {469    const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});470    await api.isReady;471472    const network = await this.detectNetwork(api);473474    await api.disconnect();475476    return network;477  }478479  static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{480    api: ApiPromise;481    network: TNetworks;482  }> {483    if(typeof network === 'undefined' || network === null) network = 'opal';484    const supportedRPC = {485      opal: {486        unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,487      },488      quartz: {489        unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,490      },491      unique: {492        unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,493      },494      rococo: {},495      westend: {},496      moonbeam: {},497      moonriver: {},498      acala: {},499      karura: {},500      westmint: {},501    };502    if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);503    const rpc = supportedRPC[network];504505    // TODO: investigate how to replace rpc in runtime506    // api._rpcCore.addUserInterfaces(rpc);507508    const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});509510    await api.isReadyOrError;511512    if (typeof listeners === 'undefined') listeners = {};513    for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {514      if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;515      api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);516    }517518    return {api, network};519  }520521  getTransactionStatus(data: {events: {event: IEvent}[], status: any}) {522    const {events, status} = data;523    if (status.isReady) {524      return this.transactionStatus.NOT_READY;525    }526    if (status.isBroadcast) {527      return this.transactionStatus.NOT_READY;528    }529    if (status.isInBlock || status.isFinalized) {530      const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');531      if (errors.length > 0) {532        return this.transactionStatus.FAIL;533      }534      if (events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {535        return this.transactionStatus.SUCCESS;536      }537    }538539    return this.transactionStatus.FAIL;540  }541542  signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {543    const sign = (callback: any) => {544      if(options !== null) return transaction.signAndSend(sender, options, callback);545      return transaction.signAndSend(sender, callback);546    };547    // eslint-disable-next-line no-async-promise-executor548    return new Promise(async (resolve, reject) => {549      try {550        const unsub = await sign((result: any) => {551          const status = this.getTransactionStatus(result);552553          if (status === this.transactionStatus.SUCCESS) {554            this.logger.log(`${label} successful`);555            unsub();556            resolve({result, status});557          } else if (status === this.transactionStatus.FAIL) {558            let moduleError = null;559560            if (result.hasOwnProperty('dispatchError')) {561              const dispatchError = result['dispatchError'];562563              if (dispatchError) {564                if (dispatchError.isModule) {565                  const modErr = dispatchError.asModule;566                  const errorMeta = dispatchError.registry.findMetaError(modErr);567568                  moduleError = `${errorMeta.section}.${errorMeta.name}`;569                } else {570                  moduleError = dispatchError.toHuman();571                }572              } else {573                this.logger.log(result, this.logger.level.ERROR);574              }575            }576577            this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);578            unsub();579            reject({status, moduleError, result});580          }581        });582      } catch (e) {583        this.logger.log(e, this.logger.level.ERROR);584        reject(e);585      }586    });587  }588589  async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {590    const api = this.getApi();591    const signingInfo = await api.derive.tx.signingInfo(signer.address);592593    // We need to sign the tx because594    // unsigned transactions does not have an inclusion fee595    tx.sign(signer, {596      blockHash: api.genesisHash,597      genesisHash: api.genesisHash,598      runtimeVersion: api.runtimeVersion,599      nonce: signingInfo.nonce,600    });601602    if (len === null) {603      return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;604    } else {605      return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;606    }607  }608609  constructApiCall(apiCall: string, params: any[]) {610    if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);611    let call = this.getApi() as any;612    for(const part of apiCall.slice(4).split('.')) {613      call = call[part];614    }615    return call(...params);616  }617618  async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {619    if(this.api === null) throw Error('API not initialized');620    if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);621622    const startTime = (new Date()).getTime();623    let result: ITransactionResult;624    let events: IEvent[] = [];625    try {626      result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;627      events = this.eventHelper.extractEvents(result.result.events);628    }629    catch(e) {630      if(!(e as object).hasOwnProperty('status')) throw e;631      result = e as ITransactionResult;632    }633634    const endTime = (new Date()).getTime();635636    const log = {637      executedAt: endTime,638      executionTime: endTime - startTime,639      type: this.chainLogType.EXTRINSIC,640      status: result.status,641      call: extrinsic,642      signer: this.getSignerAddress(sender),643      params,644    } as IUniqueHelperLog;645646    if(result.status !== this.transactionStatus.SUCCESS) {647      if (result.moduleError) log.moduleError = result.moduleError;648      else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;649    }650    if(events.length > 0) log.events = events;651652    this.chainLog.push(log);653654    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {655      if (result.moduleError) throw Error(`${result.moduleError}`);656      else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));657    }658    return result;659  }660661  async callRpc(rpc: string, params?: any[]) {662    if(typeof params === 'undefined') params = [];663    if(this.api === null) throw Error('API not initialized');664    if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);665666    const startTime = (new Date()).getTime();667    let result;668    let error = null;669    const log = {670      type: this.chainLogType.RPC,671      call: rpc,672      params,673    } as IUniqueHelperLog;674675    try {676      result = await this.constructApiCall(rpc, params);677    }678    catch(e) {679      error = e;680    }681682    const endTime = (new Date()).getTime();683684    log.executedAt = endTime;685    log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';686    log.executionTime = endTime - startTime;687688    this.chainLog.push(log);689690    if(error !== null) throw error;691692    return result;693  }694695  getSignerAddress(signer: IKeyringPair | string): string {696    if(typeof signer === 'string') return signer;697    return signer.address;698  }699700  fetchAllPalletNames(): string[] {701    if(this.api === null) throw Error('API not initialized');702    return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());703  }704705  fetchMissingPalletNames(requiredPallets: string[]): string[] {706    const palletNames = this.fetchAllPalletNames();707    return requiredPallets.filter(p => !palletNames.includes(p));708  }709}710711712class HelperGroup<T extends ChainHelperBase> {713  helper: T;714715  constructor(uniqueHelper: T) {716    this.helper = uniqueHelper;717  }718}719720721class CollectionGroup extends HelperGroup<UniqueHelper> {722  /**723 * Get number of blocks when sponsored transaction is available.724 *725 * @param collectionId ID of collection726 * @param tokenId ID of token727 * @param addressObj address for which the sponsorship is checked728 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});729 * @returns number of blocks or null if sponsorship hasn't been set730 */731  async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {732    return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();733  }734735  /**736   * Get the number of created collections.737   *738   * @returns number of created collections739   */740  async getTotalCount(): Promise<number> {741    return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();742  }743744  /**745   * Get information about the collection with additional data,746   * including the number of tokens it contains, its administrators,747   * the normalized address of the collection's owner, and decoded name and description.748   *749   * @param collectionId ID of collection750   * @example await getData(2)751   * @returns collection information object752   */753  async getData(collectionId: number): Promise<{754    id: number;755    name: string;756    description: string;757    tokensCount: number;758    admins: CrossAccountId[];759    normalizedOwner: TSubstrateAccount;760    raw: any761  } | null> {762    const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);763    const humanCollection = collection.toHuman(), collectionData = {764      id: collectionId, name: null, description: null, tokensCount: 0, admins: [],765      raw: humanCollection,766    } as any, jsonCollection = collection.toJSON();767    if (humanCollection === null) return null;768    collectionData.raw.limits = jsonCollection.limits;769    collectionData.raw.permissions = jsonCollection.permissions;770    collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);771    for (const key of ['name', 'description']) {772      collectionData[key] = this.helper.util.vec2str(humanCollection[key]);773    }774775    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))776      ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)777      : 0;778    collectionData.admins = await this.getAdmins(collectionId);779780    return collectionData;781  }782783  /**784   * Get the addresses of the collection's administrators, optionally normalized.785   *786   * @param collectionId ID of collection787   * @param normalize whether to normalize the addresses to the default ss58 format788   * @example await getAdmins(1)789   * @returns array of administrators790   */791  async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {792    const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();793794    return normalize795      ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())796      : admins;797  }798799  /**800   * Get the addresses added to the collection allow-list, optionally normalized.801   * @param collectionId ID of collection802   * @param normalize whether to normalize the addresses to the default ss58 format803   * @example await getAllowList(1)804   * @returns array of allow-listed addresses805   */806  async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {807    const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();808    return normalize809      ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())810      : allowListed;811  }812813  /**814   * Get the effective limits of the collection instead of null for default values815   *816   * @param collectionId ID of collection817   * @example await getEffectiveLimits(2)818   * @returns object of collection limits819   */820  async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {821    return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();822  }823824  /**825   * Burns the collection if the signer has sufficient permissions and collection is empty.826   *827   * @param signer keyring of signer828   * @param collectionId ID of collection829   * @example await helper.collection.burn(aliceKeyring, 3);830   * @returns ```true``` if extrinsic success, otherwise ```false```831   */832  async burn(signer: TSigner, collectionId: number): Promise<boolean> {833    const result = await this.helper.executeExtrinsic(834      signer,835      'api.tx.unique.destroyCollection', [collectionId],836      true,837    );838839    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');840  }841842  /**843   * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.844   *845   * @param signer keyring of signer846   * @param collectionId ID of collection847   * @param sponsorAddress Sponsor substrate address848   * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")849   * @returns ```true``` if extrinsic success, otherwise ```false```850   */851  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {852    const result = await this.helper.executeExtrinsic(853      signer,854      'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],855      true,856    );857858    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorSet');859  }860861  /**862   * Confirms consent to sponsor the collection on behalf of the signer.863   *864   * @param signer keyring of signer865   * @param collectionId ID of collection866   * @example confirmSponsorship(aliceKeyring, 10)867   * @returns ```true``` if extrinsic success, otherwise ```false```868   */869  async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {870    const result = await this.helper.executeExtrinsic(871      signer,872      'api.tx.unique.confirmSponsorship', [collectionId],873      true,874    );875876    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'SponsorshipConfirmed');877  }878879  /**880   * Removes the sponsor of a collection, regardless if it consented or not.881   *882   * @param signer keyring of signer883   * @param collectionId ID of collection884   * @example removeSponsor(aliceKeyring, 10)885   * @returns ```true``` if extrinsic success, otherwise ```false```886   */887  async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {888    const result = await this.helper.executeExtrinsic(889      signer,890      'api.tx.unique.removeCollectionSponsor', [collectionId],891      true,892    );893894    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorRemoved');895  }896897  /**898   * Sets the limits of the collection. At least one limit must be specified for a correct call.899   *900   * @param signer keyring of signer901   * @param collectionId ID of collection902   * @param limits collection limits object903   * @example904   * await setLimits(905   *   aliceKeyring,906   *   10,907   *   {908   *     sponsorTransferTimeout: 0,909   *     ownerCanDestroy: false910   *   }911   * )912   * @returns ```true``` if extrinsic success, otherwise ```false```913   */914  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {915    const result = await this.helper.executeExtrinsic(916      signer,917      'api.tx.unique.setCollectionLimits', [collectionId, limits],918      true,919    );920921    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionLimitSet');922  }923924  /**925   * Changes the owner of the collection to the new Substrate address.926   *927   * @param signer keyring of signer928   * @param collectionId ID of collection929   * @param ownerAddress substrate address of new owner930   * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")931   * @returns ```true``` if extrinsic success, otherwise ```false```932   */933  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {934    const result = await this.helper.executeExtrinsic(935      signer,936      'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],937      true,938    );939940    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionOwnerChanged');941  }942943  /**944   * Adds a collection administrator.945   *946   * @param signer keyring of signer947   * @param collectionId ID of collection948   * @param adminAddressObj Administrator address (substrate or ethereum)949   * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})950   * @returns ```true``` if extrinsic success, otherwise ```false```951   */952  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {953    const result = await this.helper.executeExtrinsic(954      signer,955      'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],956      true,957    );958959    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminAdded');960  }961962  /**963   * Removes a collection administrator.964   *965   * @param signer keyring of signer966   * @param collectionId ID of collection967   * @param adminAddressObj Administrator address (substrate or ethereum)968   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})969   * @returns ```true``` if extrinsic success, otherwise ```false```970   */971  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {972    const result = await this.helper.executeExtrinsic(973      signer,974      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],975      true,976    );977978    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminRemoved');979  }980981  /**982   * Check if user is in allow list.983   *984   * @param collectionId ID of collection985   * @param user Account to check986   * @example await getAdmins(1)987   * @returns is user in allow list988   */989  async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {990    return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();991  }992993  /**994   * Adds an address to allow list995   * @param signer keyring of signer996   * @param collectionId ID of collection997   * @param addressObj address to add to the allow list998   * @returns ```true``` if extrinsic success, otherwise ```false```999   */1000  async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1001    const result = await this.helper.executeExtrinsic(1002      signer,1003      'api.tx.unique.addToAllowList', [collectionId, addressObj],1004      true,1005    );10061007    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressAdded');1008  }10091010  /**1011   * Removes an address from allow list1012   *1013   * @param signer keyring of signer1014   * @param collectionId ID of collection1015   * @param addressObj address to remove from the allow list1016   * @returns ```true``` if extrinsic success, otherwise ```false```1017   */1018  async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1019    const result = await this.helper.executeExtrinsic(1020      signer,1021      'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1022      true,1023    );10241025    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressRemoved');1026  }10271028  /**1029   * Sets onchain permissions for selected collection.1030   *1031   * @param signer keyring of signer1032   * @param collectionId ID of collection1033   * @param permissions collection permissions object1034   * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1035   * @returns ```true``` if extrinsic success, otherwise ```false```1036   */1037  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1038    const result = await this.helper.executeExtrinsic(1039      signer,1040      'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1041      true,1042    );10431044    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPermissionSet');1045  }10461047  /**1048   * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1049   *1050   * @param signer keyring of signer1051   * @param collectionId ID of collection1052   * @param permissions nesting permissions object1053   * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1054   * @returns ```true``` if extrinsic success, otherwise ```false```1055   */1056  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1057    return await this.setPermissions(signer, collectionId, {nesting: permissions});1058  }10591060  /**1061   * Disables nesting for selected collection.1062   *1063   * @param signer keyring of signer1064   * @param collectionId ID of collection1065   * @example disableNesting(aliceKeyring, 10);1066   * @returns ```true``` if extrinsic success, otherwise ```false```1067   */1068  async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1069    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1070  }10711072  /**1073   * Sets onchain properties to the collection.1074   *1075   * @param signer keyring of signer1076   * @param collectionId ID of collection1077   * @param properties array of property objects1078   * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1079   * @returns ```true``` if extrinsic success, otherwise ```false```1080   */1081  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1082    const result = await this.helper.executeExtrinsic(1083      signer,1084      'api.tx.unique.setCollectionProperties', [collectionId, properties],1085      true,1086    );10871088    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1089  }10901091  /**1092   * Get collection properties.1093   *1094   * @param collectionId ID of collection1095   * @param propertyKeys optionally filter the returned properties to only these keys1096   * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1097   * @returns array of key-value pairs1098   */1099  async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1100    return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1101  }11021103  async getCollectionOptions(collectionId: number) {1104    return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1105  }11061107  /**1108   * Deletes onchain properties from the collection.1109   *1110   * @param signer keyring of signer1111   * @param collectionId ID of collection1112   * @param propertyKeys array of property keys to delete1113   * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1114   * @returns ```true``` if extrinsic success, otherwise ```false```1115   */1116  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1117    const result = await this.helper.executeExtrinsic(1118      signer,1119      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1120      true,1121    );11221123    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1124  }11251126  /**1127   * Changes the owner of the token.1128   *1129   * @param signer keyring of signer1130   * @param collectionId ID of collection1131   * @param tokenId ID of token1132   * @param addressObj address of a new owner1133   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1134   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1135   * @returns true if the token success, otherwise false1136   */1137  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1138    const result = await this.helper.executeExtrinsic(1139      signer,1140      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1141      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1142    );11431144    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1145  }11461147  /**1148   *1149   * Change ownership of a token(s) on behalf of the owner.1150   *1151   * @param signer keyring of signer1152   * @param collectionId ID of collection1153   * @param tokenId ID of token1154   * @param fromAddressObj address on behalf of which the token will be sent1155   * @param toAddressObj new token owner1156   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1157   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1158   * @returns true if the token success, otherwise false1159   */1160  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1161    const result = await this.helper.executeExtrinsic(1162      signer,1163      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1164      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1165    );1166    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1167  }11681169  /**1170   *1171   * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1172   *1173   * @param signer keyring of signer1174   * @param collectionId ID of collection1175   * @param tokenId ID of token1176   * @param amount amount of tokens to be burned. For NFT must be set to 1n1177   * @example burnToken(aliceKeyring, 10, 5);1178   * @returns ```true``` if the extrinsic is successful, otherwise ```false```1179   */1180  async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1181    const burnResult = await this.helper.executeExtrinsic(1182      signer,1183      'api.tx.unique.burnItem', [collectionId, tokenId, amount],1184      true, // `Unable to burn token for ${label}`,1185    );1186    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1187    if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1188    return burnedTokens.success;1189  }11901191  /**1192   * Destroys a concrete instance of NFT on behalf of the owner1193   *1194   * @param signer keyring of signer1195   * @param collectionId ID of collection1196   * @param tokenId ID of token1197   * @param fromAddressObj address on behalf of which the token will be burnt1198   * @param amount amount of tokens to be burned. For NFT must be set to 1n1199   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1200   * @returns ```true``` if extrinsic success, otherwise ```false```1201   */1202  async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1203    const burnResult = await this.helper.executeExtrinsic(1204      signer,1205      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1206      true, // `Unable to burn token from for ${label}`,1207    );1208    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1209    return burnedTokens.success && burnedTokens.tokens.length > 0;1210  }12111212  /**1213   * Set, change, or remove approved address to transfer the ownership of the NFT.1214   *1215   * @param signer keyring of signer1216   * @param collectionId ID of collection1217   * @param tokenId ID of token1218   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1219   * @param amount amount of token to be approved. For NFT must be set to 1n1220   * @returns ```true``` if extrinsic success, otherwise ```false```1221   */1222  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1223    const approveResult = await this.helper.executeExtrinsic(1224      signer,1225      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1226      true, // `Unable to approve token for ${label}`,1227    );12281229    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1230  }12311232  /**1233   * Get the amount of token pieces approved to transfer or burn. Normally 0.1234   *1235   * @param collectionId ID of collection1236   * @param tokenId ID of token1237   * @param toAccountObj address which is approved to use token pieces1238   * @param fromAccountObj address which may have allowed the use of its owned tokens1239   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1240   * @returns number of approved to transfer pieces1241   */1242  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1243    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1244  }12451246  /**1247   * Get the last created token ID in a collection1248   *1249   * @param collectionId ID of collection1250   * @example getLastTokenId(10);1251   * @returns id of the last created token1252   */1253  async getLastTokenId(collectionId: number): Promise<number> {1254    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1255  }12561257  /**1258   * Check if token exists1259   *1260   * @param collectionId ID of collection1261   * @param tokenId ID of token1262   * @example doesTokenExist(10, 20);1263   * @returns true if the token exists, otherwise false1264   */1265  async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1266    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1267  }1268}12691270class NFTnRFT extends CollectionGroup {1271  /**1272   * Get tokens owned by account1273   *1274   * @param collectionId ID of collection1275   * @param addressObj tokens owner1276   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1277   * @returns array of token ids owned by account1278   */1279  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1280    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1281  }12821283  /**1284   * Get token data1285   *1286   * @param collectionId ID of collection1287   * @param tokenId ID of token1288   * @param propertyKeys optionally filter the token properties to only these keys1289   * @param blockHashAt optionally query the data at some block with this hash1290   * @example getToken(10, 5);1291   * @returns human readable token data1292   */1293  async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1294    properties: IProperty[];1295    owner: CrossAccountId;1296    normalizedOwner: CrossAccountId;1297  }| null> {1298    let tokenData;1299    if(typeof blockHashAt === 'undefined') {1300      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1301    }1302    else {1303      if(propertyKeys.length == 0) {1304        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1305        if(!collection) return null;1306        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1307      }1308      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1309    }1310    tokenData = tokenData.toHuman();1311    if (tokenData === null || tokenData.owner === null) return null;1312    const owner = {} as any;1313    for (const key of Object.keys(tokenData.owner)) {1314      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1315        ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1316        : tokenData.owner[key];1317    }1318    tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1319    return tokenData;1320  }13211322  /**1323   * Set permissions to change token properties1324   *1325   * @param signer keyring of signer1326   * @param collectionId ID of collection1327   * @param permissions permissions to change a property by the collection admin or token owner1328   * @example setTokenPropertyPermissions(1329   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1330   * )1331   * @returns true if extrinsic success otherwise false1332   */1333  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1334    const result = await this.helper.executeExtrinsic(1335      signer,1336      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1337      true,1338    );13391340    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1341  }13421343  /**1344   * Get token property permissions.1345   *1346   * @param collectionId ID of collection1347   * @param propertyKeys optionally filter the returned property permissions to only these keys1348   * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1349   * @returns array of key-permission pairs1350   */1351  async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1352    return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1353  }13541355  /**1356   * Set token properties1357   *1358   * @param signer keyring of signer1359   * @param collectionId ID of collection1360   * @param tokenId ID of token1361   * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1362   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1363   * @returns ```true``` if extrinsic success, otherwise ```false```1364   */1365  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1366    const result = await this.helper.executeExtrinsic(1367      signer,1368      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1369      true,1370    );13711372    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1373  }13741375  /**1376   * Get properties, metadata assigned to a token.1377   *1378   * @param collectionId ID of collection1379   * @param tokenId ID of token1380   * @param propertyKeys optionally filter the returned properties to only these keys1381   * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1382   * @returns array of key-value pairs1383   */1384  async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1385    return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1386  }13871388  /**1389   * Delete the provided properties of a token1390   * @param signer keyring of signer1391   * @param collectionId ID of collection1392   * @param tokenId ID of token1393   * @param propertyKeys property keys to be deleted1394   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1395   * @returns ```true``` if extrinsic success, otherwise ```false```1396   */1397  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1398    const result = await this.helper.executeExtrinsic(1399      signer,1400      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1401      true,1402    );14031404    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1405  }14061407  /**1408   * Mint new collection1409   *1410   * @param signer keyring of signer1411   * @param collectionOptions basic collection options and properties1412   * @param mode NFT or RFT type of a collection1413   * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1414   * @returns object of the created collection1415   */1416  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1417    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1418    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1419    for (const key of ['name', 'description', 'tokenPrefix']) {1420      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);1421    }1422    const creationResult = await this.helper.executeExtrinsic(1423      signer,1424      'api.tx.unique.createCollectionEx', [collectionOptions],1425      true, // errorLabel,1426    );1427    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1428  }14291430  getCollectionObject(_collectionId: number): any {1431    return null;1432  }14331434  getTokenObject(_collectionId: number, _tokenId: number): any {1435    return null;1436  }14371438  /**1439   * Tells whether the given `owner` approves the `operator`.1440   * @param collectionId ID of collection1441   * @param owner owner address1442   * @param operator operator addrees1443   * @returns true if operator is enabled1444   */1445  async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1446    return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1447  }14481449  /** Sets or unsets the approval of a given operator.1450   *  The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1451   *  @param operator Operator1452   *  @param approved Should operator status be granted or revoked?1453   *  @returns ```true``` if extrinsic success, otherwise ```false```1454   */1455  async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1456    const result = await this.helper.executeExtrinsic(1457      signer,1458      'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1459      true,1460    );1461    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1462  }1463}146414651466class NFTGroup extends NFTnRFT {1467  /**1468   * Get collection object1469   * @param collectionId ID of collection1470   * @example getCollectionObject(2);1471   * @returns instance of UniqueNFTCollection1472   */1473  getCollectionObject(collectionId: number): UniqueNFTCollection {1474    return new UniqueNFTCollection(collectionId, this.helper);1475  }14761477  /**1478   * Get token object1479   * @param collectionId ID of collection1480   * @param tokenId ID of token1481   * @example getTokenObject(10, 5);1482   * @returns instance of UniqueNFTToken1483   */1484  getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1485    return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1486  }14871488  /**1489   * Get token's owner1490   * @param collectionId ID of collection1491   * @param tokenId ID of token1492   * @param blockHashAt optionally query the data at the block with this hash1493   * @example getTokenOwner(10, 5);1494   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1495   */1496  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1497    let owner;1498    if (typeof blockHashAt === 'undefined') {1499      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1500    } else {1501      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1502    }1503    return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1504  }15051506  /**1507   * Is token approved to transfer1508   * @param collectionId ID of collection1509   * @param tokenId ID of token1510   * @param toAccountObj address to be approved1511   * @returns ```true``` if extrinsic success, otherwise ```false```1512   */1513  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1514    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1515  }15161517  /**1518   * Changes the owner of the token.1519   *1520   * @param signer keyring of signer1521   * @param collectionId ID of collection1522   * @param tokenId ID of token1523   * @param addressObj address of a new owner1524   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1525   * @returns ```true``` if extrinsic success, otherwise ```false```1526   */1527  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1528    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1529  }15301531  /**1532   *1533   * Change ownership of a NFT on behalf of the owner.1534   *1535   * @param signer keyring of signer1536   * @param collectionId ID of collection1537   * @param tokenId ID of token1538   * @param fromAddressObj address on behalf of which the token will be sent1539   * @param toAddressObj new token owner1540   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1541   * @returns ```true``` if extrinsic success, otherwise ```false```1542   */1543  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1544    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1545  }15461547  /**1548   * Recursively find the address that owns the token1549   * @param collectionId ID of collection1550   * @param tokenId ID of token1551   * @param blockHashAt1552   * @example getTokenTopmostOwner(10, 5);1553   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1554   */1555  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1556    let owner;1557    if (typeof blockHashAt === 'undefined') {1558      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1559    } else {1560      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1561    }15621563    if (owner === null) return null;15641565    return owner.toHuman();1566  }15671568  /**1569   * Get tokens nested in the provided token1570   * @param collectionId ID of collection1571   * @param tokenId ID of token1572   * @param blockHashAt optionally query the data at the block with this hash1573   * @example getTokenChildren(10, 5);1574   * @returns tokens whose depth of nesting is <= 51575   */1576  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1577    let children;1578    if(typeof blockHashAt === 'undefined') {1579      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1580    } else {1581      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1582    }15831584    return children.toJSON().map((x: any) => {1585      return {collectionId: x.collection, tokenId: x.token};1586    });1587  }15881589  /**1590   * Nest one token into another1591   * @param signer keyring of signer1592   * @param tokenObj token to be nested1593   * @param rootTokenObj token to be parent1594   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1595   * @returns ```true``` if extrinsic success, otherwise ```false```1596   */1597  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1598    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1599    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1600    if(!result) {1601      throw Error('Unable to nest token!');1602    }1603    return result;1604  }16051606  /**1607   * Remove token from nested state1608   * @param signer keyring of signer1609   * @param tokenObj token to unnest1610   * @param rootTokenObj parent of a token1611   * @param toAddressObj address of a new token owner1612   * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1613   * @returns ```true``` if extrinsic success, otherwise ```false```1614   */1615  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1616    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1617    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1618    if(!result) {1619      throw Error('Unable to unnest token!');1620    }1621    return result;1622  }16231624  /**1625   * Mint new collection1626   * @param signer keyring of signer1627   * @param collectionOptions Collection options1628   * @example1629   * mintCollection(aliceKeyring, {1630   *   name: 'New',1631   *   description: 'New collection',1632   *   tokenPrefix: 'NEW',1633   * })1634   * @returns object of the created collection1635   */1636  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1637    return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1638  }16391640  /**1641   * Mint new token1642   * @param signer keyring of signer1643   * @param data token data1644   * @returns created token object1645   */1646  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1647    const creationResult = await this.helper.executeExtrinsic(1648      signer,1649      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1650        nft: {1651          properties: data.properties,1652        },1653      }],1654      true,1655    );1656    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1657    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1658    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1659    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1660  }16611662  /**1663   * Mint multiple NFT tokens1664   * @param signer keyring of signer1665   * @param collectionId ID of collection1666   * @param tokens array of tokens with owner and properties1667   * @example1668   * mintMultipleTokens(aliceKeyring, 10, [{1669   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1670   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1671   *   },{1672   *     owner: {Ethereum: "0x9F0583DbB855d..."},1673   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1674   * }]);1675   * @returns ```true``` if extrinsic success, otherwise ```false```1676   */1677  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1678    const creationResult = await this.helper.executeExtrinsic(1679      signer,1680      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1681      true,1682    );1683    const collection = this.getCollectionObject(collectionId);1684    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1685  }16861687  /**1688   * Mint multiple NFT tokens with one owner1689   * @param signer keyring of signer1690   * @param collectionId ID of collection1691   * @param owner tokens owner1692   * @param tokens array of tokens with owner and properties1693   * @example1694   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1695   *   properties: [{1696   *   key: "gender",1697   *   value: "female",1698   *  },{1699   *   key: "age",1700   *   value: "33",1701   *  }],1702   * }]);1703   * @returns array of newly created tokens1704   */1705  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1706    const rawTokens = [];1707    for (const token of tokens) {1708      const raw = {NFT: {properties: token.properties}};1709      rawTokens.push(raw);1710    }1711    const creationResult = await this.helper.executeExtrinsic(1712      signer,1713      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1714      true,1715    );1716    const collection = this.getCollectionObject(collectionId);1717    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1718  }17191720  /**1721   * Set, change, or remove approved address to transfer the ownership of the NFT.1722   *1723   * @param signer keyring of signer1724   * @param collectionId ID of collection1725   * @param tokenId ID of token1726   * @param toAddressObj address to approve1727   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1728   * @returns ```true``` if extrinsic success, otherwise ```false```1729   */1730  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId) {1731    return super.approveToken(signer, collectionId, tokenId, toAddressObj, 1n);1732  }1733}173417351736class RFTGroup extends NFTnRFT {1737  /**1738   * Get collection object1739   * @param collectionId ID of collection1740   * @example getCollectionObject(2);1741   * @returns instance of UniqueRFTCollection1742   */1743  getCollectionObject(collectionId: number): UniqueRFTCollection {1744    return new UniqueRFTCollection(collectionId, this.helper);1745  }17461747  /**1748   * Get token object1749   * @param collectionId ID of collection1750   * @param tokenId ID of token1751   * @example getTokenObject(10, 5);1752   * @returns instance of UniqueNFTToken1753   */1754  getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1755    return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1756  }17571758  /**1759   * Get top 10 token owners with the largest number of pieces1760   * @param collectionId ID of collection1761   * @param tokenId ID of token1762   * @example getTokenTop10Owners(10, 5);1763   * @returns array of top 10 owners1764   */1765  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1766    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1767  }17681769  /**1770   * Get number of pieces owned by address1771   * @param collectionId ID of collection1772   * @param tokenId ID of token1773   * @param addressObj address token owner1774   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1775   * @returns number of pieces ownerd by address1776   */1777  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1778    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1779  }17801781  /**1782   * Transfer pieces of token to another address1783   * @param signer keyring of signer1784   * @param collectionId ID of collection1785   * @param tokenId ID of token1786   * @param addressObj address of a new owner1787   * @param amount number of pieces to be transfered1788   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1789   * @returns ```true``` if extrinsic success, otherwise ```false```1790   */1791  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1792    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1793  }17941795  /**1796   * Change ownership of some pieces of RFT on behalf of the owner.1797   * @param signer keyring of signer1798   * @param collectionId ID of collection1799   * @param tokenId ID of token1800   * @param fromAddressObj address on behalf of which the token will be sent1801   * @param toAddressObj new token owner1802   * @param amount number of pieces to be transfered1803   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1804   * @returns ```true``` if extrinsic success, otherwise ```false```1805   */1806  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1807    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1808  }18091810  /**1811   * Mint new collection1812   * @param signer keyring of signer1813   * @param collectionOptions Collection options1814   * @example1815   * mintCollection(aliceKeyring, {1816   *   name: 'New',1817   *   description: 'New collection',1818   *   tokenPrefix: 'NEW',1819   * })1820   * @returns object of the created collection1821   */1822  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1823    return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1824  }18251826  /**1827   * Mint new token1828   * @param signer keyring of signer1829   * @param data token data1830   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1831   * @returns created token object1832   */1833  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1834    const creationResult = await this.helper.executeExtrinsic(1835      signer,1836      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1837        refungible: {1838          pieces: data.pieces,1839          properties: data.properties,1840        },1841      }],1842      true,1843    );1844    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1845    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1846    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1847    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1848  }18491850  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1851    throw Error('Not implemented');1852    const creationResult = await this.helper.executeExtrinsic(1853      signer,1854      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1855      true, // `Unable to mint RFT tokens for ${label}`,1856    );1857    const collection = this.getCollectionObject(collectionId);1858    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1859  }18601861  /**1862   * Mint multiple RFT tokens with one owner1863   * @param signer keyring of signer1864   * @param collectionId ID of collection1865   * @param owner tokens owner1866   * @param tokens array of tokens with properties and pieces1867   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1868   * @returns array of newly created RFT tokens1869   */1870  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1871    const rawTokens = [];1872    for (const token of tokens) {1873      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1874      rawTokens.push(raw);1875    }1876    const creationResult = await this.helper.executeExtrinsic(1877      signer,1878      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1879      true,1880    );1881    const collection = this.getCollectionObject(collectionId);1882    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1883  }18841885  /**1886   * Destroys a concrete instance of RFT.1887   * @param signer keyring of signer1888   * @param collectionId ID of collection1889   * @param tokenId ID of token1890   * @param amount number of pieces to be burnt1891   * @example burnToken(aliceKeyring, 10, 5);1892   * @returns ```true``` if the extrinsic is successful, otherwise ```false```1893   */1894  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1895    return await super.burnToken(signer, collectionId, tokenId, amount);1896  }18971898  /**1899   * Destroys a concrete instance of RFT on behalf of the owner.1900   * @param signer keyring of signer1901   * @param collectionId ID of collection1902   * @param tokenId ID of token1903   * @param fromAddressObj address on behalf of which the token will be burnt1904   * @param amount number of pieces to be burnt1905   * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1906   * @returns ```true``` if extrinsic success, otherwise ```false```1907   */1908  async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1909    return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1910  }19111912  /**1913   * Set, change, or remove approved address to transfer the ownership of the RFT.1914   *1915   * @param signer keyring of signer1916   * @param collectionId ID of collection1917   * @param tokenId ID of token1918   * @param toAddressObj address to approve1919   * @param amount number of pieces to be approved1920   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1921   * @returns true if the token success, otherwise false1922   */1923  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1924    return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1925  }19261927  /**1928   * Get total number of pieces1929   * @param collectionId ID of collection1930   * @param tokenId ID of token1931   * @example getTokenTotalPieces(10, 5);1932   * @returns number of pieces1933   */1934  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1935    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1936  }19371938  /**1939   * Change number of token pieces. Signer must be the owner of all token pieces.1940   * @param signer keyring of signer1941   * @param collectionId ID of collection1942   * @param tokenId ID of token1943   * @param amount new number of pieces1944   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1945   * @returns true if the repartion was success, otherwise false1946   */1947  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {1948    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1949    const repartitionResult = await this.helper.executeExtrinsic(1950      signer,1951      'api.tx.unique.repartition', [collectionId, tokenId, amount],1952      true,1953    );1954    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');1955    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');1956  }1957}195819591960class FTGroup extends CollectionGroup {1961  /**1962   * Get collection object1963   * @param collectionId ID of collection1964   * @example getCollectionObject(2);1965   * @returns instance of UniqueFTCollection1966   */1967  getCollectionObject(collectionId: number): UniqueFTCollection {1968    return new UniqueFTCollection(collectionId, this.helper);1969  }19701971  /**1972   * Mint new fungible collection1973   * @param signer keyring of signer1974   * @param collectionOptions Collection options1975   * @param decimalPoints number of token decimals1976   * @example1977   * mintCollection(aliceKeyring, {1978   *   name: 'New',1979   *   description: 'New collection',1980   *   tokenPrefix: 'NEW',1981   * }, 18)1982   * @returns newly created fungible collection1983   */1984  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1985    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1986    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1987    collectionOptions.mode = {fungible: decimalPoints};1988    for (const key of ['name', 'description', 'tokenPrefix']) {1989      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);1990    }1991    const creationResult = await this.helper.executeExtrinsic(1992      signer,1993      'api.tx.unique.createCollectionEx', [collectionOptions],1994      true,1995    );1996    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1997  }19981999  /**2000   * Mint tokens2001   * @param signer keyring of signer2002   * @param collectionId ID of collection2003   * @param owner address owner of new tokens2004   * @param amount amount of tokens to be meanted2005   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);2006   * @returns ```true``` if extrinsic success, otherwise ```false```2007   */2008  async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {2009    const creationResult = await this.helper.executeExtrinsic(2010      signer,2011      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {2012        fungible: {2013          value: amount,2014        },2015      }],2016      true, // `Unable to mint fungible tokens for ${label}`,2017    );2018    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2019  }20202021  /**2022   * Mint multiple Fungible tokens with one owner2023   * @param signer keyring of signer2024   * @param collectionId ID of collection2025   * @param owner tokens owner2026   * @param tokens array of tokens with properties and pieces2027   * @returns ```true``` if extrinsic success, otherwise ```false```2028   */2029  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: {value: bigint}[], owner: ICrossAccountId): Promise<boolean> {2030    const rawTokens = [];2031    for (const token of tokens) {2032      const raw = {Fungible: {Value: token.value}};2033      rawTokens.push(raw);2034    }2035    const creationResult = await this.helper.executeExtrinsic(2036      signer,2037      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2038      true,2039    );2040    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2041  }20422043  /**2044   * Get the top 10 owners with the largest balance for the Fungible collection2045   * @param collectionId ID of collection2046   * @example getTop10Owners(10);2047   * @returns array of ```ICrossAccountId```2048   */2049  async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2050    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2051  }20522053  /**2054   * Get account balance2055   * @param collectionId ID of collection2056   * @param addressObj address of owner2057   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2058   * @returns amount of fungible tokens owned by address2059   */2060  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2061    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2062  }20632064  /**2065   * Transfer tokens to address2066   * @param signer keyring of signer2067   * @param collectionId ID of collection2068   * @param toAddressObj address recipient2069   * @param amount amount of tokens to be sent2070   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2071   * @returns ```true``` if extrinsic success, otherwise ```false```2072   */2073  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2074    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2075  }20762077  /**2078   * Transfer some tokens on behalf of the owner.2079   * @param signer keyring of signer2080   * @param collectionId ID of collection2081   * @param fromAddressObj address on behalf of which tokens will be sent2082   * @param toAddressObj address where token to be sent2083   * @param amount number of tokens to be sent2084   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2085   * @returns ```true``` if extrinsic success, otherwise ```false```2086   */2087  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {2088    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2089  }20902091  /**2092   * Destroy some amount of tokens2093   * @param signer keyring of signer2094   * @param collectionId ID of collection2095   * @param amount amount of tokens to be destroyed2096   * @example burnTokens(aliceKeyring, 10, 1000n);2097   * @returns ```true``` if extrinsic success, otherwise ```false```2098   */2099  async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise<boolean> {2100    return await super.burnToken(signer, collectionId, 0, amount);2101  }21022103  /**2104   * Burn some tokens on behalf of the owner.2105   * @param signer keyring of signer2106   * @param collectionId ID of collection2107   * @param fromAddressObj address on behalf of which tokens will be burnt2108   * @param amount amount of tokens to be burnt2109   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2110   * @returns ```true``` if extrinsic success, otherwise ```false```2111   */2112  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {2113    return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2114  }21152116  /**2117   * Get total collection supply2118   * @param collectionId2119   * @returns2120   */2121  async getTotalPieces(collectionId: number): Promise<bigint> {2122    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2123  }21242125  /**2126   * Set, change, or remove approved address to transfer tokens.2127   *2128   * @param signer keyring of signer2129   * @param collectionId ID of collection2130   * @param toAddressObj address to be approved2131   * @param amount amount of tokens to be approved2132   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2133   * @returns ```true``` if extrinsic success, otherwise ```false```2134   */2135  approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2136    return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2137  }21382139  /**2140   * Get amount of fungible tokens approved to transfer2141   * @param collectionId ID of collection2142   * @param fromAddressObj owner of tokens2143   * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2144   * @returns number of tokens approved for the transfer2145   */2146  getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2147    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2148  }2149}215021512152class ChainGroup extends HelperGroup<ChainHelperBase> {2153  /**2154   * Get system properties of a chain2155   * @example getChainProperties();2156   * @returns ss58Format, token decimals, and token symbol2157   */2158  getChainProperties(): IChainProperties {2159    const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2160    return {2161      ss58Format: properties.ss58Format.toJSON(),2162      tokenDecimals: properties.tokenDecimals.toJSON(),2163      tokenSymbol: properties.tokenSymbol.toJSON(),2164    };2165  }21662167  /**2168   * Get chain header2169   * @example getLatestBlockNumber();2170   * @returns the number of the last block2171   */2172  async getLatestBlockNumber(): Promise<number> {2173    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2174  }21752176  /**2177   * Get block hash by block number2178   * @param blockNumber number of block2179   * @example getBlockHashByNumber(12345);2180   * @returns hash of a block2181   */2182  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2183    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2184    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2185    return blockHash;2186  }21872188  // TODO add docs2189  async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2190    const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2191    if (!blockHash) return null;2192    return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2193  }21942195  /**2196   * Get account nonce2197   * @param address substrate address2198   * @example getNonce("5GrwvaEF5zXb26Fz...");2199   * @returns number, account's nonce2200   */2201  async getNonce(address: TSubstrateAccount): Promise<number> {2202    return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2203  }2204}22052206class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2207  /**2208 * Get substrate address balance2209 * @param address substrate address2210 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2211 * @returns amount of tokens on address2212 */2213  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2214    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2215  }22162217  /**2218   * Transfer tokens to substrate address2219   * @param signer keyring of signer2220   * @param address substrate address of a recipient2221   * @param amount amount of tokens to be transfered2222   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2223   * @returns ```true``` if extrinsic success, otherwise ```false```2224   */2225  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2226    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}`*/);22272228    let transfer = {from: null, to: null, amount: 0n} as any;2229    result.result.events.forEach(({event: {data, method, section}}) => {2230      if ((section === 'balances') && (method === 'Transfer')) {2231        transfer = {2232          from: this.helper.address.normalizeSubstrate(data[0]),2233          to: this.helper.address.normalizeSubstrate(data[1]),2234          amount: BigInt(data[2]),2235        };2236      }2237    });2238    const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2239      && this.helper.address.normalizeSubstrate(address) === transfer.to2240      && BigInt(amount) === transfer.amount;2241    return isSuccess;2242  }22432244  /**2245   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2246   * @param address substrate address2247   * @returns2248   */2249  async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2250    const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2251    return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2252  }2253}22542255class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2256  /**2257   * Get ethereum address balance2258   * @param address ethereum address2259   * @example getEthereum("0x9F0583DbB855d...")2260   * @returns amount of tokens on address2261   */2262  async getEthereum(address: TEthereumAccount): Promise<bigint> {2263    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2264  }22652266  /**2267   * Transfer tokens to address2268   * @param signer keyring of signer2269   * @param address Ethereum address of a recipient2270   * @param amount amount of tokens to be transfered2271   * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2272   * @returns ```true``` if extrinsic success, otherwise ```false```2273   */2274  async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2275    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);22762277    let transfer = {from: null, to: null, amount: 0n} as any;2278    result.result.events.forEach(({event: {data, method, section}}) => {2279      if ((section === 'balances') && (method === 'Transfer')) {2280        transfer = {2281          from: data[0].toString(),2282          to: data[1].toString(),2283          amount: BigInt(data[2]),2284        };2285      }2286    });2287    const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2288      && address === transfer.to2289      && BigInt(amount) === transfer.amount;2290    return isSuccess;2291  }2292}22932294class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2295  subBalanceGroup: SubstrateBalanceGroup<T>;2296  ethBalanceGroup: EthereumBalanceGroup<T>;22972298  constructor(helper: T) {2299    super(helper);2300    this.subBalanceGroup = new SubstrateBalanceGroup(helper);2301    this.ethBalanceGroup = new EthereumBalanceGroup(helper);2302  }23032304  getCollectionCreationPrice(): bigint {2305    return 2n * this.getOneTokenNominal();2306  }2307  /**2308   * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2309   * @example getOneTokenNominal()2310   * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2311   */2312  getOneTokenNominal(): bigint {2313    const chainProperties = this.helper.chain.getChainProperties();2314    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2315  }23162317  /**2318   * Get substrate address balance2319   * @param address substrate address2320   * @example getSubstrate("5GrwvaEF5zXb26Fz...")2321   * @returns amount of tokens on address2322   */2323  getSubstrate(address: TSubstrateAccount): Promise<bigint> {2324    return this.subBalanceGroup.getSubstrate(address);2325  }23262327  /**2328   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2329   * @param address substrate address2330   * @returns2331   */2332  getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2333    return this.subBalanceGroup.getSubstrateFull(address);2334  }23352336  /**2337   * Get ethereum address balance2338   * @param address ethereum address2339   * @example getEthereum("0x9F0583DbB855d...")2340   * @returns amount of tokens on address2341   */2342  getEthereum(address: TEthereumAccount): Promise<bigint> {2343    return this.ethBalanceGroup.getEthereum(address);2344  }23452346  /**2347   * Transfer tokens to substrate address2348   * @param signer keyring of signer2349   * @param address substrate address of a recipient2350   * @param amount amount of tokens to be transfered2351   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2352   * @returns ```true``` if extrinsic success, otherwise ```false```2353   */2354  transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2355    return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2356  }23572358  async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2359    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);23602361    let transfer = {from: null, to: null, amount: 0n} as any;2362    result.result.events.forEach(({event: {data, method, section}}) => {2363      if ((section === 'balances') && (method === 'Transfer')) {2364        transfer = {2365          from: this.helper.address.normalizeSubstrate(data[0]),2366          to: this.helper.address.normalizeSubstrate(data[1]),2367          amount: BigInt(data[2]),2368        };2369      }2370    });2371    let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2372    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2373    isSuccess = isSuccess && BigInt(amount) === transfer.amount;2374    return isSuccess;2375  }2376}23772378class AddressGroup extends HelperGroup<ChainHelperBase> {2379  /**2380   * Normalizes the address to the specified ss58 format, by default ```42```.2381   * @param address substrate address2382   * @param ss58Format format for address conversion, by default ```42```2383   * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2384   * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2385   */2386  normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2387    return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2388  }23892390  /**2391   * Get address in the connected chain format2392   * @param address substrate address2393   * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2394   * @returns address in chain format2395   */2396  normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2397    return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2398  }23992400  /**2401   * Get substrate mirror of an ethereum address2402   * @param ethAddress ethereum address2403   * @param toChainFormat false for normalized account2404   * @example ethToSubstrate('0x9F0583DbB855d...')2405   * @returns substrate mirror of a provided ethereum address2406   */2407  ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): TSubstrateAccount {2408    return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2409  }24102411  /**2412   * Get ethereum mirror of a substrate address2413   * @param subAddress substrate account2414   * @example substrateToEth("5DnSF6RRjwteE3BrC...")2415   * @returns ethereum mirror of a provided substrate address2416   */2417  substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2418    return CrossAccountId.translateSubToEth(subAddress);2419  }24202421  /**2422   * Encode key to substrate address2423   * @param key key for encoding address2424   * @param ss58Format prefix for encoding to the address of the corresponding network2425   * @returns encoded substrate address2426   */2427  encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format = 42): string {2428    const u8a :Uint8Array = typeof key === 'string'2429      ? hexToU8a(key)2430      : typeof key === 'bigint'2431        ? hexToU8a(key.toString(16))2432        : key;2433  2434    if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2435      throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`);2436    }2437  2438    const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2439    if (!allowedDecodedLengths.includes(u8a.length)) {2440      throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2441    }2442  2443    const u8aPrefix = ss58Format < 642444      ? new Uint8Array([ss58Format])2445      : new Uint8Array([2446        ((ss58Format & 0xfc) >> 2) | 0x40,2447        (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2448      ]);24492450    const input = u8aConcat(u8aPrefix, u8a);2451  2452    return base58Encode(u8aConcat(2453      input,2454      blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2455    ));2456  }24572458  /**2459   * Restore substrate address from bigint representation2460   * @param number decimal representation of substrate address2461   * @returns substrate address2462   */2463  restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2464    if (this.helper.api === null) {2465      throw 'Not connected';2466    }2467    const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2468    if (res === undefined || res === null) {2469      throw 'Restore address error';2470    }2471    return res.toString();2472  }24732474  /**2475   * Convert etherium cross account id to substrate cross account id2476   * @param ethCrossAccount etherium cross account2477   * @returns substrate cross account id2478   */2479  convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2480    if (ethCrossAccount.sub === '0') {2481      return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2482    }2483    2484    const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2485    return {Substrate: ss58};2486  }24872488  paraSiblingSovereignAccount(paraid: number) {2489    // We are getting a *sibling* parachain sovereign account,2490    // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2491    const siblingPrefix = '0x7369626c';24922493    const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2494    const suffix = '000000000000000000000000000000000000000000000000';24952496    return siblingPrefix + encodedParaId + suffix;2497  }2498}24992500class StakingGroup extends HelperGroup<UniqueHelper> {2501  /**2502   * Stake tokens for App Promotion2503   * @param signer keyring of signer2504   * @param amountToStake amount of tokens to stake2505   * @param label extra label for log2506   * @returns2507   */2508  async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2509    if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2510    const _stakeResult = await this.helper.executeExtrinsic(2511      signer, 'api.tx.appPromotion.stake',2512      [amountToStake], true,2513    );2514    // TODO extract info from stakeResult2515    return true;2516  }25172518  /**2519   * Unstake tokens for App Promotion2520   * @param signer keyring of signer2521   * @param amountToUnstake amount of tokens to unstake2522   * @param label extra label for log2523   * @returns block number where balances will be unlocked2524   */2525  async unstake(signer: TSigner, label?: string): Promise<number> {2526    if(typeof label === 'undefined') label = `${signer.address}`;2527    const _unstakeResult = await this.helper.executeExtrinsic(2528      signer, 'api.tx.appPromotion.unstake',2529      [], true,2530    );2531    // TODO extract block number fron events2532    return 1;2533  }25342535  /**2536   * Get total staked amount for address2537   * @param address substrate or ethereum address2538   * @returns total staked amount2539   */2540  async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2541    if (address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2542    return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2543  }25442545  /**2546   * Get total staked per block2547   * @param address substrate or ethereum address2548   * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2549   */2550  async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2551    const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2552    return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => {2553      return {2554        block: block.toBigInt(),2555        amount: amount.toBigInt(),2556      };2557    });2558  }25592560  /**2561   * Get total pending unstake amount for address2562   * @param address substrate or ethereum address2563   * @returns total pending unstake amount2564   */2565  async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2566    return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2567  }25682569  /**2570   * Get pending unstake amount per block for address2571   * @param address substrate or ethereum address2572   * @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 block2573   */2574  async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2575    const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2576    const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => {2577      return {2578        block: block.toBigInt(),2579        amount: amount.toBigInt(),2580      };2581    });2582    return result;2583  }2584}25852586class SchedulerGroup extends HelperGroup<UniqueHelper> {2587  constructor(helper: UniqueHelper) {2588    super(helper);2589  }25902591  cancelScheduled(signer: TSigner, scheduledId: string) {2592    return this.helper.executeExtrinsic(2593      signer,2594      'api.tx.scheduler.cancelNamed',2595      [scheduledId],2596      true,2597    );2598  }25992600  changePriority(signer: TSigner, scheduledId: string, priority: number) {2601    return this.helper.executeExtrinsic(2602      signer,2603      'api.tx.scheduler.changeNamedPriority',2604      [scheduledId, priority],2605      true,2606    );2607  }26082609  scheduleAt<T extends UniqueHelper>(2610    executionBlockNumber: number,2611    options: ISchedulerOptions = {},2612  ) {2613    return this.schedule<T>('schedule', executionBlockNumber, options);2614  }26152616  scheduleAfter<T extends UniqueHelper>(2617    blocksBeforeExecution: number,2618    options: ISchedulerOptions = {},2619  ) {2620    return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2621  }26222623  schedule<T extends UniqueHelper>(2624    scheduleFn: 'schedule' | 'scheduleAfter',2625    blocksNum: number,2626    options: ISchedulerOptions = {},2627  ) {2628    // eslint-disable-next-line @typescript-eslint/naming-convention2629    const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2630    return this.helper.clone(ScheduledHelperType, {2631      scheduleFn,2632      blocksNum,2633      options,2634    }) as T;2635  }2636}26372638class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {2639  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2640    await this.helper.executeExtrinsic(2641      signer,2642      'api.tx.foreignAssets.registerForeignAsset',2643      [ownerAddress, location, metadata],2644      true,2645    );2646  }26472648  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {2649    await this.helper.executeExtrinsic(2650      signer,2651      'api.tx.foreignAssets.updateForeignAsset',2652      [foreignAssetId, location, metadata],2653      true,2654    );2655  }2656}26572658class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {2659  palletName: string;26602661  constructor(helper: T, palletName: string) {2662    super(helper);26632664    this.palletName = palletName;2665  }26662667  async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {2668    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);2669  }2670}26712672class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2673  async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {2674    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);2675  }26762677  async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {2678    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);2679  }26802681  async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {2682    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);2683  }2684}26852686class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2687  async accounts(address: string, currencyId: any) {2688    const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;2689    return BigInt(free);2690  }2691}26922693class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {2694  async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {2695    await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);2696  }26972698  async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {2699    await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);2700  }27012702  async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {2703    await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);2704  }27052706  async account(assetId: string | number, address: string) {2707    const accountAsset = (2708      await this.helper.callRpc('api.query.assets.account', [assetId, address])2709    ).toJSON()! as any;27102711    if (accountAsset !== null) {2712      return BigInt(accountAsset['balance']);2713    } else {2714      return null;2715    }2716  }2717}27182719class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {2720  async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {2721    await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);2722  }2723}27242725class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {2726  makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {2727    const apiPrefix = 'api.tx.assetManager.';27282729    const registerTx = this.helper.constructApiCall(2730      apiPrefix + 'registerForeignAsset',2731      [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],2732    );27332734    const setUnitsTx = this.helper.constructApiCall(2735      apiPrefix + 'setAssetUnitsPerSecond',2736      [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],2737    );27382739    const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);2740    const encodedProposal = batchCall?.method.toHex() || '';2741    return encodedProposal;2742  }27432744  async assetTypeId(location: any) {2745    return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);2746  }2747}27482749class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {2750  async notePreimage(signer: TSigner, encodedProposal: string) {2751    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);2752  }27532754  externalProposeMajority(proposalHash: string) {2755    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);2756  }27572758  fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {2759    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);2760  }27612762  async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {2763    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);2764  }2765}27662767class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {2768  collective: string;27692770  constructor(helper: MoonbeamHelper, collective: string) {2771    super(helper);27722773    this.collective = collective;2774  }27752776  async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {2777    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);2778  }27792780  async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {2781    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);2782  }27832784  async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {2785    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);2786  }27872788  async proposalCount() {2789    return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));2790  }2791}27922793export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;2794export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;27952796export class UniqueHelper extends ChainHelperBase {2797  balance: BalanceGroup<UniqueHelper>;2798  collection: CollectionGroup;2799  nft: NFTGroup;2800  rft: RFTGroup;2801  ft: FTGroup;2802  staking: StakingGroup;2803  scheduler: SchedulerGroup;2804  foreignAssets: ForeignAssetsGroup;2805  xcm: XcmGroup<UniqueHelper>;2806  xTokens: XTokensGroup<UniqueHelper>;2807  tokens: TokensGroup<UniqueHelper>;28082809  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2810    super(logger, options.helperBase ?? UniqueHelper);28112812    this.balance = new BalanceGroup(this);2813    this.collection = new CollectionGroup(this);2814    this.nft = new NFTGroup(this);2815    this.rft = new RFTGroup(this);2816    this.ft = new FTGroup(this);2817    this.staking = new StakingGroup(this);2818    this.scheduler = new SchedulerGroup(this);2819    this.foreignAssets = new ForeignAssetsGroup(this);2820    this.xcm = new XcmGroup(this, 'polkadotXcm');2821    this.xTokens = new XTokensGroup(this);2822    this.tokens = new TokensGroup(this);2823  }28242825  getSudo<T extends UniqueHelper>() {2826    // eslint-disable-next-line @typescript-eslint/naming-convention2827    const SudoHelperType = SudoHelper(this.helperBase);2828    return this.clone(SudoHelperType) as T;2829  }2830}28312832export class XcmChainHelper extends ChainHelperBase {2833  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {2834    const wsProvider = new WsProvider(wsEndpoint);2835    this.api = new ApiPromise({2836      provider: wsProvider,2837    });2838    await this.api.isReadyOrError;2839    this.network = await UniqueHelper.detectNetwork(this.api);2840  }2841}28422843export class RelayHelper extends XcmChainHelper {2844  xcm: XcmGroup<RelayHelper>;28452846  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2847    super(logger, options.helperBase ?? RelayHelper);28482849    this.xcm = new XcmGroup(this, 'xcmPallet');2850  }2851}28522853export class WestmintHelper extends XcmChainHelper {2854  balance: SubstrateBalanceGroup<WestmintHelper>;2855  xcm: XcmGroup<WestmintHelper>;2856  assets: AssetsGroup<WestmintHelper>;2857  xTokens: XTokensGroup<WestmintHelper>;28582859  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2860    super(logger, options.helperBase ?? WestmintHelper);28612862    this.balance = new SubstrateBalanceGroup(this);2863    this.xcm = new XcmGroup(this, 'polkadotXcm');2864    this.assets = new AssetsGroup(this);2865    this.xTokens = new XTokensGroup(this);2866  }2867}28682869export class MoonbeamHelper extends XcmChainHelper {2870  balance: EthereumBalanceGroup<MoonbeamHelper>;2871  assetManager: MoonbeamAssetManagerGroup;2872  assets: AssetsGroup<MoonbeamHelper>;2873  xTokens: XTokensGroup<MoonbeamHelper>;2874  democracy: MoonbeamDemocracyGroup;2875  collective: {2876    council: MoonbeamCollectiveGroup,2877    techCommittee: MoonbeamCollectiveGroup,2878  };28792880  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2881    super(logger, options.helperBase ?? MoonbeamHelper);28822883    this.balance = new EthereumBalanceGroup(this);2884    this.assetManager = new MoonbeamAssetManagerGroup(this);2885    this.assets = new AssetsGroup(this);2886    this.xTokens = new XTokensGroup(this);2887    this.democracy = new MoonbeamDemocracyGroup(this);2888    this.collective = {2889      council: new MoonbeamCollectiveGroup(this, 'councilCollective'),2890      techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),2891    };2892  }2893}28942895export class AcalaHelper extends XcmChainHelper {2896  balance: SubstrateBalanceGroup<AcalaHelper>;2897  assetRegistry: AcalaAssetRegistryGroup;2898  xTokens: XTokensGroup<AcalaHelper>;2899  tokens: TokensGroup<AcalaHelper>;29002901  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2902    super(logger, options.helperBase ?? AcalaHelper);29032904    this.balance = new SubstrateBalanceGroup(this);2905    this.assetRegistry = new AcalaAssetRegistryGroup(this);2906    this.xTokens = new XTokensGroup(this);2907    this.tokens = new TokensGroup(this);2908  }29092910  getSudo<T extends AcalaHelper>() {2911    // eslint-disable-next-line @typescript-eslint/naming-convention2912    const SudoHelperType = SudoHelper(this.helperBase);2913    return this.clone(SudoHelperType) as T;2914  }2915}29162917// eslint-disable-next-line @typescript-eslint/naming-convention2918function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2919  return class extends Base {2920    scheduleFn: 'schedule' | 'scheduleAfter';2921    blocksNum: number;2922    options: ISchedulerOptions;29232924    constructor(...args: any[]) {2925      const logger = args[0] as ILogger;2926      const options = args[1] as {2927        scheduleFn: 'schedule' | 'scheduleAfter',2928        blocksNum: number,2929        options: ISchedulerOptions2930      };29312932      super(logger);29332934      this.scheduleFn = options.scheduleFn;2935      this.blocksNum = options.blocksNum;2936      this.options = options.options;2937    }29382939    executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {2940      const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);2941      2942      const mandatorySchedArgs = [2943        this.blocksNum,2944        this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,2945        this.options.priority ?? null,2946        scheduledTx,2947      ];2948      2949      let schedArgs;2950      let scheduleFn;29512952      if (this.options.scheduledId) {2953        schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];29542955        if (this.scheduleFn == 'schedule') {2956          scheduleFn = 'scheduleNamed';2957        } else if (this.scheduleFn == 'scheduleAfter') {2958          scheduleFn = 'scheduleNamedAfter';2959        }2960      } else {2961        schedArgs = mandatorySchedArgs;2962        scheduleFn = this.scheduleFn;2963      }29642965      const extrinsic = 'api.tx.scheduler.' +  scheduleFn;29662967      return super.executeExtrinsic(2968        sender,2969        extrinsic,2970        schedArgs,2971        expectSuccess,2972      );2973    }2974  };2975}29762977// eslint-disable-next-line @typescript-eslint/naming-convention2978function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {2979  return class extends Base {2980    constructor(...args: any[]) {2981      super(...args);2982    }29832984    executeExtrinsic (2985      sender: IKeyringPair,2986      extrinsic: string,2987      params: any[],2988      expectSuccess?: boolean,2989    ): Promise<ITransactionResult> {2990      const call = this.constructApiCall(extrinsic, params);2991      return super.executeExtrinsic(2992        sender,2993        'api.tx.sudo.sudo',2994        [call],2995        expectSuccess,2996      );2997    }2998  };2999}30003001export class UniqueBaseCollection {3002  helper: UniqueHelper;3003  collectionId: number;30043005  constructor(collectionId: number, uniqueHelper: UniqueHelper) {3006    this.collectionId = collectionId;3007    this.helper = uniqueHelper;3008  }30093010  async getData() {3011    return await this.helper.collection.getData(this.collectionId);3012  }30133014  async getLastTokenId() {3015    return await this.helper.collection.getLastTokenId(this.collectionId);3016  }30173018  async doesTokenExist(tokenId: number) {3019    return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);3020  }30213022  async getAdmins() {3023    return await this.helper.collection.getAdmins(this.collectionId);3024  }30253026  async getAllowList() {3027    return await this.helper.collection.getAllowList(this.collectionId);3028  }30293030  async getEffectiveLimits() {3031    return await this.helper.collection.getEffectiveLimits(this.collectionId);3032  }30333034  async getProperties(propertyKeys?: string[] | null) {3035    return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3036  }30373038  async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3039    return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3040  }30413042  async getOptions() {3043    return await this.helper.collection.getCollectionOptions(this.collectionId);3044  }30453046  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {3047    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);3048  }30493050  async confirmSponsorship(signer: TSigner) {3051    return await this.helper.collection.confirmSponsorship(signer, this.collectionId);3052  }30533054  async removeSponsor(signer: TSigner) {3055    return await this.helper.collection.removeSponsor(signer, this.collectionId);3056  }30573058  async setLimits(signer: TSigner, limits: ICollectionLimits) {3059    return await this.helper.collection.setLimits(signer, this.collectionId, limits);3060  }30613062  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {3063    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);3064  }30653066  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3067    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);3068  }30693070  async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {3071    return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);3072  }30733074  async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {3075    return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);3076  }30773078  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3079    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);3080  }30813082  async setProperties(signer: TSigner, properties: IProperty[]) {3083    return await this.helper.collection.setProperties(signer, this.collectionId, properties);3084  }30853086  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3087    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);3088  }30893090  async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {3091    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);3092  }30933094  async enableNesting(signer: TSigner, permissions: INestingPermissions) {3095    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);3096  }30973098  async disableNesting(signer: TSigner) {3099    return await this.helper.collection.disableNesting(signer, this.collectionId);3100  }31013102  async burn(signer: TSigner) {3103    return await this.helper.collection.burn(signer, this.collectionId);3104  }31053106  scheduleAt<T extends UniqueHelper>(3107    executionBlockNumber: number,3108    options: ISchedulerOptions = {},3109  ) {3110    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3111    return new UniqueBaseCollection(this.collectionId, scheduledHelper);3112  }31133114  scheduleAfter<T extends UniqueHelper>(3115    blocksBeforeExecution: number,3116    options: ISchedulerOptions = {},3117  ) {3118    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3119    return new UniqueBaseCollection(this.collectionId, scheduledHelper);3120  }31213122  getSudo<T extends UniqueHelper>() {3123    return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());3124  }3125}312631273128export class UniqueNFTCollection extends UniqueBaseCollection {3129  getTokenObject(tokenId: number) {3130    return new UniqueNFToken(tokenId, this);3131  }31323133  async getTokensByAddress(addressObj: ICrossAccountId) {3134    return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);3135  }31363137  async getToken(tokenId: number, blockHashAt?: string) {3138    return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);3139  }31403141  async getTokenOwner(tokenId: number, blockHashAt?: string) {3142    return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3143  }31443145  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3146    return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3147  }31483149  async getTokenChildren(tokenId: number, blockHashAt?: string) {3150    return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);3151  }31523153  async getPropertyPermissions(propertyKeys: string[] | null = null) {3154    return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);3155  }31563157  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3158    return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3159  }31603161  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {3162    return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);3163  }31643165  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3166    return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);3167  }31683169  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {3170    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);3171  }31723173  async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {3174    return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);3175  }31763177  async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3178    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});3179  }31803181  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) {3182    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);3183  }31843185  async burnToken(signer: TSigner, tokenId: number) {3186    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);3187  }31883189  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {3190    return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);3191  }31923193  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3194    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);3195  }31963197  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3198    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3199  }32003201  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3202    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3203  }32043205  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3206    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3207  }32083209  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3210    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3211  }32123213  scheduleAt<T extends UniqueHelper>(3214    executionBlockNumber: number,3215    options: ISchedulerOptions = {},3216  ) {3217    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3218    return new UniqueNFTCollection(this.collectionId, scheduledHelper);3219  }32203221  scheduleAfter<T extends UniqueHelper>(3222    blocksBeforeExecution: number,3223    options: ISchedulerOptions = {},3224  ) {3225    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3226    return new UniqueNFTCollection(this.collectionId, scheduledHelper);3227  }32283229  getSudo<T extends UniqueHelper>() {3230    return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());3231  }3232}323332343235export class UniqueRFTCollection extends UniqueBaseCollection {3236  getTokenObject(tokenId: number) {3237    return new UniqueRFToken(tokenId, this);3238  }32393240  async getToken(tokenId: number, blockHashAt?: string) {3241    return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3242  }32433244  async getTokensByAddress(addressObj: ICrossAccountId) {3245    return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3246  }32473248  async getTop10TokenOwners(tokenId: number) {3249    return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3250  }32513252  async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3253    return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3254  }32553256  async getTokenTotalPieces(tokenId: number) {3257    return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3258  }32593260  async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3261    return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3262  }32633264  async getPropertyPermissions(propertyKeys: string[] | null = null) {3265    return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3266  }32673268  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3269    return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3270  }32713272  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {3273    return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3274  }32753276  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3277    return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3278  }32793280  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {3281    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3282  }32833284  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3285    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3286  }32873288  async mintToken(signer: TSigner, pieces=1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3289    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3290  }32913292  async mintMultipleTokens(signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) {3293    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3294  }32953296  async burnToken(signer: TSigner, tokenId: number, amount=1n) {3297    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3298  }32993300  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId,  amount=1n) {3301    return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3302  }33033304  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3305    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3306  }33073308  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3309    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3310  }33113312  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3313    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3314  }33153316  scheduleAt<T extends UniqueHelper>(3317    executionBlockNumber: number,3318    options: ISchedulerOptions = {},3319  ) {3320    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3321    return new UniqueRFTCollection(this.collectionId, scheduledHelper);3322  }33233324  scheduleAfter<T extends UniqueHelper>(3325    blocksBeforeExecution: number,3326    options: ISchedulerOptions = {},3327  ) {3328    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3329    return new UniqueRFTCollection(this.collectionId, scheduledHelper);3330  }33313332  getSudo<T extends UniqueHelper>() {3333    return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());3334  }3335}333633373338export class UniqueFTCollection extends UniqueBaseCollection {3339  async getBalance(addressObj: ICrossAccountId) {3340    return await this.helper.ft.getBalance(this.collectionId, addressObj);3341  }33423343  async getTotalPieces() {3344    return await this.helper.ft.getTotalPieces(this.collectionId);3345  }33463347  async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3348    return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3349  }33503351  async getTop10Owners() {3352    return await this.helper.ft.getTop10Owners(this.collectionId);3353  }33543355  async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {3356    return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3357  }33583359  async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {3360    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3361  }33623363  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3364    return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3365  }33663367  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3368    return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3369  }33703371  async burnTokens(signer: TSigner, amount=1n) {3372    return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3373  }33743375  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3376    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3377  }33783379  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3380    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3381  }33823383  scheduleAt<T extends UniqueHelper>(3384    executionBlockNumber: number,3385    options: ISchedulerOptions = {},3386  ) {3387    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3388    return new UniqueFTCollection(this.collectionId, scheduledHelper);3389  }33903391  scheduleAfter<T extends UniqueHelper>(3392    blocksBeforeExecution: number,3393    options: ISchedulerOptions = {},3394  ) {3395    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3396    return new UniqueFTCollection(this.collectionId, scheduledHelper);3397  }33983399  getSudo<T extends UniqueHelper>() {3400    return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());3401  }3402}340334043405export class UniqueBaseToken {3406  collection: UniqueNFTCollection | UniqueRFTCollection;3407  collectionId: number;3408  tokenId: number;34093410  constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3411    this.collection = collection;3412    this.collectionId = collection.collectionId;3413    this.tokenId = tokenId;3414  }34153416  async getNextSponsored(addressObj: ICrossAccountId) {3417    return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3418  }34193420  async getProperties(propertyKeys?: string[] | null) {3421    return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3422  }34233424  async setProperties(signer: TSigner, properties: IProperty[]) {3425    return await this.collection.setTokenProperties(signer, this.tokenId, properties);3426  }34273428  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3429    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3430  }34313432  async doesExist() {3433    return await this.collection.doesTokenExist(this.tokenId);3434  }34353436  nestingAccount() {3437    return this.collection.helper.util.getTokenAccount(this);3438  }34393440  scheduleAt<T extends UniqueHelper>(3441    executionBlockNumber: number,3442    options: ISchedulerOptions = {},3443  ) {3444    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3445    return new UniqueBaseToken(this.tokenId, scheduledCollection);3446  }34473448  scheduleAfter<T extends UniqueHelper>(3449    blocksBeforeExecution: number,3450    options: ISchedulerOptions = {},3451  ) {3452    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3453    return new UniqueBaseToken(this.tokenId, scheduledCollection);3454  }34553456  getSudo<T extends UniqueHelper>() {3457    return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());3458  }3459}346034613462export class UniqueNFToken extends UniqueBaseToken {3463  collection: UniqueNFTCollection;34643465  constructor(tokenId: number, collection: UniqueNFTCollection) {3466    super(tokenId, collection);3467    this.collection = collection;3468  }34693470  async getData(blockHashAt?: string) {3471    return await this.collection.getToken(this.tokenId, blockHashAt);3472  }34733474  async getOwner(blockHashAt?: string) {3475    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3476  }34773478  async getTopmostOwner(blockHashAt?: string) {3479    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3480  }34813482  async getChildren(blockHashAt?: string) {3483    return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3484  }34853486  async nest(signer: TSigner, toTokenObj: IToken) {3487    return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3488  }34893490  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3491    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3492  }34933494  async transfer(signer: TSigner, addressObj: ICrossAccountId) {3495    return await this.collection.transferToken(signer, this.tokenId, addressObj);3496  }34973498  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3499    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3500  }35013502  async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3503    return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3504  }35053506  async isApproved(toAddressObj: ICrossAccountId) {3507    return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3508  }35093510  async burn(signer: TSigner) {3511    return await this.collection.burnToken(signer, this.tokenId);3512  }35133514  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3515    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3516  }35173518  scheduleAt<T extends UniqueHelper>(3519    executionBlockNumber: number,3520    options: ISchedulerOptions = {},3521  ) {3522    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3523    return new UniqueNFToken(this.tokenId, scheduledCollection);3524  }35253526  scheduleAfter<T extends UniqueHelper>(3527    blocksBeforeExecution: number,3528    options: ISchedulerOptions = {},3529  ) {3530    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3531    return new UniqueNFToken(this.tokenId, scheduledCollection);3532  }35333534  getSudo<T extends UniqueHelper>() {3535    return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());3536  }3537}35383539export class UniqueRFToken extends UniqueBaseToken {3540  collection: UniqueRFTCollection;35413542  constructor(tokenId: number, collection: UniqueRFTCollection) {3543    super(tokenId, collection);3544    this.collection = collection;3545  }35463547  async getData(blockHashAt?: string) {3548    return await this.collection.getToken(this.tokenId, blockHashAt);3549  }35503551  async getTop10Owners() {3552    return await this.collection.getTop10TokenOwners(this.tokenId);3553  }35543555  async getBalance(addressObj: ICrossAccountId) {3556    return await this.collection.getTokenBalance(this.tokenId, addressObj);3557  }35583559  async getTotalPieces() {3560    return await this.collection.getTokenTotalPieces(this.tokenId);3561  }35623563  async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3564    return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3565  }35663567  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {3568    return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3569  }35703571  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3572    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3573  }35743575  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3576    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3577  }35783579  async repartition(signer: TSigner, amount: bigint) {3580    return await this.collection.repartitionToken(signer, this.tokenId, amount);3581  }35823583  async burn(signer: TSigner, amount=1n) {3584    return await this.collection.burnToken(signer, this.tokenId, amount);3585  }35863587  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3588    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3589  }35903591  scheduleAt<T extends UniqueHelper>(3592    executionBlockNumber: number,3593    options: ISchedulerOptions = {},3594  ) {3595    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3596    return new UniqueRFToken(this.tokenId, scheduledCollection);3597  }35983599  scheduleAfter<T extends UniqueHelper>(3600    blocksBeforeExecution: number,3601    options: ISchedulerOptions = {},3602  ) {3603    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3604    return new UniqueRFToken(this.tokenId, scheduledCollection);3605  }36063607  getSudo<T extends UniqueHelper>() {3608    return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());3609  }3610}