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

difftreelog

source

tests/src/util/playgrounds/unique.ts134.9 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';4647export class CrossAccountId implements ICrossAccountId {48  Substrate?: TSubstrateAccount;49  Ethereum?: TEthereumAccount;5051  constructor(account: ICrossAccountId) {52    if (account.Substrate) this.Substrate = account.Substrate;53    if (account.Ethereum) this.Ethereum = account.Ethereum;54  }5556  static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {57    switch (domain) {58      case 'Substrate': return new CrossAccountId({Substrate: account.address});59      case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();60    }61  }6263  static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {64    return new CrossAccountId({Substrate: address.substrate, Ethereum: address.ethereum});65  }6667  static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {68    return encodeAddress(decodeAddress(address), ss58Format);69  }7071  static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {72    return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});73  }7475  withNormalizedSubstrate(ss58Format = 42): CrossAccountId {76    if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);77    return this;78  }7980  static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {81    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));82  }8384  toEthereum(): CrossAccountId {85    if (this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});86    return this;87  }8889  static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {90    return evmToAddress(address, ss58Format);91  }9293  toSubstrate(ss58Format?: number): CrossAccountId {94    if (this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});95    return this;96  }9798  toLowerCase(): CrossAccountId {99    if (this.Substrate) this.Substrate = this.Substrate.toLowerCase();100    if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();101    return this;102  }103}104105const nesting = {106  toChecksumAddress(address: string): string {107    if (typeof address === 'undefined') return '';108109    if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);110111    address = address.toLowerCase().replace(/^0x/i,'');112    const addressHash = keccakAsHex(address).replace(/^0x/i,'');113    const checksumAddress = ['0x'];114115    for (let i = 0; i < address.length; i++) {116      // If ith character is 8 to f then make it uppercase117      if (parseInt(addressHash[i], 16) > 7) {118        checksumAddress.push(address[i].toUpperCase());119      } else {120        checksumAddress.push(address[i]);121      }122    }123    return checksumAddress.join('');124  },125  tokenIdToAddress(collectionId: number, tokenId: number) {126    return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8, '0')}${tokenId.toString(16).padStart(8, '0')}`);127  },128};129130class UniqueUtil {131  static transactionStatus = {132    NOT_READY: 'NotReady',133    FAIL: 'Fail',134    SUCCESS: 'Success',135  };136137  static chainLogType = {138    EXTRINSIC: 'extrinsic',139    RPC: 'rpc',140  };141142  static getTokenAccount(token: IToken): CrossAccountId {143    return new CrossAccountId({Ethereum: this.getTokenAddress(token)});144  }145146  static getTokenAddress(token: IToken): string {147    return nesting.tokenIdToAddress(token.collectionId, token.tokenId);148  }149150  static getDefaultLogger(): ILogger {151    return {152      log(msg: any, level = 'INFO') {153        console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));154      },155      level: {156        ERROR: 'ERROR',157        WARNING: 'WARNING',158        INFO: 'INFO',159      },160    };161  }162163  static vec2str(arr: string[] | number[]) {164    return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');165  }166167  static str2vec(string: string) {168    if (typeof string !== 'string') return string;169    return Array.from(string).map(x => x.charCodeAt(0));170  }171172  static fromSeed(seed: string, ss58Format = 42) {173    const keyring = new Keyring({type: 'sr25519', ss58Format});174    return keyring.addFromUri(seed);175  }176177  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {178    if (creationResult.status !== this.transactionStatus.SUCCESS) {179      throw Error('Unable to create collection!');180    }181182    let collectionId = null;183    creationResult.result.events.forEach(({event: {data, method, section}}) => {184      if ((section === 'common') && (method === 'CollectionCreated')) {185        collectionId = parseInt(data[0].toString(), 10);186      }187    });188189    if (collectionId === null) {190      throw Error('No CollectionCreated event was found!');191    }192193    return collectionId;194  }195196  static extractTokensFromCreationResult(creationResult: ITransactionResult): {197    success: boolean,198    tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],199  } {200    if (creationResult.status !== this.transactionStatus.SUCCESS) {201      throw Error('Unable to create tokens!');202    }203    let success = false;204    const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];205    creationResult.result.events.forEach(({event: {data, method, section}}) => {206      if (method === 'ExtrinsicSuccess') {207        success = true;208      } else if ((section === 'common') && (method === 'ItemCreated')) {209        tokens.push({210          collectionId: parseInt(data[0].toString(), 10),211          tokenId: parseInt(data[1].toString(), 10),212          owner: data[2].toHuman(),213          amount: data[3].toBigInt(),214        });215      }216    });217    return {success, tokens};218  }219220  static extractTokensFromBurnResult(burnResult: ITransactionResult): {221    success: boolean,222    tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],223  } {224    if (burnResult.status !== this.transactionStatus.SUCCESS) {225      throw Error('Unable to burn tokens!');226    }227    let success = false;228    const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];229    burnResult.result.events.forEach(({event: {data, method, section}}) => {230      if (method === 'ExtrinsicSuccess') {231        success = true;232      } else if ((section === 'common') && (method === 'ItemDestroyed')) {233        tokens.push({234          collectionId: parseInt(data[0].toString(), 10),235          tokenId: parseInt(data[1].toString(), 10),236          owner: data[2].toHuman(),237          amount: data[3].toBigInt(),238        });239      }240    });241    return {success, tokens};242  }243244  static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {245    let eventId = null;246    events.forEach(({event: {data, method, section}}) => {247      if ((section === expectedSection) && (method === expectedMethod)) {248        eventId = parseInt(data[0].toString(), 10);249      }250    });251252    if (eventId === null) {253      throw Error(`No ${expectedMethod} event was found!`);254    }255    return eventId === collectionId;256  }257258  static isTokenTransferSuccess(events: {event: IEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {259    const normalizeAddress = (address: string | ICrossAccountId) => {260      if(typeof address === 'string') return address;261      const obj = {} as any;262      Object.keys(address).forEach(k => {263        obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];264      });265      if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);266      if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();267      return address;268    };269    let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;270    events.forEach(({event: {data, method, section}}) => {271      if ((section === 'common') && (method === 'Transfer')) {272        const hData = (data as any).toJSON();273        transfer = {274          collectionId: hData[0],275          tokenId: hData[1],276          from: normalizeAddress(hData[2]),277          to: normalizeAddress(hData[3]),278          amount: BigInt(hData[4]),279        };280      }281    });282    let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;283    isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);284    isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);285    isSuccess = isSuccess && amount === transfer.amount;286    return isSuccess;287  }288289  static bigIntToDecimals(number: bigint, decimals = 18) {290    const numberStr = number.toString();291    const dotPos = numberStr.length - decimals;292293    if (dotPos <= 0) {294      return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;295    } else {296      const intPart = numberStr.substring(0, dotPos);297      const fractPart = numberStr.substring(dotPos);298      return intPart + '.' + fractPart;299    }300  }301}302303class UniqueEventHelper {304  private static extractIndex(index: any): [number, number] | string {305    if(index.toRawType() === '[u8;2]') return [index[0], index[1]];306    return index.toJSON();307  }308309  private static extractSub(data: any, subTypes: any): {[key: string]: any} {310    let obj: any = {};311    let index = 0;312313    if (data.entries) {314      for(const [key, value] of data.entries()) {315        obj[key] = this.extractData(value, subTypes[index]);316        index++;317      }318    } else obj = data.toJSON();319320    return obj;321  }322323  private static toHuman(data: any) {324    return data && data.toHuman ? data.toHuman() : `${data}`;325  }326327  private static extractData(data: any, type: any): any {328    if(!type) return this.toHuman(data);329    if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();330    if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();331    if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);332    return this.toHuman(data);333  }334335  public static extractEvents(events: {event: any, phase: any}[]): IEvent[] {336    const parsedEvents: IEvent[] = [];337338    events.forEach((record) => {339      const {event, phase} = record;340      const types = event.typeDef;341342      const eventData: IEvent = {343        section: event.section.toString(),344        method: event.method.toString(),345        index: this.extractIndex(event.index),346        data: [],347        phase: phase.toJSON(),348      };349350      event.data.forEach((val: any, index: number) => {351        eventData.data.push(this.extractData(val, types[index]));352      });353354      parsedEvents.push(eventData);355    });356357    return parsedEvents;358  }359}360361export class ChainHelperBase {362  helperBase: any;363364  transactionStatus = UniqueUtil.transactionStatus;365  chainLogType = UniqueUtil.chainLogType;366  util: typeof UniqueUtil;367  eventHelper: typeof UniqueEventHelper;368  logger: ILogger;369  api: ApiPromise | null;370  forcedNetwork: TNetworks | null;371  network: TNetworks | null;372  chainLog: IUniqueHelperLog[];373  children: ChainHelperBase[];374  address: AddressGroup;375  chain: ChainGroup;376377  constructor(logger?: ILogger, helperBase?: any) {378    this.helperBase = helperBase;379380    this.util = UniqueUtil;381    this.eventHelper = UniqueEventHelper;382    if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();383    this.logger = logger;384    this.api = null;385    this.forcedNetwork = null;386    this.network = null;387    this.chainLog = [];388    this.children = [];389    this.address = new AddressGroup(this);390    this.chain = new ChainGroup(this);391  }392393  clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {394    Object.setPrototypeOf(helperCls.prototype, this);395    const newHelper = new helperCls(this.logger, options);396397    newHelper.api = this.api;398    newHelper.network = this.network;399    newHelper.forceNetwork = this.forceNetwork;400401    this.children.push(newHelper);402403    return newHelper;404  }405406  getApi(): ApiPromise {407    if(this.api === null) throw Error('API not initialized');408    return this.api;409  }410411  clearChainLog(): void {412    this.chainLog = [];413  }414415  forceNetwork(value: TNetworks): void {416    this.forcedNetwork = value;417  }418419  async connect(wsEndpoint: string, listeners?: IApiListeners) {420    if (this.api !== null) throw Error('Already connected');421    const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);422    this.api = api;423    this.network = network;424  }425426  async disconnect() {427    for (const child of this.children) {428      child.clearApi();429    }430431    if (this.api === null) return;432    await this.api.disconnect();433    this.clearApi();434  }435436  clearApi() {437    this.api = null;438    this.network = null;439  }440441  static async detectNetwork(api: ApiPromise): Promise<TNetworks> {442    const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;443    const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];444445    if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;446447    if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;448    return 'opal';449  }450451  static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {452    const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});453    await api.isReady;454455    const network = await this.detectNetwork(api);456457    await api.disconnect();458459    return network;460  }461462  static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{463    api: ApiPromise;464    network: TNetworks;465  }> {466    if(typeof network === 'undefined' || network === null) network = 'opal';467    const supportedRPC = {468      opal: {469        unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,470      },471      quartz: {472        unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,473      },474      unique: {475        unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,476      },477      rococo: {},478      westend: {},479      moonbeam: {},480      moonriver: {},481      acala: {},482      karura: {},483      westmint: {},484    };485    if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);486    const rpc = supportedRPC[network];487488    // TODO: investigate how to replace rpc in runtime489    // api._rpcCore.addUserInterfaces(rpc);490491    const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});492493    await api.isReadyOrError;494495    if (typeof listeners === 'undefined') listeners = {};496    for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {497      if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;498      api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);499    }500501    return {api, network};502  }503504  getTransactionStatus(data: {events: {event: IEvent}[], status: any}) {505    const {events, status} = data;506    if (status.isReady) {507      return this.transactionStatus.NOT_READY;508    }509    if (status.isBroadcast) {510      return this.transactionStatus.NOT_READY;511    }512    if (status.isInBlock || status.isFinalized) {513      const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');514      if (errors.length > 0) {515        return this.transactionStatus.FAIL;516      }517      if (events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {518        return this.transactionStatus.SUCCESS;519      }520    }521522    return this.transactionStatus.FAIL;523  }524525  signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {526    const sign = (callback: any) => {527      if(options !== null) return transaction.signAndSend(sender, options, callback);528      return transaction.signAndSend(sender, callback);529    };530    // eslint-disable-next-line no-async-promise-executor531    return new Promise(async (resolve, reject) => {532      try {533        const unsub = await sign((result: any) => {534          const status = this.getTransactionStatus(result);535536          if (status === this.transactionStatus.SUCCESS) {537            this.logger.log(`${label} successful`);538            unsub();539            resolve({result, status});540          } else if (status === this.transactionStatus.FAIL) {541            let moduleError = null;542543            if (result.hasOwnProperty('dispatchError')) {544              const dispatchError = result['dispatchError'];545546              if (dispatchError) {547                if (dispatchError.isModule) {548                  const modErr = dispatchError.asModule;549                  const errorMeta = dispatchError.registry.findMetaError(modErr);550551                  moduleError = `${errorMeta.section}.${errorMeta.name}`;552                } else {553                  moduleError = dispatchError.toHuman();554                }555              } else {556                this.logger.log(result, this.logger.level.ERROR);557              }558            }559560            this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);561            unsub();562            reject({status, moduleError, result});563          }564        });565      } catch (e) {566        this.logger.log(e, this.logger.level.ERROR);567        reject(e);568      }569    });570  }571572  async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {573    const api = this.getApi();574    const signingInfo = await api.derive.tx.signingInfo(signer.address);575576    // We need to sign the tx because577    // unsigned transactions does not have an inclusion fee578    tx.sign(signer, {579      blockHash: api.genesisHash,580      genesisHash: api.genesisHash,581      runtimeVersion: api.runtimeVersion,582      nonce: signingInfo.nonce,583    });584585    if (len === null) {586      return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;587    } else {588      return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;589    }590  }591592  constructApiCall(apiCall: string, params: any[]) {593    if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);594    let call = this.getApi() as any;595    for(const part of apiCall.slice(4).split('.')) {596      call = call[part];597    }598    return call(...params);599  }600601  async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {602    if(this.api === null) throw Error('API not initialized');603    if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);604605    const startTime = (new Date()).getTime();606    let result: ITransactionResult;607    let events: IEvent[] = [];608    try {609      result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;610      events = this.eventHelper.extractEvents(result.result.events);611    }612    catch(e) {613      if(!(e as object).hasOwnProperty('status')) throw e;614      result = e as ITransactionResult;615    }616617    const endTime = (new Date()).getTime();618619    const log = {620      executedAt: endTime,621      executionTime: endTime - startTime,622      type: this.chainLogType.EXTRINSIC,623      status: result.status,624      call: extrinsic,625      signer: this.getSignerAddress(sender),626      params,627    } as IUniqueHelperLog;628629    if(result.status !== this.transactionStatus.SUCCESS) {630      if (result.moduleError) log.moduleError = result.moduleError;631      else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;632    }633    if(events.length > 0) log.events = events;634635    this.chainLog.push(log);636637    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {638      if (result.moduleError) throw Error(`${result.moduleError}`);639      else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));640    }641    return result;642  }643644  async callRpc(rpc: string, params?: any[]) {645    if(typeof params === 'undefined') params = [];646    if(this.api === null) throw Error('API not initialized');647    if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);648649    const startTime = (new Date()).getTime();650    let result;651    let error = null;652    const log = {653      type: this.chainLogType.RPC,654      call: rpc,655      params,656    } as IUniqueHelperLog;657658    try {659      result = await this.constructApiCall(rpc, params);660    }661    catch(e) {662      error = e;663    }664665    const endTime = (new Date()).getTime();666667    log.executedAt = endTime;668    log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';669    log.executionTime = endTime - startTime;670671    this.chainLog.push(log);672673    if(error !== null) throw error;674675    return result;676  }677678  getSignerAddress(signer: IKeyringPair | string): string {679    if(typeof signer === 'string') return signer;680    return signer.address;681  }682683  fetchAllPalletNames(): string[] {684    if(this.api === null) throw Error('API not initialized');685    return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());686  }687688  fetchMissingPalletNames(requiredPallets: string[]): string[] {689    const palletNames = this.fetchAllPalletNames();690    return requiredPallets.filter(p => !palletNames.includes(p));691  }692}693694695class HelperGroup<T extends ChainHelperBase> {696  helper: T;697698  constructor(uniqueHelper: T) {699    this.helper = uniqueHelper;700  }701}702703704class CollectionGroup extends HelperGroup<UniqueHelper> {705  /**706 * Get number of blocks when sponsored transaction is available.707 *708 * @param collectionId ID of collection709 * @param tokenId ID of token710 * @param addressObj address for which the sponsorship is checked711 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});712 * @returns number of blocks or null if sponsorship hasn't been set713 */714  async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {715    return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();716  }717718  /**719   * Get the number of created collections.720   *721   * @returns number of created collections722   */723  async getTotalCount(): Promise<number> {724    return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();725  }726727  /**728   * Get information about the collection with additional data,729   * including the number of tokens it contains, its administrators,730   * the normalized address of the collection's owner, and decoded name and description.731   *732   * @param collectionId ID of collection733   * @example await getData(2)734   * @returns collection information object735   */736  async getData(collectionId: number): Promise<{737    id: number;738    name: string;739    description: string;740    tokensCount: number;741    admins: CrossAccountId[];742    normalizedOwner: TSubstrateAccount;743    raw: any744  } | null> {745    const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);746    const humanCollection = collection.toHuman(), collectionData = {747      id: collectionId, name: null, description: null, tokensCount: 0, admins: [],748      raw: humanCollection,749    } as any, jsonCollection = collection.toJSON();750    if (humanCollection === null) return null;751    collectionData.raw.limits = jsonCollection.limits;752    collectionData.raw.permissions = jsonCollection.permissions;753    collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);754    for (const key of ['name', 'description']) {755      collectionData[key] = this.helper.util.vec2str(humanCollection[key]);756    }757758    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))759      ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)760      : 0;761    collectionData.admins = await this.getAdmins(collectionId);762763    return collectionData;764  }765766  /**767   * Get the addresses of the collection's administrators, optionally normalized.768   *769   * @param collectionId ID of collection770   * @param normalize whether to normalize the addresses to the default ss58 format771   * @example await getAdmins(1)772   * @returns array of administrators773   */774  async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {775    const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();776777    return normalize778      ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())779      : admins;780  }781782  /**783   * Get the addresses added to the collection allow-list, optionally normalized.784   * @param collectionId ID of collection785   * @param normalize whether to normalize the addresses to the default ss58 format786   * @example await getAllowList(1)787   * @returns array of allow-listed addresses788   */789  async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {790    const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();791    return normalize792      ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())793      : allowListed;794  }795796  /**797   * Get the effective limits of the collection instead of null for default values798   *799   * @param collectionId ID of collection800   * @example await getEffectiveLimits(2)801   * @returns object of collection limits802   */803  async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {804    return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();805  }806807  /**808   * Burns the collection if the signer has sufficient permissions and collection is empty.809   *810   * @param signer keyring of signer811   * @param collectionId ID of collection812   * @example await helper.collection.burn(aliceKeyring, 3);813   * @returns ```true``` if extrinsic success, otherwise ```false```814   */815  async burn(signer: TSigner, collectionId: number): Promise<boolean> {816    const result = await this.helper.executeExtrinsic(817      signer,818      'api.tx.unique.destroyCollection', [collectionId],819      true,820    );821822    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');823  }824825  /**826   * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.827   *828   * @param signer keyring of signer829   * @param collectionId ID of collection830   * @param sponsorAddress Sponsor substrate address831   * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")832   * @returns ```true``` if extrinsic success, otherwise ```false```833   */834  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {835    const result = await this.helper.executeExtrinsic(836      signer,837      'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],838      true,839    );840841    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet');842  }843844  /**845   * Confirms consent to sponsor the collection on behalf of the signer.846   *847   * @param signer keyring of signer848   * @param collectionId ID of collection849   * @example confirmSponsorship(aliceKeyring, 10)850   * @returns ```true``` if extrinsic success, otherwise ```false```851   */852  async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {853    const result = await this.helper.executeExtrinsic(854      signer,855      'api.tx.unique.confirmSponsorship', [collectionId],856      true,857    );858859    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed');860  }861862  /**863   * Removes the sponsor of a collection, regardless if it consented or not.864   *865   * @param signer keyring of signer866   * @param collectionId ID of collection867   * @example removeSponsor(aliceKeyring, 10)868   * @returns ```true``` if extrinsic success, otherwise ```false```869   */870  async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {871    const result = await this.helper.executeExtrinsic(872      signer,873      'api.tx.unique.removeCollectionSponsor', [collectionId],874      true,875    );876877    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorRemoved');878  }879880  /**881   * Sets the limits of the collection. At least one limit must be specified for a correct call.882   *883   * @param signer keyring of signer884   * @param collectionId ID of collection885   * @param limits collection limits object886   * @example887   * await setLimits(888   *   aliceKeyring,889   *   10,890   *   {891   *     sponsorTransferTimeout: 0,892   *     ownerCanDestroy: false893   *   }894   * )895   * @returns ```true``` if extrinsic success, otherwise ```false```896   */897  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {898    const result = await this.helper.executeExtrinsic(899      signer,900      'api.tx.unique.setCollectionLimits', [collectionId, limits],901      true,902    );903904    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet');905  }906907  /**908   * Changes the owner of the collection to the new Substrate address.909   *910   * @param signer keyring of signer911   * @param collectionId ID of collection912   * @param ownerAddress substrate address of new owner913   * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")914   * @returns ```true``` if extrinsic success, otherwise ```false```915   */916  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {917    const result = await this.helper.executeExtrinsic(918      signer,919      'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],920      true,921    );922923    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged');924  }925926  /**927   * Adds a collection administrator.928   *929   * @param signer keyring of signer930   * @param collectionId ID of collection931   * @param adminAddressObj Administrator address (substrate or ethereum)932   * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})933   * @returns ```true``` if extrinsic success, otherwise ```false```934   */935  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {936    const result = await this.helper.executeExtrinsic(937      signer,938      'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],939      true,940    );941942    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded');943  }944945  /**946   * Removes a collection administrator.947   *948   * @param signer keyring of signer949   * @param collectionId ID of collection950   * @param adminAddressObj Administrator address (substrate or ethereum)951   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})952   * @returns ```true``` if extrinsic success, otherwise ```false```953   */954  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {955    const result = await this.helper.executeExtrinsic(956      signer,957      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],958      true,959    );960961    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved');962  }963964  /**965   * Check if user is in allow list.966   *967   * @param collectionId ID of collection968   * @param user Account to check969   * @example await getAdmins(1)970   * @returns is user in allow list971   */972  async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {973    return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();974  }975976  /**977   * Adds an address to allow list978   * @param signer keyring of signer979   * @param collectionId ID of collection980   * @param addressObj address to add to the allow list981   * @returns ```true``` if extrinsic success, otherwise ```false```982   */983  async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {984    const result = await this.helper.executeExtrinsic(985      signer,986      'api.tx.unique.addToAllowList', [collectionId, addressObj],987      true,988    );989990    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');991  }992993  /**994   * Removes an address from allow list995   *996   * @param signer keyring of signer997   * @param collectionId ID of collection998   * @param addressObj address to remove from the allow list999   * @returns ```true``` if extrinsic success, otherwise ```false```1000   */1001  async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1002    const result = await this.helper.executeExtrinsic(1003      signer,1004      'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1005      true,1006    );10071008    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved');1009  }10101011  /**1012   * Sets onchain permissions for selected collection.1013   *1014   * @param signer keyring of signer1015   * @param collectionId ID of collection1016   * @param permissions collection permissions object1017   * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1018   * @returns ```true``` if extrinsic success, otherwise ```false```1019   */1020  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1021    const result = await this.helper.executeExtrinsic(1022      signer,1023      'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1024      true,1025    );10261027    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet');1028  }10291030  /**1031   * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1032   *1033   * @param signer keyring of signer1034   * @param collectionId ID of collection1035   * @param permissions nesting permissions object1036   * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1037   * @returns ```true``` if extrinsic success, otherwise ```false```1038   */1039  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1040    return await this.setPermissions(signer, collectionId, {nesting: permissions});1041  }10421043  /**1044   * Disables nesting for selected collection.1045   *1046   * @param signer keyring of signer1047   * @param collectionId ID of collection1048   * @example disableNesting(aliceKeyring, 10);1049   * @returns ```true``` if extrinsic success, otherwise ```false```1050   */1051  async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1052    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1053  }10541055  /**1056   * Sets onchain properties to the collection.1057   *1058   * @param signer keyring of signer1059   * @param collectionId ID of collection1060   * @param properties array of property objects1061   * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1062   * @returns ```true``` if extrinsic success, otherwise ```false```1063   */1064  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1065    const result = await this.helper.executeExtrinsic(1066      signer,1067      'api.tx.unique.setCollectionProperties', [collectionId, properties],1068      true,1069    );10701071    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1072  }10731074  /**1075   * Get collection properties.1076   *1077   * @param collectionId ID of collection1078   * @param propertyKeys optionally filter the returned properties to only these keys1079   * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1080   * @returns array of key-value pairs1081   */1082  async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1083    return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1084  }10851086  async getCollectionOptions(collectionId: number) {1087    return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1088  }10891090  /**1091   * Deletes onchain properties from the collection.1092   *1093   * @param signer keyring of signer1094   * @param collectionId ID of collection1095   * @param propertyKeys array of property keys to delete1096   * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1097   * @returns ```true``` if extrinsic success, otherwise ```false```1098   */1099  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1100    const result = await this.helper.executeExtrinsic(1101      signer,1102      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1103      true,1104    );11051106    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1107  }11081109  /**1110   * Changes the owner of the token.1111   *1112   * @param signer keyring of signer1113   * @param collectionId ID of collection1114   * @param tokenId ID of token1115   * @param addressObj address of a new owner1116   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1117   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1118   * @returns true if the token success, otherwise false1119   */1120  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1121    const result = await this.helper.executeExtrinsic(1122      signer,1123      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1124      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1125    );11261127    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1128  }11291130  /**1131   *1132   * Change ownership of a token(s) on behalf of the owner.1133   *1134   * @param signer keyring of signer1135   * @param collectionId ID of collection1136   * @param tokenId ID of token1137   * @param fromAddressObj address on behalf of which the token will be sent1138   * @param toAddressObj new token owner1139   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1140   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1141   * @returns true if the token success, otherwise false1142   */1143  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1144    const result = await this.helper.executeExtrinsic(1145      signer,1146      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1147      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1148    );1149    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1150  }11511152  /**1153   *1154   * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1155   *1156   * @param signer keyring of signer1157   * @param collectionId ID of collection1158   * @param tokenId ID of token1159   * @param amount amount of tokens to be burned. For NFT must be set to 1n1160   * @example burnToken(aliceKeyring, 10, 5);1161   * @returns ```true``` if the extrinsic is successful, otherwise ```false```1162   */1163  async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1164    const burnResult = await this.helper.executeExtrinsic(1165      signer,1166      'api.tx.unique.burnItem', [collectionId, tokenId, amount],1167      true, // `Unable to burn token for ${label}`,1168    );1169    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1170    if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1171    return burnedTokens.success;1172  }11731174  /**1175   * Destroys a concrete instance of NFT on behalf of the owner1176   *1177   * @param signer keyring of signer1178   * @param collectionId ID of collection1179   * @param tokenId ID of token1180   * @param fromAddressObj address on behalf of which the token will be burnt1181   * @param amount amount of tokens to be burned. For NFT must be set to 1n1182   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1183   * @returns ```true``` if extrinsic success, otherwise ```false```1184   */1185  async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1186    const burnResult = await this.helper.executeExtrinsic(1187      signer,1188      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1189      true, // `Unable to burn token from for ${label}`,1190    );1191    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1192    return burnedTokens.success && burnedTokens.tokens.length > 0;1193  }11941195  /**1196   * Set, change, or remove approved address to transfer the ownership of the NFT.1197   *1198   * @param signer keyring of signer1199   * @param collectionId ID of collection1200   * @param tokenId ID of token1201   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1202   * @param amount amount of token to be approved. For NFT must be set to 1n1203   * @returns ```true``` if extrinsic success, otherwise ```false```1204   */1205  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1206    const approveResult = await this.helper.executeExtrinsic(1207      signer,1208      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1209      true, // `Unable to approve token for ${label}`,1210    );12111212    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1213  }12141215  /**1216   * Get the amount of token pieces approved to transfer or burn. Normally 0.1217   *1218   * @param collectionId ID of collection1219   * @param tokenId ID of token1220   * @param toAccountObj address which is approved to use token pieces1221   * @param fromAccountObj address which may have allowed the use of its owned tokens1222   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1223   * @returns number of approved to transfer pieces1224   */1225  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1226    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1227  }12281229  /**1230   * Get the last created token ID in a collection1231   *1232   * @param collectionId ID of collection1233   * @example getLastTokenId(10);1234   * @returns id of the last created token1235   */1236  async getLastTokenId(collectionId: number): Promise<number> {1237    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1238  }12391240  /**1241   * Check if token exists1242   *1243   * @param collectionId ID of collection1244   * @param tokenId ID of token1245   * @example doesTokenExist(10, 20);1246   * @returns true if the token exists, otherwise false1247   */1248  async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1249    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1250  }1251}12521253class NFTnRFT extends CollectionGroup {1254  /**1255   * Get tokens owned by account1256   *1257   * @param collectionId ID of collection1258   * @param addressObj tokens owner1259   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1260   * @returns array of token ids owned by account1261   */1262  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1263    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1264  }12651266  /**1267   * Get token data1268   *1269   * @param collectionId ID of collection1270   * @param tokenId ID of token1271   * @param propertyKeys optionally filter the token properties to only these keys1272   * @param blockHashAt optionally query the data at some block with this hash1273   * @example getToken(10, 5);1274   * @returns human readable token data1275   */1276  async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1277    properties: IProperty[];1278    owner: CrossAccountId;1279    normalizedOwner: CrossAccountId;1280  }| null> {1281    let tokenData;1282    if(typeof blockHashAt === 'undefined') {1283      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1284    }1285    else {1286      if(propertyKeys.length == 0) {1287        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1288        if(!collection) return null;1289        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1290      }1291      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1292    }1293    tokenData = tokenData.toHuman();1294    if (tokenData === null || tokenData.owner === null) return null;1295    const owner = {} as any;1296    for (const key of Object.keys(tokenData.owner)) {1297      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1298        ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1299        : tokenData.owner[key];1300    }1301    tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1302    return tokenData;1303  }13041305  /**1306   * Set permissions to change token properties1307   *1308   * @param signer keyring of signer1309   * @param collectionId ID of collection1310   * @param permissions permissions to change a property by the collection admin or token owner1311   * @example setTokenPropertyPermissions(1312   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1313   * )1314   * @returns true if extrinsic success otherwise false1315   */1316  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1317    const result = await this.helper.executeExtrinsic(1318      signer,1319      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1320      true,1321    );13221323    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1324  }13251326  /**1327   * Get token property permissions.1328   *1329   * @param collectionId ID of collection1330   * @param propertyKeys optionally filter the returned property permissions to only these keys1331   * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1332   * @returns array of key-permission pairs1333   */1334  async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1335    return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1336  }13371338  /**1339   * Set token properties1340   *1341   * @param signer keyring of signer1342   * @param collectionId ID of collection1343   * @param tokenId ID of token1344   * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1345   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1346   * @returns ```true``` if extrinsic success, otherwise ```false```1347   */1348  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1349    const result = await this.helper.executeExtrinsic(1350      signer,1351      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1352      true,1353    );13541355    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1356  }13571358  /**1359   * Get properties, metadata assigned to a token.1360   *1361   * @param collectionId ID of collection1362   * @param tokenId ID of token1363   * @param propertyKeys optionally filter the returned properties to only these keys1364   * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1365   * @returns array of key-value pairs1366   */1367  async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1368    return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1369  }13701371  /**1372   * Delete the provided properties of a token1373   * @param signer keyring of signer1374   * @param collectionId ID of collection1375   * @param tokenId ID of token1376   * @param propertyKeys property keys to be deleted1377   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1378   * @returns ```true``` if extrinsic success, otherwise ```false```1379   */1380  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1381    const result = await this.helper.executeExtrinsic(1382      signer,1383      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1384      true,1385    );13861387    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1388  }13891390  /**1391   * Mint new collection1392   *1393   * @param signer keyring of signer1394   * @param collectionOptions basic collection options and properties1395   * @param mode NFT or RFT type of a collection1396   * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1397   * @returns object of the created collection1398   */1399  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1400    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1401    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1402    for (const key of ['name', 'description', 'tokenPrefix']) {1403      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);1404    }1405    const creationResult = await this.helper.executeExtrinsic(1406      signer,1407      'api.tx.unique.createCollectionEx', [collectionOptions],1408      true, // errorLabel,1409    );1410    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1411  }14121413  getCollectionObject(_collectionId: number): any {1414    return null;1415  }14161417  getTokenObject(_collectionId: number, _tokenId: number): any {1418    return null;1419  }14201421  /**1422   * Tells whether the given `owner` approves the `operator`.1423   * @param collectionId ID of collection1424   * @param owner owner address1425   * @param operator operator addrees1426   * @returns true if operator is enabled1427   */1428  async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1429    return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1430  }14311432  /** Sets or unsets the approval of a given operator.1433   *  The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1434   *  @param operator Operator1435   *  @param approved Should operator status be granted or revoked?1436   *  @returns ```true``` if extrinsic success, otherwise ```false```1437   */1438  async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1439    const result = await this.helper.executeExtrinsic(1440      signer,1441      'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1442      true,1443    );1444    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1445  }1446}144714481449class NFTGroup extends NFTnRFT {1450  /**1451   * Get collection object1452   * @param collectionId ID of collection1453   * @example getCollectionObject(2);1454   * @returns instance of UniqueNFTCollection1455   */1456  getCollectionObject(collectionId: number): UniqueNFTCollection {1457    return new UniqueNFTCollection(collectionId, this.helper);1458  }14591460  /**1461   * Get token object1462   * @param collectionId ID of collection1463   * @param tokenId ID of token1464   * @example getTokenObject(10, 5);1465   * @returns instance of UniqueNFTToken1466   */1467  getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1468    return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1469  }14701471  /**1472   * Get token's owner1473   * @param collectionId ID of collection1474   * @param tokenId ID of token1475   * @param blockHashAt optionally query the data at the block with this hash1476   * @example getTokenOwner(10, 5);1477   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1478   */1479  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1480    let owner;1481    if (typeof blockHashAt === 'undefined') {1482      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1483    } else {1484      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1485    }1486    return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1487  }14881489  /**1490   * Is token approved to transfer1491   * @param collectionId ID of collection1492   * @param tokenId ID of token1493   * @param toAccountObj address to be approved1494   * @returns ```true``` if extrinsic success, otherwise ```false```1495   */1496  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1497    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1498  }14991500  /**1501   * Changes the owner of the token.1502   *1503   * @param signer keyring of signer1504   * @param collectionId ID of collection1505   * @param tokenId ID of token1506   * @param addressObj address of a new owner1507   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1508   * @returns ```true``` if extrinsic success, otherwise ```false```1509   */1510  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1511    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1512  }15131514  /**1515   *1516   * Change ownership of a NFT on behalf of the owner.1517   *1518   * @param signer keyring of signer1519   * @param collectionId ID of collection1520   * @param tokenId ID of token1521   * @param fromAddressObj address on behalf of which the token will be sent1522   * @param toAddressObj new token owner1523   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1524   * @returns ```true``` if extrinsic success, otherwise ```false```1525   */1526  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1527    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1528  }15291530  /**1531   * Recursively find the address that owns the token1532   * @param collectionId ID of collection1533   * @param tokenId ID of token1534   * @param blockHashAt1535   * @example getTokenTopmostOwner(10, 5);1536   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1537   */1538  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1539    let owner;1540    if (typeof blockHashAt === 'undefined') {1541      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1542    } else {1543      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1544    }15451546    if (owner === null) return null;15471548    return owner.toHuman();1549  }15501551  /**1552   * Get tokens nested in the provided token1553   * @param collectionId ID of collection1554   * @param tokenId ID of token1555   * @param blockHashAt optionally query the data at the block with this hash1556   * @example getTokenChildren(10, 5);1557   * @returns tokens whose depth of nesting is <= 51558   */1559  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1560    let children;1561    if(typeof blockHashAt === 'undefined') {1562      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1563    } else {1564      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1565    }15661567    return children.toJSON().map((x: any) => {1568      return {collectionId: x.collection, tokenId: x.token};1569    });1570  }15711572  /**1573   * Nest one token into another1574   * @param signer keyring of signer1575   * @param tokenObj token to be nested1576   * @param rootTokenObj token to be parent1577   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1578   * @returns ```true``` if extrinsic success, otherwise ```false```1579   */1580  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1581    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1582    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1583    if(!result) {1584      throw Error('Unable to nest token!');1585    }1586    return result;1587  }15881589  /**1590   * Remove token from nested state1591   * @param signer keyring of signer1592   * @param tokenObj token to unnest1593   * @param rootTokenObj parent of a token1594   * @param toAddressObj address of a new token owner1595   * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1596   * @returns ```true``` if extrinsic success, otherwise ```false```1597   */1598  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1599    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1600    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1601    if(!result) {1602      throw Error('Unable to unnest token!');1603    }1604    return result;1605  }16061607  /**1608   * Mint new collection1609   * @param signer keyring of signer1610   * @param collectionOptions Collection options1611   * @example1612   * mintCollection(aliceKeyring, {1613   *   name: 'New',1614   *   description: 'New collection',1615   *   tokenPrefix: 'NEW',1616   * })1617   * @returns object of the created collection1618   */1619  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1620    return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1621  }16221623  /**1624   * Mint new token1625   * @param signer keyring of signer1626   * @param data token data1627   * @returns created token object1628   */1629  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1630    const creationResult = await this.helper.executeExtrinsic(1631      signer,1632      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1633        nft: {1634          properties: data.properties,1635        },1636      }],1637      true,1638    );1639    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1640    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1641    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1642    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1643  }16441645  /**1646   * Mint multiple NFT tokens1647   * @param signer keyring of signer1648   * @param collectionId ID of collection1649   * @param tokens array of tokens with owner and properties1650   * @example1651   * mintMultipleTokens(aliceKeyring, 10, [{1652   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1653   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1654   *   },{1655   *     owner: {Ethereum: "0x9F0583DbB855d..."},1656   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1657   * }]);1658   * @returns ```true``` if extrinsic success, otherwise ```false```1659   */1660  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1661    const creationResult = await this.helper.executeExtrinsic(1662      signer,1663      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1664      true,1665    );1666    const collection = this.getCollectionObject(collectionId);1667    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1668  }16691670  /**1671   * Mint multiple NFT tokens with one owner1672   * @param signer keyring of signer1673   * @param collectionId ID of collection1674   * @param owner tokens owner1675   * @param tokens array of tokens with owner and properties1676   * @example1677   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1678   *   properties: [{1679   *   key: "gender",1680   *   value: "female",1681   *  },{1682   *   key: "age",1683   *   value: "33",1684   *  }],1685   * }]);1686   * @returns array of newly created tokens1687   */1688  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1689    const rawTokens = [];1690    for (const token of tokens) {1691      const raw = {NFT: {properties: token.properties}};1692      rawTokens.push(raw);1693    }1694    const creationResult = await this.helper.executeExtrinsic(1695      signer,1696      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1697      true,1698    );1699    const collection = this.getCollectionObject(collectionId);1700    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1701  }17021703  /**1704   * Set, change, or remove approved address to transfer the ownership of the NFT.1705   *1706   * @param signer keyring of signer1707   * @param collectionId ID of collection1708   * @param tokenId ID of token1709   * @param toAddressObj address to approve1710   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1711   * @returns ```true``` if extrinsic success, otherwise ```false```1712   */1713  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId) {1714    return super.approveToken(signer, collectionId, tokenId, toAddressObj, 1n);1715  }1716}171717181719class RFTGroup extends NFTnRFT {1720  /**1721   * Get collection object1722   * @param collectionId ID of collection1723   * @example getCollectionObject(2);1724   * @returns instance of UniqueRFTCollection1725   */1726  getCollectionObject(collectionId: number): UniqueRFTCollection {1727    return new UniqueRFTCollection(collectionId, this.helper);1728  }17291730  /**1731   * Get token object1732   * @param collectionId ID of collection1733   * @param tokenId ID of token1734   * @example getTokenObject(10, 5);1735   * @returns instance of UniqueNFTToken1736   */1737  getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1738    return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1739  }17401741  /**1742   * Get top 10 token owners with the largest number of pieces1743   * @param collectionId ID of collection1744   * @param tokenId ID of token1745   * @example getTokenTop10Owners(10, 5);1746   * @returns array of top 10 owners1747   */1748  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1749    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1750  }17511752  /**1753   * Get number of pieces owned by address1754   * @param collectionId ID of collection1755   * @param tokenId ID of token1756   * @param addressObj address token owner1757   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1758   * @returns number of pieces ownerd by address1759   */1760  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1761    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1762  }17631764  /**1765   * Transfer pieces of token to another address1766   * @param signer keyring of signer1767   * @param collectionId ID of collection1768   * @param tokenId ID of token1769   * @param addressObj address of a new owner1770   * @param amount number of pieces to be transfered1771   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1772   * @returns ```true``` if extrinsic success, otherwise ```false```1773   */1774  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1775    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1776  }17771778  /**1779   * Change ownership of some pieces of RFT on behalf of the owner.1780   * @param signer keyring of signer1781   * @param collectionId ID of collection1782   * @param tokenId ID of token1783   * @param fromAddressObj address on behalf of which the token will be sent1784   * @param toAddressObj new token owner1785   * @param amount number of pieces to be transfered1786   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1787   * @returns ```true``` if extrinsic success, otherwise ```false```1788   */1789  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1790    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1791  }17921793  /**1794   * Mint new collection1795   * @param signer keyring of signer1796   * @param collectionOptions Collection options1797   * @example1798   * mintCollection(aliceKeyring, {1799   *   name: 'New',1800   *   description: 'New collection',1801   *   tokenPrefix: 'NEW',1802   * })1803   * @returns object of the created collection1804   */1805  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1806    return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1807  }18081809  /**1810   * Mint new token1811   * @param signer keyring of signer1812   * @param data token data1813   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1814   * @returns created token object1815   */1816  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1817    const creationResult = await this.helper.executeExtrinsic(1818      signer,1819      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1820        refungible: {1821          pieces: data.pieces,1822          properties: data.properties,1823        },1824      }],1825      true,1826    );1827    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1828    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1829    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1830    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1831  }18321833  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1834    throw Error('Not implemented');1835    const creationResult = await this.helper.executeExtrinsic(1836      signer,1837      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1838      true, // `Unable to mint RFT tokens for ${label}`,1839    );1840    const collection = this.getCollectionObject(collectionId);1841    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1842  }18431844  /**1845   * Mint multiple RFT tokens with one owner1846   * @param signer keyring of signer1847   * @param collectionId ID of collection1848   * @param owner tokens owner1849   * @param tokens array of tokens with properties and pieces1850   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1851   * @returns array of newly created RFT tokens1852   */1853  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1854    const rawTokens = [];1855    for (const token of tokens) {1856      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1857      rawTokens.push(raw);1858    }1859    const creationResult = await this.helper.executeExtrinsic(1860      signer,1861      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1862      true,1863    );1864    const collection = this.getCollectionObject(collectionId);1865    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1866  }18671868  /**1869   * Destroys a concrete instance of RFT.1870   * @param signer keyring of signer1871   * @param collectionId ID of collection1872   * @param tokenId ID of token1873   * @param amount number of pieces to be burnt1874   * @example burnToken(aliceKeyring, 10, 5);1875   * @returns ```true``` if the extrinsic is successful, otherwise ```false```1876   */1877  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1878    return await super.burnToken(signer, collectionId, tokenId, amount);1879  }18801881  /**1882   * Destroys a concrete instance of RFT on behalf of the owner.1883   * @param signer keyring of signer1884   * @param collectionId ID of collection1885   * @param tokenId ID of token1886   * @param fromAddressObj address on behalf of which the token will be burnt1887   * @param amount number of pieces to be burnt1888   * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1889   * @returns ```true``` if extrinsic success, otherwise ```false```1890   */1891  async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1892    return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1893  }18941895  /**1896   * Set, change, or remove approved address to transfer the ownership of the RFT.1897   *1898   * @param signer keyring of signer1899   * @param collectionId ID of collection1900   * @param tokenId ID of token1901   * @param toAddressObj address to approve1902   * @param amount number of pieces to be approved1903   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1904   * @returns true if the token success, otherwise false1905   */1906  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1907    return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1908  }19091910  /**1911   * Get total number of pieces1912   * @param collectionId ID of collection1913   * @param tokenId ID of token1914   * @example getTokenTotalPieces(10, 5);1915   * @returns number of pieces1916   */1917  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1918    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1919  }19201921  /**1922   * Change number of token pieces. Signer must be the owner of all token pieces.1923   * @param signer keyring of signer1924   * @param collectionId ID of collection1925   * @param tokenId ID of token1926   * @param amount new number of pieces1927   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1928   * @returns true if the repartion was success, otherwise false1929   */1930  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {1931    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1932    const repartitionResult = await this.helper.executeExtrinsic(1933      signer,1934      'api.tx.unique.repartition', [collectionId, tokenId, amount],1935      true,1936    );1937    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');1938    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');1939  }1940}194119421943class FTGroup extends CollectionGroup {1944  /**1945   * Get collection object1946   * @param collectionId ID of collection1947   * @example getCollectionObject(2);1948   * @returns instance of UniqueFTCollection1949   */1950  getCollectionObject(collectionId: number): UniqueFTCollection {1951    return new UniqueFTCollection(collectionId, this.helper);1952  }19531954  /**1955   * Mint new fungible collection1956   * @param signer keyring of signer1957   * @param collectionOptions Collection options1958   * @param decimalPoints number of token decimals1959   * @example1960   * mintCollection(aliceKeyring, {1961   *   name: 'New',1962   *   description: 'New collection',1963   *   tokenPrefix: 'NEW',1964   * }, 18)1965   * @returns newly created fungible collection1966   */1967  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1968    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1969    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1970    collectionOptions.mode = {fungible: decimalPoints};1971    for (const key of ['name', 'description', 'tokenPrefix']) {1972      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);1973    }1974    const creationResult = await this.helper.executeExtrinsic(1975      signer,1976      'api.tx.unique.createCollectionEx', [collectionOptions],1977      true,1978    );1979    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1980  }19811982  /**1983   * Mint tokens1984   * @param signer keyring of signer1985   * @param collectionId ID of collection1986   * @param owner address owner of new tokens1987   * @param amount amount of tokens to be meanted1988   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1989   * @returns ```true``` if extrinsic success, otherwise ```false```1990   */1991  async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {1992    const creationResult = await this.helper.executeExtrinsic(1993      signer,1994      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1995        fungible: {1996          value: amount,1997        },1998      }],1999      true, // `Unable to mint fungible tokens for ${label}`,2000    );2001    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2002  }20032004  /**2005   * Mint multiple Fungible tokens with one owner2006   * @param signer keyring of signer2007   * @param collectionId ID of collection2008   * @param owner tokens owner2009   * @param tokens array of tokens with properties and pieces2010   * @returns ```true``` if extrinsic success, otherwise ```false```2011   */2012  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: {value: bigint}[], owner: ICrossAccountId): Promise<boolean> {2013    const rawTokens = [];2014    for (const token of tokens) {2015      const raw = {Fungible: {Value: token.value}};2016      rawTokens.push(raw);2017    }2018    const creationResult = await this.helper.executeExtrinsic(2019      signer,2020      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2021      true,2022    );2023    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2024  }20252026  /**2027   * Get the top 10 owners with the largest balance for the Fungible collection2028   * @param collectionId ID of collection2029   * @example getTop10Owners(10);2030   * @returns array of ```ICrossAccountId```2031   */2032  async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2033    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2034  }20352036  /**2037   * Get account balance2038   * @param collectionId ID of collection2039   * @param addressObj address of owner2040   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2041   * @returns amount of fungible tokens owned by address2042   */2043  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2044    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2045  }20462047  /**2048   * Transfer tokens to address2049   * @param signer keyring of signer2050   * @param collectionId ID of collection2051   * @param toAddressObj address recipient2052   * @param amount amount of tokens to be sent2053   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2054   * @returns ```true``` if extrinsic success, otherwise ```false```2055   */2056  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2057    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2058  }20592060  /**2061   * Transfer some tokens on behalf of the owner.2062   * @param signer keyring of signer2063   * @param collectionId ID of collection2064   * @param fromAddressObj address on behalf of which tokens will be sent2065   * @param toAddressObj address where token to be sent2066   * @param amount number of tokens to be sent2067   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2068   * @returns ```true``` if extrinsic success, otherwise ```false```2069   */2070  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {2071    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2072  }20732074  /**2075   * Destroy some amount of tokens2076   * @param signer keyring of signer2077   * @param collectionId ID of collection2078   * @param amount amount of tokens to be destroyed2079   * @example burnTokens(aliceKeyring, 10, 1000n);2080   * @returns ```true``` if extrinsic success, otherwise ```false```2081   */2082  async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise<boolean> {2083    return await super.burnToken(signer, collectionId, 0, amount);2084  }20852086  /**2087   * Burn some tokens on behalf of the owner.2088   * @param signer keyring of signer2089   * @param collectionId ID of collection2090   * @param fromAddressObj address on behalf of which tokens will be burnt2091   * @param amount amount of tokens to be burnt2092   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2093   * @returns ```true``` if extrinsic success, otherwise ```false```2094   */2095  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {2096    return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2097  }20982099  /**2100   * Get total collection supply2101   * @param collectionId2102   * @returns2103   */2104  async getTotalPieces(collectionId: number): Promise<bigint> {2105    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2106  }21072108  /**2109   * Set, change, or remove approved address to transfer tokens.2110   *2111   * @param signer keyring of signer2112   * @param collectionId ID of collection2113   * @param toAddressObj address to be approved2114   * @param amount amount of tokens to be approved2115   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2116   * @returns ```true``` if extrinsic success, otherwise ```false```2117   */2118  approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2119    return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2120  }21212122  /**2123   * Get amount of fungible tokens approved to transfer2124   * @param collectionId ID of collection2125   * @param fromAddressObj owner of tokens2126   * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2127   * @returns number of tokens approved for the transfer2128   */2129  getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2130    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2131  }2132}213321342135class ChainGroup extends HelperGroup<ChainHelperBase> {2136  /**2137   * Get system properties of a chain2138   * @example getChainProperties();2139   * @returns ss58Format, token decimals, and token symbol2140   */2141  getChainProperties(): IChainProperties {2142    const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2143    return {2144      ss58Format: properties.ss58Format.toJSON(),2145      tokenDecimals: properties.tokenDecimals.toJSON(),2146      tokenSymbol: properties.tokenSymbol.toJSON(),2147    };2148  }21492150  /**2151   * Get chain header2152   * @example getLatestBlockNumber();2153   * @returns the number of the last block2154   */2155  async getLatestBlockNumber(): Promise<number> {2156    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2157  }21582159  /**2160   * Get block hash by block number2161   * @param blockNumber number of block2162   * @example getBlockHashByNumber(12345);2163   * @returns hash of a block2164   */2165  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2166    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2167    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2168    return blockHash;2169  }21702171  // TODO add docs2172  async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2173    const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2174    if (!blockHash) return null;2175    return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2176  }21772178  /**2179   * Get account nonce2180   * @param address substrate address2181   * @example getNonce("5GrwvaEF5zXb26Fz...");2182   * @returns number, account's nonce2183   */2184  async getNonce(address: TSubstrateAccount): Promise<number> {2185    return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2186  }2187}21882189class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2190  /**2191 * Get substrate address balance2192 * @param address substrate address2193 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2194 * @returns amount of tokens on address2195 */2196  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2197    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2198  }21992200  /**2201   * Transfer tokens to substrate address2202   * @param signer keyring of signer2203   * @param address substrate address of a recipient2204   * @param amount amount of tokens to be transfered2205   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2206   * @returns ```true``` if extrinsic success, otherwise ```false```2207   */2208  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2209    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}`*/);22102211    let transfer = {from: null, to: null, amount: 0n} as any;2212    result.result.events.forEach(({event: {data, method, section}}) => {2213      if ((section === 'balances') && (method === 'Transfer')) {2214        transfer = {2215          from: this.helper.address.normalizeSubstrate(data[0]),2216          to: this.helper.address.normalizeSubstrate(data[1]),2217          amount: BigInt(data[2]),2218        };2219      }2220    });2221    const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2222      && this.helper.address.normalizeSubstrate(address) === transfer.to2223      && BigInt(amount) === transfer.amount;2224    return isSuccess;2225  }22262227  /**2228   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2229   * @param address substrate address2230   * @returns2231   */2232  async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2233    const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2234    return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2235  }2236}22372238class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2239  /**2240   * Get ethereum address balance2241   * @param address ethereum address2242   * @example getEthereum("0x9F0583DbB855d...")2243   * @returns amount of tokens on address2244   */2245  async getEthereum(address: TEthereumAccount): Promise<bigint> {2246    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2247  }22482249  /**2250   * Transfer tokens to address2251   * @param signer keyring of signer2252   * @param address Ethereum address of a recipient2253   * @param amount amount of tokens to be transfered2254   * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2255   * @returns ```true``` if extrinsic success, otherwise ```false```2256   */2257  async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2258    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);22592260    let transfer = {from: null, to: null, amount: 0n} as any;2261    result.result.events.forEach(({event: {data, method, section}}) => {2262      if ((section === 'balances') && (method === 'Transfer')) {2263        transfer = {2264          from: data[0].toString(),2265          to: data[1].toString(),2266          amount: BigInt(data[2]),2267        };2268      }2269    });2270    const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2271      && address === transfer.to2272      && BigInt(amount) === transfer.amount;2273    return isSuccess;2274  }2275}22762277class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2278  subBalanceGroup: SubstrateBalanceGroup<T>;2279  ethBalanceGroup: EthereumBalanceGroup<T>;22802281  constructor(helper: T) {2282    super(helper);2283    this.subBalanceGroup = new SubstrateBalanceGroup(helper);2284    this.ethBalanceGroup = new EthereumBalanceGroup(helper);2285  }22862287  getCollectionCreationPrice(): bigint {2288    return 2n * this.getOneTokenNominal();2289  }2290  /**2291   * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2292   * @example getOneTokenNominal()2293   * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2294   */2295  getOneTokenNominal(): bigint {2296    const chainProperties = this.helper.chain.getChainProperties();2297    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2298  }22992300  /**2301   * Get substrate address balance2302   * @param address substrate address2303   * @example getSubstrate("5GrwvaEF5zXb26Fz...")2304   * @returns amount of tokens on address2305   */2306  getSubstrate(address: TSubstrateAccount): Promise<bigint> {2307    return this.subBalanceGroup.getSubstrate(address);2308  }23092310  /**2311   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2312   * @param address substrate address2313   * @returns2314   */2315  getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2316    return this.subBalanceGroup.getSubstrateFull(address);2317  }23182319  /**2320   * Get ethereum address balance2321   * @param address ethereum address2322   * @example getEthereum("0x9F0583DbB855d...")2323   * @returns amount of tokens on address2324   */2325  getEthereum(address: TEthereumAccount): Promise<bigint> {2326    return this.ethBalanceGroup.getEthereum(address);2327  }23282329  /**2330   * Transfer tokens to substrate address2331   * @param signer keyring of signer2332   * @param address substrate address of a recipient2333   * @param amount amount of tokens to be transfered2334   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2335   * @returns ```true``` if extrinsic success, otherwise ```false```2336   */2337  transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2338    return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2339  }23402341  async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2342    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);23432344    let transfer = {from: null, to: null, amount: 0n} as any;2345    result.result.events.forEach(({event: {data, method, section}}) => {2346      if ((section === 'balances') && (method === 'Transfer')) {2347        transfer = {2348          from: this.helper.address.normalizeSubstrate(data[0]),2349          to: this.helper.address.normalizeSubstrate(data[1]),2350          amount: BigInt(data[2]),2351        };2352      }2353    });2354    let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2355    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2356    isSuccess = isSuccess && BigInt(amount) === transfer.amount;2357    return isSuccess;2358  }2359}23602361class AddressGroup extends HelperGroup<ChainHelperBase> {2362  /**2363   * Normalizes the address to the specified ss58 format, by default ```42```.2364   * @param address substrate address2365   * @param ss58Format format for address conversion, by default ```42```2366   * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2367   * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2368   */2369  normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2370    return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2371  }23722373  /**2374   * Get address in the connected chain format2375   * @param address substrate address2376   * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2377   * @returns address in chain format2378   */2379  normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2380    return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2381  }23822383  /**2384   * Get substrate mirror of an ethereum address2385   * @param ethAddress ethereum address2386   * @param toChainFormat false for normalized account2387   * @example ethToSubstrate('0x9F0583DbB855d...')2388   * @returns substrate mirror of a provided ethereum address2389   */2390  ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): TSubstrateAccount {2391    return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2392  }23932394  /**2395   * Get ethereum mirror of a substrate address2396   * @param subAddress substrate account2397   * @example substrateToEth("5DnSF6RRjwteE3BrC...")2398   * @returns ethereum mirror of a provided substrate address2399   */2400  substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2401    return CrossAccountId.translateSubToEth(subAddress);2402  }24032404  /**2405   * Encode key to substrate address2406   * @param key key for encoding address2407   * @param ss58Format prefix for encoding to the address of the corresponding network2408   * @returns encoded substrate address2409   */2410  encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format = 42): string {2411    const u8a :Uint8Array = typeof key === 'string'2412      ? hexToU8a(key)2413      : typeof key === 'bigint'2414        ? hexToU8a(key.toString(16))2415        : key;2416  2417    if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2418      throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`);2419    }2420  2421    const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2422    if (!allowedDecodedLengths.includes(u8a.length)) {2423      throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2424    }2425  2426    const u8aPrefix = ss58Format < 642427      ? new Uint8Array([ss58Format])2428      : new Uint8Array([2429        ((ss58Format & 0xfc) >> 2) | 0x40,2430        (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2431      ]);24322433    const input = u8aConcat(u8aPrefix, u8a);2434  2435    return base58Encode(u8aConcat(2436      input,2437      blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2438    ));2439  }24402441  /**2442   * Restore substrate address from bigint representation2443   * @param number decimal representation of substrate address2444   * @returns substrate address2445   */2446  restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2447    if (this.helper.api === null) {2448      throw 'Not connected';2449    }2450    const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2451    if (res === undefined || res === null) {2452      throw 'Restore address error';2453    }2454    return res.toString();2455  }24562457  /**2458   * Convert etherium cross account id to substrate cross account id2459   * @param ethCrossAccount etherium cross account2460   * @returns substrate cross account id2461   */2462  convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2463    if (ethCrossAccount.sub === '0') {2464      return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2465    }2466    2467    const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2468    return {Substrate: ss58};2469  }24702471  paraSiblingSovereignAccount(paraid: number) {2472    // We are getting a *sibling* parachain sovereign account,2473    // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2474    const siblingPrefix = '0x7369626c';24752476    const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2477    const suffix = '000000000000000000000000000000000000000000000000';24782479    return siblingPrefix + encodedParaId + suffix;2480  }2481}24822483class StakingGroup extends HelperGroup<UniqueHelper> {2484  /**2485   * Stake tokens for App Promotion2486   * @param signer keyring of signer2487   * @param amountToStake amount of tokens to stake2488   * @param label extra label for log2489   * @returns2490   */2491  async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2492    if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2493    const _stakeResult = await this.helper.executeExtrinsic(2494      signer, 'api.tx.appPromotion.stake',2495      [amountToStake], true,2496    );2497    // TODO extract info from stakeResult2498    return true;2499  }25002501  /**2502   * Unstake tokens for App Promotion2503   * @param signer keyring of signer2504   * @param amountToUnstake amount of tokens to unstake2505   * @param label extra label for log2506   * @returns block number where balances will be unlocked2507   */2508  async unstake(signer: TSigner, label?: string): Promise<number> {2509    if(typeof label === 'undefined') label = `${signer.address}`;2510    const _unstakeResult = await this.helper.executeExtrinsic(2511      signer, 'api.tx.appPromotion.unstake',2512      [], true,2513    );2514    // TODO extract block number fron events2515    return 1;2516  }25172518  /**2519   * Get total staked amount for address2520   * @param address substrate or ethereum address2521   * @returns total staked amount2522   */2523  async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2524    if (address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2525    return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2526  }25272528  /**2529   * Get total staked per block2530   * @param address substrate or ethereum address2531   * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2532   */2533  async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2534    const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2535    return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => {2536      return {2537        block: block.toBigInt(),2538        amount: amount.toBigInt(),2539      };2540    });2541  }25422543  /**2544   * Get total pending unstake amount for address2545   * @param address substrate or ethereum address2546   * @returns total pending unstake amount2547   */2548  async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2549    return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2550  }25512552  /**2553   * Get pending unstake amount per block for address2554   * @param address substrate or ethereum address2555   * @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 block2556   */2557  async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2558    const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2559    const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => {2560      return {2561        block: block.toBigInt(),2562        amount: amount.toBigInt(),2563      };2564    });2565    return result;2566  }2567}25682569class SchedulerGroup extends HelperGroup<UniqueHelper> {2570  constructor(helper: UniqueHelper) {2571    super(helper);2572  }25732574  cancelScheduled(signer: TSigner, scheduledId: string) {2575    return this.helper.executeExtrinsic(2576      signer,2577      'api.tx.scheduler.cancelNamed',2578      [scheduledId],2579      true,2580    );2581  }25822583  changePriority(signer: TSigner, scheduledId: string, priority: number) {2584    return this.helper.executeExtrinsic(2585      signer,2586      'api.tx.scheduler.changeNamedPriority',2587      [scheduledId, priority],2588      true,2589    );2590  }25912592  scheduleAt<T extends UniqueHelper>(2593    executionBlockNumber: number,2594    options: ISchedulerOptions = {},2595  ) {2596    return this.schedule<T>('schedule', executionBlockNumber, options);2597  }25982599  scheduleAfter<T extends UniqueHelper>(2600    blocksBeforeExecution: number,2601    options: ISchedulerOptions = {},2602  ) {2603    return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2604  }26052606  schedule<T extends UniqueHelper>(2607    scheduleFn: 'schedule' | 'scheduleAfter',2608    blocksNum: number,2609    options: ISchedulerOptions = {},2610  ) {2611    // eslint-disable-next-line @typescript-eslint/naming-convention2612    const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2613    return this.helper.clone(ScheduledHelperType, {2614      scheduleFn,2615      blocksNum,2616      options,2617    }) as T;2618  }2619}26202621class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {2622  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2623    await this.helper.executeExtrinsic(2624      signer,2625      'api.tx.foreignAssets.registerForeignAsset',2626      [ownerAddress, location, metadata],2627      true,2628    );2629  }26302631  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {2632    await this.helper.executeExtrinsic(2633      signer,2634      'api.tx.foreignAssets.updateForeignAsset',2635      [foreignAssetId, location, metadata],2636      true,2637    );2638  }2639}26402641class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {2642  palletName: string;26432644  constructor(helper: T, palletName: string) {2645    super(helper);26462647    this.palletName = palletName;2648  }26492650  async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {2651    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);2652  }2653}26542655class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2656  async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {2657    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);2658  }26592660  async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {2661    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);2662  }26632664  async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {2665    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);2666  }2667}26682669class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2670  async accounts(address: string, currencyId: any) {2671    const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;2672    return BigInt(free);2673  }2674}26752676class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {2677  async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {2678    await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);2679  }26802681  async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {2682    await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);2683  }26842685  async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {2686    await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);2687  }26882689  async account(assetId: string | number, address: string) {2690    const accountAsset = (2691      await this.helper.callRpc('api.query.assets.account', [assetId, address])2692    ).toJSON()! as any;26932694    if (accountAsset !== null) {2695      return BigInt(accountAsset['balance']);2696    } else {2697      return null;2698    }2699  }2700}27012702class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {2703  async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {2704    await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);2705  }2706}27072708class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {2709  makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {2710    const apiPrefix = 'api.tx.assetManager.';27112712    const registerTx = this.helper.constructApiCall(2713      apiPrefix + 'registerForeignAsset',2714      [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],2715    );27162717    const setUnitsTx = this.helper.constructApiCall(2718      apiPrefix + 'setAssetUnitsPerSecond',2719      [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],2720    );27212722    const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);2723    const encodedProposal = batchCall?.method.toHex() || '';2724    return encodedProposal;2725  }27262727  async assetTypeId(location: any) {2728    return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);2729  }2730}27312732class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {2733  async notePreimage(signer: TSigner, encodedProposal: string) {2734    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);2735  }27362737  externalProposeMajority(proposalHash: string) {2738    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);2739  }27402741  fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {2742    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);2743  }27442745  async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {2746    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);2747  }2748}27492750class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {2751  collective: string;27522753  constructor(helper: MoonbeamHelper, collective: string) {2754    super(helper);27552756    this.collective = collective;2757  }27582759  async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {2760    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);2761  }27622763  async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {2764    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);2765  }27662767  async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {2768    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);2769  }27702771  async proposalCount() {2772    return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));2773  }2774}27752776export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;2777export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;27782779export class UniqueHelper extends ChainHelperBase {2780  balance: BalanceGroup<UniqueHelper>;2781  collection: CollectionGroup;2782  nft: NFTGroup;2783  rft: RFTGroup;2784  ft: FTGroup;2785  staking: StakingGroup;2786  scheduler: SchedulerGroup;2787  foreignAssets: ForeignAssetsGroup;2788  xcm: XcmGroup<UniqueHelper>;2789  xTokens: XTokensGroup<UniqueHelper>;2790  tokens: TokensGroup<UniqueHelper>;27912792  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2793    super(logger, options.helperBase ?? UniqueHelper);27942795    this.balance = new BalanceGroup(this);2796    this.collection = new CollectionGroup(this);2797    this.nft = new NFTGroup(this);2798    this.rft = new RFTGroup(this);2799    this.ft = new FTGroup(this);2800    this.staking = new StakingGroup(this);2801    this.scheduler = new SchedulerGroup(this);2802    this.foreignAssets = new ForeignAssetsGroup(this);2803    this.xcm = new XcmGroup(this, 'polkadotXcm');2804    this.xTokens = new XTokensGroup(this);2805    this.tokens = new TokensGroup(this);2806  }28072808  getSudo<T extends UniqueHelper>() {2809    // eslint-disable-next-line @typescript-eslint/naming-convention2810    const SudoHelperType = SudoHelper(this.helperBase);2811    return this.clone(SudoHelperType) as T;2812  }2813}28142815export class XcmChainHelper extends ChainHelperBase {2816  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {2817    const wsProvider = new WsProvider(wsEndpoint);2818    this.api = new ApiPromise({2819      provider: wsProvider,2820    });2821    await this.api.isReadyOrError;2822    this.network = await UniqueHelper.detectNetwork(this.api);2823  }2824}28252826export class RelayHelper extends XcmChainHelper {2827  xcm: XcmGroup<RelayHelper>;28282829  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2830    super(logger, options.helperBase ?? RelayHelper);28312832    this.xcm = new XcmGroup(this, 'xcmPallet');2833  }2834}28352836export class WestmintHelper extends XcmChainHelper {2837  balance: SubstrateBalanceGroup<WestmintHelper>;2838  xcm: XcmGroup<WestmintHelper>;2839  assets: AssetsGroup<WestmintHelper>;2840  xTokens: XTokensGroup<WestmintHelper>;28412842  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2843    super(logger, options.helperBase ?? WestmintHelper);28442845    this.balance = new SubstrateBalanceGroup(this);2846    this.xcm = new XcmGroup(this, 'polkadotXcm');2847    this.assets = new AssetsGroup(this);2848    this.xTokens = new XTokensGroup(this);2849  }2850}28512852export class MoonbeamHelper extends XcmChainHelper {2853  balance: EthereumBalanceGroup<MoonbeamHelper>;2854  assetManager: MoonbeamAssetManagerGroup;2855  assets: AssetsGroup<MoonbeamHelper>;2856  xTokens: XTokensGroup<MoonbeamHelper>;2857  democracy: MoonbeamDemocracyGroup;2858  collective: {2859    council: MoonbeamCollectiveGroup,2860    techCommittee: MoonbeamCollectiveGroup,2861  };28622863  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2864    super(logger, options.helperBase ?? MoonbeamHelper);28652866    this.balance = new EthereumBalanceGroup(this);2867    this.assetManager = new MoonbeamAssetManagerGroup(this);2868    this.assets = new AssetsGroup(this);2869    this.xTokens = new XTokensGroup(this);2870    this.democracy = new MoonbeamDemocracyGroup(this);2871    this.collective = {2872      council: new MoonbeamCollectiveGroup(this, 'councilCollective'),2873      techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),2874    };2875  }2876}28772878export class AcalaHelper extends XcmChainHelper {2879  balance: SubstrateBalanceGroup<AcalaHelper>;2880  assetRegistry: AcalaAssetRegistryGroup;2881  xTokens: XTokensGroup<AcalaHelper>;2882  tokens: TokensGroup<AcalaHelper>;28832884  constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2885    super(logger, options.helperBase ?? AcalaHelper);28862887    this.balance = new SubstrateBalanceGroup(this);2888    this.assetRegistry = new AcalaAssetRegistryGroup(this);2889    this.xTokens = new XTokensGroup(this);2890    this.tokens = new TokensGroup(this);2891  }28922893  getSudo<T extends AcalaHelper>() {2894    // eslint-disable-next-line @typescript-eslint/naming-convention2895    const SudoHelperType = SudoHelper(this.helperBase);2896    return this.clone(SudoHelperType) as T;2897  }2898}28992900// eslint-disable-next-line @typescript-eslint/naming-convention2901function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2902  return class extends Base {2903    scheduleFn: 'schedule' | 'scheduleAfter';2904    blocksNum: number;2905    options: ISchedulerOptions;29062907    constructor(...args: any[]) {2908      const logger = args[0] as ILogger;2909      const options = args[1] as {2910        scheduleFn: 'schedule' | 'scheduleAfter',2911        blocksNum: number,2912        options: ISchedulerOptions2913      };29142915      super(logger);29162917      this.scheduleFn = options.scheduleFn;2918      this.blocksNum = options.blocksNum;2919      this.options = options.options;2920    }29212922    executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {2923      const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);2924      2925      const mandatorySchedArgs = [2926        this.blocksNum,2927        this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,2928        this.options.priority ?? null,2929        scheduledTx,2930      ];2931      2932      let schedArgs;2933      let scheduleFn;29342935      if (this.options.scheduledId) {2936        schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];29372938        if (this.scheduleFn == 'schedule') {2939          scheduleFn = 'scheduleNamed';2940        } else if (this.scheduleFn == 'scheduleAfter') {2941          scheduleFn = 'scheduleNamedAfter';2942        }2943      } else {2944        schedArgs = mandatorySchedArgs;2945        scheduleFn = this.scheduleFn;2946      }29472948      const extrinsic = 'api.tx.scheduler.' +  scheduleFn;29492950      return super.executeExtrinsic(2951        sender,2952        extrinsic,2953        schedArgs,2954        expectSuccess,2955      );2956    }2957  };2958}29592960// eslint-disable-next-line @typescript-eslint/naming-convention2961function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {2962  return class extends Base {2963    constructor(...args: any[]) {2964      super(...args);2965    }29662967    executeExtrinsic (2968      sender: IKeyringPair,2969      extrinsic: string,2970      params: any[],2971      expectSuccess?: boolean,2972    ): Promise<ITransactionResult> {2973      const call = this.constructApiCall(extrinsic, params);2974      return super.executeExtrinsic(2975        sender,2976        'api.tx.sudo.sudo',2977        [call],2978        expectSuccess,2979      );2980    }2981  };2982}29832984export class UniqueBaseCollection {2985  helper: UniqueHelper;2986  collectionId: number;29872988  constructor(collectionId: number, uniqueHelper: UniqueHelper) {2989    this.collectionId = collectionId;2990    this.helper = uniqueHelper;2991  }29922993  async getData() {2994    return await this.helper.collection.getData(this.collectionId);2995  }29962997  async getLastTokenId() {2998    return await this.helper.collection.getLastTokenId(this.collectionId);2999  }30003001  async doesTokenExist(tokenId: number) {3002    return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);3003  }30043005  async getAdmins() {3006    return await this.helper.collection.getAdmins(this.collectionId);3007  }30083009  async getAllowList() {3010    return await this.helper.collection.getAllowList(this.collectionId);3011  }30123013  async getEffectiveLimits() {3014    return await this.helper.collection.getEffectiveLimits(this.collectionId);3015  }30163017  async getProperties(propertyKeys?: string[] | null) {3018    return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3019  }30203021  async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3022    return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3023  }30243025  async getOptions() {3026    return await this.helper.collection.getCollectionOptions(this.collectionId);3027  }30283029  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {3030    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);3031  }30323033  async confirmSponsorship(signer: TSigner) {3034    return await this.helper.collection.confirmSponsorship(signer, this.collectionId);3035  }30363037  async removeSponsor(signer: TSigner) {3038    return await this.helper.collection.removeSponsor(signer, this.collectionId);3039  }30403041  async setLimits(signer: TSigner, limits: ICollectionLimits) {3042    return await this.helper.collection.setLimits(signer, this.collectionId, limits);3043  }30443045  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {3046    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);3047  }30483049  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3050    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);3051  }30523053  async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {3054    return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);3055  }30563057  async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {3058    return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);3059  }30603061  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3062    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);3063  }30643065  async setProperties(signer: TSigner, properties: IProperty[]) {3066    return await this.helper.collection.setProperties(signer, this.collectionId, properties);3067  }30683069  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3070    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);3071  }30723073  async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {3074    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);3075  }30763077  async enableNesting(signer: TSigner, permissions: INestingPermissions) {3078    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);3079  }30803081  async disableNesting(signer: TSigner) {3082    return await this.helper.collection.disableNesting(signer, this.collectionId);3083  }30843085  async burn(signer: TSigner) {3086    return await this.helper.collection.burn(signer, this.collectionId);3087  }30883089  scheduleAt<T extends UniqueHelper>(3090    executionBlockNumber: number,3091    options: ISchedulerOptions = {},3092  ) {3093    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3094    return new UniqueBaseCollection(this.collectionId, scheduledHelper);3095  }30963097  scheduleAfter<T extends UniqueHelper>(3098    blocksBeforeExecution: number,3099    options: ISchedulerOptions = {},3100  ) {3101    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3102    return new UniqueBaseCollection(this.collectionId, scheduledHelper);3103  }31043105  getSudo<T extends UniqueHelper>() {3106    return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());3107  }3108}310931103111export class UniqueNFTCollection extends UniqueBaseCollection {3112  getTokenObject(tokenId: number) {3113    return new UniqueNFToken(tokenId, this);3114  }31153116  async getTokensByAddress(addressObj: ICrossAccountId) {3117    return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);3118  }31193120  async getToken(tokenId: number, blockHashAt?: string) {3121    return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);3122  }31233124  async getTokenOwner(tokenId: number, blockHashAt?: string) {3125    return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3126  }31273128  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3129    return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3130  }31313132  async getTokenChildren(tokenId: number, blockHashAt?: string) {3133    return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);3134  }31353136  async getPropertyPermissions(propertyKeys: string[] | null = null) {3137    return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);3138  }31393140  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3141    return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3142  }31433144  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {3145    return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);3146  }31473148  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3149    return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);3150  }31513152  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {3153    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);3154  }31553156  async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {3157    return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);3158  }31593160  async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3161    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});3162  }31633164  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) {3165    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);3166  }31673168  async burnToken(signer: TSigner, tokenId: number) {3169    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);3170  }31713172  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {3173    return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);3174  }31753176  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3177    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);3178  }31793180  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3181    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3182  }31833184  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3185    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3186  }31873188  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3189    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3190  }31913192  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3193    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3194  }31953196  scheduleAt<T extends UniqueHelper>(3197    executionBlockNumber: number,3198    options: ISchedulerOptions = {},3199  ) {3200    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3201    return new UniqueNFTCollection(this.collectionId, scheduledHelper);3202  }32033204  scheduleAfter<T extends UniqueHelper>(3205    blocksBeforeExecution: number,3206    options: ISchedulerOptions = {},3207  ) {3208    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3209    return new UniqueNFTCollection(this.collectionId, scheduledHelper);3210  }32113212  getSudo<T extends UniqueHelper>() {3213    return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());3214  }3215}321632173218export class UniqueRFTCollection extends UniqueBaseCollection {3219  getTokenObject(tokenId: number) {3220    return new UniqueRFToken(tokenId, this);3221  }32223223  async getToken(tokenId: number, blockHashAt?: string) {3224    return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3225  }32263227  async getTokensByAddress(addressObj: ICrossAccountId) {3228    return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3229  }32303231  async getTop10TokenOwners(tokenId: number) {3232    return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3233  }32343235  async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3236    return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3237  }32383239  async getTokenTotalPieces(tokenId: number) {3240    return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3241  }32423243  async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3244    return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3245  }32463247  async getPropertyPermissions(propertyKeys: string[] | null = null) {3248    return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3249  }32503251  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3252    return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3253  }32543255  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {3256    return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3257  }32583259  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3260    return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3261  }32623263  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {3264    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3265  }32663267  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3268    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3269  }32703271  async mintToken(signer: TSigner, pieces=1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3272    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3273  }32743275  async mintMultipleTokens(signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) {3276    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3277  }32783279  async burnToken(signer: TSigner, tokenId: number, amount=1n) {3280    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3281  }32823283  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId,  amount=1n) {3284    return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3285  }32863287  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3288    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3289  }32903291  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3292    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3293  }32943295  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3296    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3297  }32983299  scheduleAt<T extends UniqueHelper>(3300    executionBlockNumber: number,3301    options: ISchedulerOptions = {},3302  ) {3303    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3304    return new UniqueRFTCollection(this.collectionId, scheduledHelper);3305  }33063307  scheduleAfter<T extends UniqueHelper>(3308    blocksBeforeExecution: number,3309    options: ISchedulerOptions = {},3310  ) {3311    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3312    return new UniqueRFTCollection(this.collectionId, scheduledHelper);3313  }33143315  getSudo<T extends UniqueHelper>() {3316    return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());3317  }3318}331933203321export class UniqueFTCollection extends UniqueBaseCollection {3322  async getBalance(addressObj: ICrossAccountId) {3323    return await this.helper.ft.getBalance(this.collectionId, addressObj);3324  }33253326  async getTotalPieces() {3327    return await this.helper.ft.getTotalPieces(this.collectionId);3328  }33293330  async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3331    return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3332  }33333334  async getTop10Owners() {3335    return await this.helper.ft.getTop10Owners(this.collectionId);3336  }33373338  async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {3339    return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3340  }33413342  async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {3343    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3344  }33453346  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3347    return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3348  }33493350  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3351    return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3352  }33533354  async burnTokens(signer: TSigner, amount=1n) {3355    return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3356  }33573358  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3359    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3360  }33613362  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3363    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3364  }33653366  scheduleAt<T extends UniqueHelper>(3367    executionBlockNumber: number,3368    options: ISchedulerOptions = {},3369  ) {3370    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3371    return new UniqueFTCollection(this.collectionId, scheduledHelper);3372  }33733374  scheduleAfter<T extends UniqueHelper>(3375    blocksBeforeExecution: number,3376    options: ISchedulerOptions = {},3377  ) {3378    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3379    return new UniqueFTCollection(this.collectionId, scheduledHelper);3380  }33813382  getSudo<T extends UniqueHelper>() {3383    return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());3384  }3385}338633873388export class UniqueBaseToken {3389  collection: UniqueNFTCollection | UniqueRFTCollection;3390  collectionId: number;3391  tokenId: number;33923393  constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3394    this.collection = collection;3395    this.collectionId = collection.collectionId;3396    this.tokenId = tokenId;3397  }33983399  async getNextSponsored(addressObj: ICrossAccountId) {3400    return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3401  }34023403  async getProperties(propertyKeys?: string[] | null) {3404    return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3405  }34063407  async setProperties(signer: TSigner, properties: IProperty[]) {3408    return await this.collection.setTokenProperties(signer, this.tokenId, properties);3409  }34103411  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3412    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3413  }34143415  async doesExist() {3416    return await this.collection.doesTokenExist(this.tokenId);3417  }34183419  nestingAccount() {3420    return this.collection.helper.util.getTokenAccount(this);3421  }34223423  scheduleAt<T extends UniqueHelper>(3424    executionBlockNumber: number,3425    options: ISchedulerOptions = {},3426  ) {3427    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3428    return new UniqueBaseToken(this.tokenId, scheduledCollection);3429  }34303431  scheduleAfter<T extends UniqueHelper>(3432    blocksBeforeExecution: number,3433    options: ISchedulerOptions = {},3434  ) {3435    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3436    return new UniqueBaseToken(this.tokenId, scheduledCollection);3437  }34383439  getSudo<T extends UniqueHelper>() {3440    return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());3441  }3442}344334443445export class UniqueNFToken extends UniqueBaseToken {3446  collection: UniqueNFTCollection;34473448  constructor(tokenId: number, collection: UniqueNFTCollection) {3449    super(tokenId, collection);3450    this.collection = collection;3451  }34523453  async getData(blockHashAt?: string) {3454    return await this.collection.getToken(this.tokenId, blockHashAt);3455  }34563457  async getOwner(blockHashAt?: string) {3458    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3459  }34603461  async getTopmostOwner(blockHashAt?: string) {3462    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3463  }34643465  async getChildren(blockHashAt?: string) {3466    return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3467  }34683469  async nest(signer: TSigner, toTokenObj: IToken) {3470    return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3471  }34723473  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3474    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3475  }34763477  async transfer(signer: TSigner, addressObj: ICrossAccountId) {3478    return await this.collection.transferToken(signer, this.tokenId, addressObj);3479  }34803481  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3482    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3483  }34843485  async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3486    return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3487  }34883489  async isApproved(toAddressObj: ICrossAccountId) {3490    return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3491  }34923493  async burn(signer: TSigner) {3494    return await this.collection.burnToken(signer, this.tokenId);3495  }34963497  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3498    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3499  }35003501  scheduleAt<T extends UniqueHelper>(3502    executionBlockNumber: number,3503    options: ISchedulerOptions = {},3504  ) {3505    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3506    return new UniqueNFToken(this.tokenId, scheduledCollection);3507  }35083509  scheduleAfter<T extends UniqueHelper>(3510    blocksBeforeExecution: number,3511    options: ISchedulerOptions = {},3512  ) {3513    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3514    return new UniqueNFToken(this.tokenId, scheduledCollection);3515  }35163517  getSudo<T extends UniqueHelper>() {3518    return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());3519  }3520}35213522export class UniqueRFToken extends UniqueBaseToken {3523  collection: UniqueRFTCollection;35243525  constructor(tokenId: number, collection: UniqueRFTCollection) {3526    super(tokenId, collection);3527    this.collection = collection;3528  }35293530  async getData(blockHashAt?: string) {3531    return await this.collection.getToken(this.tokenId, blockHashAt);3532  }35333534  async getTop10Owners() {3535    return await this.collection.getTop10TokenOwners(this.tokenId);3536  }35373538  async getBalance(addressObj: ICrossAccountId) {3539    return await this.collection.getTokenBalance(this.tokenId, addressObj);3540  }35413542  async getTotalPieces() {3543    return await this.collection.getTokenTotalPieces(this.tokenId);3544  }35453546  async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3547    return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3548  }35493550  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {3551    return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3552  }35533554  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3555    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3556  }35573558  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3559    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3560  }35613562  async repartition(signer: TSigner, amount: bigint) {3563    return await this.collection.repartitionToken(signer, this.tokenId, amount);3564  }35653566  async burn(signer: TSigner, amount=1n) {3567    return await this.collection.burnToken(signer, this.tokenId, amount);3568  }35693570  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3571    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3572  }35733574  scheduleAt<T extends UniqueHelper>(3575    executionBlockNumber: number,3576    options: ISchedulerOptions = {},3577  ) {3578    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3579    return new UniqueRFToken(this.tokenId, scheduledCollection);3580  }35813582  scheduleAfter<T extends UniqueHelper>(3583    blocksBeforeExecution: number,3584    options: ISchedulerOptions = {},3585  ) {3586    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3587    return new UniqueRFToken(this.tokenId, scheduledCollection);3588  }35893590  getSudo<T extends UniqueHelper>() {3591    return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());3592  }3593}