git.delta.rocks / unique-network / refs/commits / 5ec9dbee0705

difftreelog

source

tests/src/util/playgrounds/unique.ts133.6 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034/* eslint-disable @typescript-eslint/no-var-requires */5/* eslint-disable function-call-argument-newline */6/* eslint-disable no-prototype-builtins */78import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {SignerOptions} from '@polkadot/api/types/submittable';10import '../../interfaces/augment-api';11import {AugmentedSubmittables} from '@polkadot/api-base/types/submittable';12import {ApiInterfaceEvents} from '@polkadot/api/types';13import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a, blake2AsHex} from '@polkadot/util-crypto';14import {IKeyringPair} from '@polkadot/types/types';15import {hexToU8a} from '@polkadot/util/hex';16import {u8aConcat} from '@polkadot/util/u8a';17import {18  IApiListeners,19  IBlock,20  IEvent,21  IChainProperties,22  ICollectionCreationOptions,23  ICollectionLimits,24  ICollectionPermissions,25  ICrossAccountId,26  ICrossAccountIdLower,27  ILogger,28  INestingPermissions,29  IProperty,30  IStakingInfo,31  ISchedulerOptions,32  ISubstrateBalance,33  IToken,34  ITokenPropertyPermission,35  ITransactionResult,36  IUniqueHelperLog,37  TApiAllowedListeners,38  TEthereumAccount,39  TSigner,40  TSubstrateAccount,41  TNetworks,42  IEthCrossAccountId,43} from './types';44import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';45import type {Vec} from '@polkadot/types-codec';46import {FrameSystemEventRecord} from '@polkadot/types/lookup';4748export class CrossAccountId {49  Substrate!: TSubstrateAccount;50  Ethereum!: TEthereumAccount;5152  constructor(account: ICrossAccountId) {53    if('Substrate' in account) this.Substrate = account.Substrate;54    else this.Ethereum = account.Ethereum;55  }5657  static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {58    switch (domain) {59      case 'Substrate': return new CrossAccountId({Substrate: account.address});60      case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();61    }62  }6364  static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {65    if('substrate' in address) return new CrossAccountId({Substrate: address.substrate});66    else return new CrossAccountId({Ethereum: address.ethereum});67  }6869  static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {70    return encodeAddress(decodeAddress(address), ss58Format);71  }7273  static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {74    return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});75  }7677  withNormalizedSubstrate(ss58Format = 42): CrossAccountId {78    if(this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);79    return this;80  }8182  static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {83    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));84  }8586  toEthereum(): CrossAccountId {87    if(this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});88    return this;89  }9091  static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {92    return evmToAddress(address, ss58Format);93  }9495  toSubstrate(ss58Format?: number): CrossAccountId {96    if(this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});97    return this;98  }99100  toLowerCase(): CrossAccountId {101    if(this.Substrate) this.Substrate = this.Substrate.toLowerCase();102    if(this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();103    return this;104  }105}106107const nesting = {108  toChecksumAddress(address: string): string {109    if(typeof address === 'undefined') return '';110111    if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);112113    address = address.toLowerCase().replace(/^0x/i, '');114    const addressHash = keccakAsHex(address).replace(/^0x/i, '');115    const checksumAddress = ['0x'];116117    for(let i = 0; i < address.length; i++) {118      // If ith character is 8 to f then make it uppercase119      if(parseInt(addressHash[i], 16) > 7) {120        checksumAddress.push(address[i].toUpperCase());121      } else {122        checksumAddress.push(address[i]);123      }124    }125    return checksumAddress.join('');126  },127  tokenIdToAddress(collectionId: number, tokenId: number) {128    return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8, '0')}${tokenId.toString(16).padStart(8, '0')}`);129  },130};131132class UniqueUtil {133  static transactionStatus = {134    NOT_READY: 'NotReady',135    FAIL: 'Fail',136    SUCCESS: 'Success',137  };138139  static chainLogType = {140    EXTRINSIC: 'extrinsic',141    RPC: 'rpc',142  };143144  static getTokenAccount(token: IToken): CrossAccountId {145    return new CrossAccountId({Ethereum: this.getTokenAddress(token)});146  }147148  static getTokenAddress(token: IToken): string {149    return nesting.tokenIdToAddress(token.collectionId, token.tokenId);150  }151152  static getDefaultLogger(): ILogger {153    return {154      log(msg: any, level = 'INFO') {155        console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));156      },157      level: {158        ERROR: 'ERROR',159        WARNING: 'WARNING',160        INFO: 'INFO',161      },162    };163  }164165  static vec2str(arr: string[] | number[]) {166    return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');167  }168169  static str2vec(string: string) {170    if(typeof string !== 'string') return string;171    return Array.from(string).map(x => x.charCodeAt(0));172  }173174  static fromSeed(seed: string, ss58Format = 42) {175    const keyring = new Keyring({type: 'sr25519', ss58Format});176    return keyring.addFromUri(seed);177  }178179  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {180    if(creationResult.status !== this.transactionStatus.SUCCESS) {181      throw Error('Unable to create collection!');182    }183184    let collectionId = null;185    creationResult.result.events.forEach(({event: {data, method, section}}) => {186      if((section === 'common') && (method === 'CollectionCreated')) {187        collectionId = parseInt(data[0].toString(), 10);188      }189    });190191    if(collectionId === null) {192      throw Error('No CollectionCreated event was found!');193    }194195    return collectionId;196  }197198  static extractTokensFromCreationResult(creationResult: ITransactionResult): {199    success: boolean,200    tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],201  } {202    if(creationResult.status !== this.transactionStatus.SUCCESS) {203      throw Error('Unable to create tokens!');204    }205    let success = false;206    const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];207    creationResult.result.events.forEach(({event: {data, method, section}}) => {208      if(method === 'ExtrinsicSuccess') {209        success = true;210      } else if((section === 'common') && (method === 'ItemCreated')) {211        tokens.push({212          collectionId: parseInt(data[0].toString(), 10),213          tokenId: parseInt(data[1].toString(), 10),214          owner: data[2].toHuman(),215          amount: data[3].toBigInt(),216        });217      }218    });219    return {success, tokens};220  }221222  static extractTokensFromBurnResult(burnResult: ITransactionResult): {223    success: boolean,224    tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],225  } {226    if(burnResult.status !== this.transactionStatus.SUCCESS) {227      throw Error('Unable to burn tokens!');228    }229    let success = false;230    const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];231    burnResult.result.events.forEach(({event: {data, method, section}}) => {232      if(method === 'ExtrinsicSuccess') {233        success = true;234      } else if((section === 'common') && (method === 'ItemDestroyed')) {235        tokens.push({236          collectionId: parseInt(data[0].toString(), 10),237          tokenId: parseInt(data[1].toString(), 10),238          owner: data[2].toHuman(),239          amount: data[3].toBigInt(),240        });241      }242    });243    return {success, tokens};244  }245246  static findCollectionInEvents(events: { event: IEvent }[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {247    let eventId = null;248    events.forEach(({event: {data, method, section}}) => {249      if((section === expectedSection) && (method === expectedMethod)) {250        eventId = parseInt(data[0].toString(), 10);251      }252    });253254    if(eventId === null) {255      throw Error(`No ${expectedMethod} event was found!`);256    }257    return eventId === collectionId;258  }259260  static isTokenTransferSuccess(events: { event: IEvent }[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {261    const normalizeAddress = (address: string | ICrossAccountId) => {262      if(typeof address === 'string') return address;263      const obj = {} as any;264      Object.keys(address).forEach(k => {265        obj[k.toLocaleLowerCase()] = (address as any)[k];266      });267      if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);268      if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();269      return address;270    };271    let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;272    events.forEach(({event: {data, method, section}}) => {273      if((section === 'common') && (method === 'Transfer')) {274        const hData = (data as any).toJSON();275        transfer = {276          collectionId: hData[0],277          tokenId: hData[1],278          from: normalizeAddress(hData[2]),279          to: normalizeAddress(hData[3]),280          amount: BigInt(hData[4]),281        };282      }283    });284    let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;285    isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);286    isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);287    isSuccess = isSuccess && amount === transfer.amount;288    return isSuccess;289  }290291  static bigIntToDecimals(number: bigint, decimals = 18) {292    const numberStr = number.toString();293    const dotPos = numberStr.length - decimals;294295    if(dotPos <= 0) {296      return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;297    } else {298      const intPart = numberStr.substring(0, dotPos);299      const fractPart = numberStr.substring(dotPos);300      return intPart + '.' + fractPart;301    }302  }303}304305class UniqueEventHelper {306  private static extractIndex(index: any): [number, number] | string {307    if(index.toRawType() === '[u8;2]') return [index[0], index[1]];308    return index.toJSON();309  }310311  private static extractSub(data: any, subTypes: any): { [key: string]: any } {312    let obj: any = {};313    let index = 0;314315    if(data.entries) {316      for(const [key, value] of data.entries()) {317        obj[key] = this.extractData(value, subTypes[index]);318        index++;319      }320    } else obj = data.toJSON();321322    return obj;323  }324325  private static toHuman(data: any) {326    return data && data.toHuman ? data.toHuman() : `${data}`;327  }328329  private static extractData(data: any, type: any): any {330    if(!type) return this.toHuman(data);331    if(['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();332    if(['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();333    if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);334    return this.toHuman(data);335  }336337  public static extractEvents(events: { event: any, phase: any }[]): IEvent[] {338    const parsedEvents: IEvent[] = [];339340    events.forEach((record) => {341      const {event, phase} = record;342      const types = event.typeDef;343344      const eventData: IEvent = {345        section: event.section.toString(),346        method: event.method.toString(),347        index: this.extractIndex(event.index),348        data: [],349        phase: phase.toJSON(),350      };351352      event.data.forEach((val: any, index: number) => {353        eventData.data.push(this.extractData(val, types[index]));354      });355356      parsedEvents.push(eventData);357    });358359    return parsedEvents;360  }361}362const InvalidTypeSymbol = Symbol('Invalid type');363// eslint-disable-next-line @typescript-eslint/no-unused-vars364export type Invalid<ErrorMessage> =365  | ((366    invalidType: typeof InvalidTypeSymbol,367    ..._: typeof InvalidTypeSymbol[]368  ) => typeof InvalidTypeSymbol)369  | null370  | undefined;371// Has slightly better error messages than Get372type Get2<T, P extends string, E> =373  P extends `${infer Key}.${infer Key2}` ? Key extends keyof T ? Key2 extends keyof T[Key] ? T[Key][Key2] : E : E : E;374type ForceFunction<T> = T extends (...args: any) => any ? T : (...args: any) => Invalid<'not a function'>;375376export class ChainHelperBase {377  helperBase: any;378379  transactionStatus = UniqueUtil.transactionStatus;380  chainLogType = UniqueUtil.chainLogType;381  util: typeof UniqueUtil;382  eventHelper: typeof UniqueEventHelper;383  logger: ILogger;384  api: ApiPromise | null;385  forcedNetwork: TNetworks | null;386  network: TNetworks | null;387  wsEndpoint: string | null;388  chainLog: IUniqueHelperLog[];389  children: ChainHelperBase[];390  address: AddressGroup;391  chain: ChainGroup;392393  constructor(logger?: ILogger, helperBase?: any) {394    this.helperBase = helperBase;395396    this.util = UniqueUtil;397    this.eventHelper = UniqueEventHelper;398    if(typeof logger == 'undefined') logger = this.util.getDefaultLogger();399    this.logger = logger;400    this.api = null;401    this.forcedNetwork = null;402    this.network = null;403    this.wsEndpoint = null;404    this.chainLog = [];405    this.children = [];406    this.address = new AddressGroup(this);407    this.chain = new ChainGroup(this);408  }409410  clone(helperCls: ChainHelperBaseConstructor, options: { [key: string]: any } = {}) {411    Object.setPrototypeOf(helperCls.prototype, this);412    const newHelper = new helperCls(this.logger, options);413414    newHelper.api = this.api;415    newHelper.network = this.network;416    newHelper.forceNetwork = this.forceNetwork;417418    this.children.push(newHelper);419420    return newHelper;421  }422423  getEndpoint(): string {424    if(this.wsEndpoint === null) throw Error('No connection was established');425    return this.wsEndpoint;426  }427428  getApi(): ApiPromise {429    if(this.api === null) throw Error('API not initialized');430    return this.api;431  }432433  async subscribeEvents(expectedEvents: { section: string, names: string[] }[]) {434    const collectedEvents: IEvent[] = [];435    const unsubscribe = await this.getApi().query.system.events((events: Vec<FrameSystemEventRecord>) => {436      const ievents = this.eventHelper.extractEvents(events);437      ievents.forEach((event) => {438        expectedEvents.forEach((e => {439          if(event.section === e.section && e.names.includes(event.method)) {440            collectedEvents.push(event);441          }442        }));443      });444    });445    return {unsubscribe: unsubscribe as any, collectedEvents};446  }447448  clearChainLog(): void {449    this.chainLog = [];450  }451452  forceNetwork(value: TNetworks): void {453    this.forcedNetwork = value;454  }455456  async connect(wsEndpoint: string, listeners?: IApiListeners) {457    if(this.api !== null) throw Error('Already connected');458    const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);459    this.wsEndpoint = wsEndpoint;460    this.api = api;461    this.network = network;462  }463464  async disconnect() {465    for(const child of this.children) {466      child.clearApi();467    }468469    if(this.api === null) return;470    await this.api.disconnect();471    this.clearApi();472  }473474  clearApi() {475    this.api = null;476    this.network = null;477  }478479  static async detectNetwork(api: ApiPromise): Promise<TNetworks> {480    const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;481    const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];482483    if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;484485    if(['quartz', 'unique', 'sapphire'].indexOf(spec.specName) > -1) return spec.specName;486    return 'opal';487  }488489  static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {490    if(!wsEndpoint) throw new Error('wsEndpoint was not set');491    const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});492    await api.isReady;493494    const network = await this.detectNetwork(api);495496    await api.disconnect();497498    return network;499  }500501  static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{502    api: ApiPromise;503    network: TNetworks;504  }> {505    if(typeof network === 'undefined' || network === null) network = 'opal';506    if(!wsEndpoint) throw new Error('wsEndpoint was not set');507    const supportedRPC = {508      opal: {509        unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,510      },511      quartz: {512        unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,513      },514      unique: {515        unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,516      },517      rococo: {},518      westend: {},519      moonbeam: {},520      moonriver: {},521      acala: {},522      karura: {},523      westmint: {},524    };525    if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);526    const rpc = supportedRPC[network];527528    // TODO: investigate how to replace rpc in runtime529    // api._rpcCore.addUserInterfaces(rpc);530531    const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});532533    await api.isReadyOrError;534535    if(typeof listeners === 'undefined') listeners = {};536    for(const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {537      if(!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;538      api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);539    }540541    return {api, network};542  }543544  getTransactionStatus(data: { events: { event: IEvent }[], status: any }) {545    const {events, status} = data;546    if(status.isReady) {547      return this.transactionStatus.NOT_READY;548    }549    if(status.isBroadcast) {550      return this.transactionStatus.NOT_READY;551    }552    if(status.isInBlock || status.isFinalized) {553      const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');554      if(errors.length > 0) {555        return this.transactionStatus.FAIL;556      }557      if(events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {558        return this.transactionStatus.SUCCESS;559      }560    }561562    return this.transactionStatus.FAIL;563  }564565  signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {566    const sign = (callback: any) => {567      if(options !== null) return transaction.signAndSend(sender, options, callback);568      return transaction.signAndSend(sender, callback);569    };570    // eslint-disable-next-line no-async-promise-executor571    return new Promise(async (resolve, reject) => {572      try {573        const unsub = await sign((result: any) => {574          const status = this.getTransactionStatus(result);575576          if(status === this.transactionStatus.SUCCESS) {577            this.logger.log(`${label} successful`);578            unsub();579            resolve({result, status, blockHash: result.status.asInBlock.toHuman()});580          } else if(status === this.transactionStatus.FAIL) {581            let moduleError = null;582583            if(result.hasOwnProperty('dispatchError')) {584              const dispatchError = result['dispatchError'];585586              if(dispatchError) {587                if(dispatchError.isModule) {588                  const modErr = dispatchError.asModule;589                  const errorMeta = dispatchError.registry.findMetaError(modErr);590591                  moduleError = `${errorMeta.section}.${errorMeta.name}`;592                } else if(dispatchError.isToken) {593                  moduleError = `Token: ${dispatchError.asToken}`;594                } else {595                  // May be [object Object] in case of unhandled non-unit enum596                  moduleError = `Misc: ${dispatchError.toHuman()}`;597                }598              } else {599                this.logger.log(result, this.logger.level.ERROR);600              }601            }602603            this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);604            unsub();605            reject({status, moduleError, result});606          }607        });608      } catch (e) {609        this.logger.log(e, this.logger.level.ERROR);610        reject(e);611      }612    });613  }614615  async signTransactionWithoutSending(signer: TSigner, tx: any) {616    const api = this.getApi();617    const signingInfo = await api.derive.tx.signingInfo(signer.address);618619    tx.sign(signer, {620      blockHash: api.genesisHash,621      genesisHash: api.genesisHash,622      runtimeVersion: api.runtimeVersion,623      nonce: signingInfo.nonce,624    });625626    return tx.toHex();627  }628629  async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {630    const api = this.getApi();631    const signingInfo = await api.derive.tx.signingInfo(signer.address);632633    // We need to sign the tx because634    // unsigned transactions does not have an inclusion fee635    tx.sign(signer, {636      blockHash: api.genesisHash,637      genesisHash: api.genesisHash,638      runtimeVersion: api.runtimeVersion,639      nonce: signingInfo.nonce,640    });641642    if(len === null) {643      return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;644    } else {645      return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;646    }647  }648649  constructApiCall(apiCall: string, params: any[]) {650    if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);651    let call = this.getApi() as any;652    for(const part of apiCall.slice(4).split('.')) {653      call = call[part];654      if(!call) {655        const advice = part.includes('_') ? ' Looks like it needs to be converted to camel case.' : '';656        throw Error(`Function ${part} of api call ${apiCall} not found.${advice}`);657      }658    }659    return call(...params);660  }661662  encodeApiCall(apiCall: string, params: any[]) {663    return this.constructApiCall(apiCall, params).method.toHex();664  }665666  async executeExtrinsic<667    E extends string,668    V extends (669      ...args: any) => any = ForceFunction<670        Get2<671          AugmentedSubmittables<'promise'>,672          E, (...args: any) => Invalid<'not found'>673        >674      >675  >(676    sender: TSigner,677    extrinsic: `api.tx.${E}`,678    params: Parameters<V>,679    expectSuccess = true,680    options: Partial<SignerOptions> | null = null,/*, failureMessage='expected success'*/681  ): Promise<ITransactionResult> {682    if(this.api === null) throw Error('API not initialized');683684    const startTime = (new Date()).getTime();685    let result: ITransactionResult;686    let events: IEvent[] = [];687    try {688      result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;689      events = this.eventHelper.extractEvents(result.result.events);690      const errorEvent = events.find((event) => event.method == 'ExecutedFailed' || event.method == 'CreatedFailed');691      if(errorEvent)692        throw Error(errorEvent.method + ': ' + extrinsic);693    }694    catch (e) {695      if(!(e as object).hasOwnProperty('status')) throw e;696      result = e as ITransactionResult;697    }698699    const endTime = (new Date()).getTime();700701    const log = {702      executedAt: endTime,703      executionTime: endTime - startTime,704      type: this.chainLogType.EXTRINSIC,705      status: result.status,706      call: extrinsic,707      signer: this.getSignerAddress(sender),708      params,709    } as IUniqueHelperLog;710711    let errorMessage = '';712713    if(result.status !== this.transactionStatus.SUCCESS) {714      if(result.moduleError) {715        errorMessage = typeof result.moduleError === 'string'716          ? result.moduleError717          : `${Object.keys(result.moduleError)[0]}: ${Object.values(result.moduleError)[0]}`;718        log.moduleError = errorMessage;719      }720      else if(result.result.dispatchError) log.dispatchError = result.result.dispatchError;721    }722    if(events.length > 0) log.events = events;723724    this.chainLog.push(log);725726    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {727      if(result.moduleError) throw Error(`${errorMessage}`);728      else if(result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));729    }730    return result as any;731  }732  executeExtrinsicUncheckedWeight<733      E extends string,734      V extends (735         ...args: any) => any = ForceFunction<736            Get2<737               AugmentedSubmittables<'promise'>,738               E, (...args: any) => Invalid<'not found'>739            >740         >741   >(742    sender: TSigner,743    extrinsic: `api.tx.${E}`,744    params: Parameters<V>,745    expectSuccess = true,746    options: Partial<SignerOptions> | null = null,/*, failureMessage='expected success'*/747  ): Promise<ITransactionResult> {748    throw new Error('executeExtrinsicUncheckedWeight only supported in sudo');749  }750751  async callRpc752  // TODO: make it strongly typed, or use api.query/api.rpc directly753  // <754  // K extends 'rpc' | 'query',755  // E extends string,756  // V extends (...args: any) => any = ForceFunction<757  //   Get2<758  //     K extends 'rpc' ? DecoratedRpc<'promise', RpcInterface> : QueryableStorage<'promise'>,759  //     E, (...args: any) => Invalid<'not found'>760  //   >761  // >,762  // P = Parameters<V>,763  // >764  (rpc: string, params?: any[]): Promise<any> {765766    if(typeof params === 'undefined') params = [] as any;767    if(this.api === null) throw Error('API not initialized');768    if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);769770    const startTime = (new Date()).getTime();771    let result;772    let error = null;773    const log = {774      type: this.chainLogType.RPC,775      call: rpc,776      params,777    } as any as IUniqueHelperLog;778779    try {780      result = await this.constructApiCall(rpc, params as any);781    }782    catch (e) {783      error = e;784    }785786    const endTime = (new Date()).getTime();787788    log.executedAt = endTime;789    log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';790    log.executionTime = endTime - startTime;791792    this.chainLog.push(log);793794    if(error !== null) throw error;795796    return result;797  }798799  getSignerAddress(signer: IKeyringPair | string): string {800    if(typeof signer === 'string') return signer;801    return signer.address;802  }803804  fetchAllPalletNames(): string[] {805    if(this.api === null) throw Error('API not initialized');806    return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()).sort();807  }808809  fetchMissingPalletNames(requiredPallets: readonly string[]): string[] {810    const palletNames = this.fetchAllPalletNames();811    return requiredPallets.filter(p => !palletNames.includes(p));812  }813}814815816export class HelperGroup<T extends ChainHelperBase> {817  helper: T;818819  constructor(uniqueHelper: T) {820    this.helper = uniqueHelper;821  }822}823824825class CollectionGroup extends HelperGroup<UniqueHelper> {826  /**827 * Get number of blocks when sponsored transaction is available.828 *829 * @param collectionId ID of collection830 * @param tokenId ID of token831 * @param addressObj address for which the sponsorship is checked832 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});833 * @returns number of blocks or null if sponsorship hasn't been set834 */835  async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {836    return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();837  }838839  /**840   * Get the number of created collections.841   *842   * @returns number of created collections843   */844  async getTotalCount(): Promise<number> {845    return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();846  }847848  /**849   * Get information about the collection with additional data,850   * including the number of tokens it contains, its administrators,851   * the normalized address of the collection's owner, and decoded name and description.852   *853   * @param collectionId ID of collection854   * @example await getData(2)855   * @returns collection information object856   */857  async getData(collectionId: number): Promise<{858    id: number;859    name: string;860    description: string;861    tokensCount: number;862    admins: CrossAccountId[];863    normalizedOwner: TSubstrateAccount;864    raw: any865  } | null> {866    const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);867    const humanCollection = collection.toHuman(), collectionData = {868      id: collectionId, name: null, description: null, tokensCount: 0, admins: [],869      raw: humanCollection,870    } as any, jsonCollection = collection.toJSON();871    if(humanCollection === null) return null;872    collectionData.raw.limits = jsonCollection.limits;873    collectionData.raw.permissions = jsonCollection.permissions;874    collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);875    for(const key of ['name', 'description']) {876      collectionData[key] = this.helper.util.vec2str(humanCollection[key]);877    }878879    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))880      ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)881      : 0;882    collectionData.admins = await this.getAdmins(collectionId);883884    return collectionData;885  }886887  /**888   * Get the addresses of the collection's administrators, optionally normalized.889   *890   * @param collectionId ID of collection891   * @param normalize whether to normalize the addresses to the default ss58 format892   * @example await getAdmins(1)893   * @returns array of administrators894   */895  async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {896    const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();897898    return normalize899      ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())900      : admins;901  }902903  /**904   * Get the addresses added to the collection allow-list, optionally normalized.905   * @param collectionId ID of collection906   * @param normalize whether to normalize the addresses to the default ss58 format907   * @example await getAllowList(1)908   * @returns array of allow-listed addresses909   */910  async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {911    const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();912    return normalize913      ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())914      : allowListed;915  }916917  /**918   * Get the effective limits of the collection instead of null for default values919   *920   * @param collectionId ID of collection921   * @example await getEffectiveLimits(2)922   * @returns object of collection limits923   */924  async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {925    return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();926  }927928  /**929   * Burns the collection if the signer has sufficient permissions and collection is empty.930   *931   * @param signer keyring of signer932   * @param collectionId ID of collection933   * @example await helper.collection.burn(aliceKeyring, 3);934   * @returns ```true``` if extrinsic success, otherwise ```false```935   */936  async burn(signer: TSigner, collectionId: number): Promise<boolean> {937    const result = await this.helper.executeExtrinsic(938      signer,939      'api.tx.unique.destroyCollection', [collectionId],940      true,941    );942943    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');944  }945946  /**947   * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.948   *949   * @param signer keyring of signer950   * @param collectionId ID of collection951   * @param sponsorAddress Sponsor substrate address952   * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")953   * @returns ```true``` if extrinsic success, otherwise ```false```954   */955  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {956    const result = await this.helper.executeExtrinsic(957      signer,958      'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],959      true,960    );961962    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorSet');963  }964965  /**966   * Confirms consent to sponsor the collection on behalf of the signer.967   *968   * @param signer keyring of signer969   * @param collectionId ID of collection970   * @example confirmSponsorship(aliceKeyring, 10)971   * @returns ```true``` if extrinsic success, otherwise ```false```972   */973  async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {974    const result = await this.helper.executeExtrinsic(975      signer,976      'api.tx.unique.confirmSponsorship', [collectionId],977      true,978    );979980    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'SponsorshipConfirmed');981  }982983  /**984   * Removes the sponsor of a collection, regardless if it consented or not.985   *986   * @param signer keyring of signer987   * @param collectionId ID of collection988   * @example removeSponsor(aliceKeyring, 10)989   * @returns ```true``` if extrinsic success, otherwise ```false```990   */991  async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {992    const result = await this.helper.executeExtrinsic(993      signer,994      'api.tx.unique.removeCollectionSponsor', [collectionId],995      true,996    );997998    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorRemoved');999  }10001001  /**1002   * Sets the limits of the collection. At least one limit must be specified for a correct call.1003   *1004   * @param signer keyring of signer1005   * @param collectionId ID of collection1006   * @param limits collection limits object1007   * @example1008   * await setLimits(1009   *   aliceKeyring,1010   *   10,1011   *   {1012   *     sponsorTransferTimeout: 0,1013   *     ownerCanDestroy: false1014   *   }1015   * )1016   * @returns ```true``` if extrinsic success, otherwise ```false```1017   */1018  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {1019    const result = await this.helper.executeExtrinsic(1020      signer,1021      'api.tx.unique.setCollectionLimits', [collectionId, limits],1022      true,1023    );10241025    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionLimitSet');1026  }10271028  /**1029   * Changes the owner of the collection to the new Substrate address.1030   *1031   * @param signer keyring of signer1032   * @param collectionId ID of collection1033   * @param ownerAddress substrate address of new owner1034   * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")1035   * @returns ```true``` if extrinsic success, otherwise ```false```1036   */1037  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {1038    const result = await this.helper.executeExtrinsic(1039      signer,1040      'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],1041      true,1042    );10431044    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionOwnerChanged');1045  }10461047  /**1048   * Adds a collection administrator.1049   *1050   * @param signer keyring of signer1051   * @param collectionId ID of collection1052   * @param adminAddressObj Administrator address (substrate or ethereum)1053   * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1054   * @returns ```true``` if extrinsic success, otherwise ```false```1055   */1056  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1057    const result = await this.helper.executeExtrinsic(1058      signer,1059      'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],1060      true,1061    );10621063    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminAdded');1064  }10651066  /**1067   * Removes a collection administrator.1068   *1069   * @param signer keyring of signer1070   * @param collectionId ID of collection1071   * @param adminAddressObj Administrator address (substrate or ethereum)1072   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1073   * @returns ```true``` if extrinsic success, otherwise ```false```1074   */1075  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1076    const result = await this.helper.executeExtrinsic(1077      signer,1078      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],1079      true,1080    );10811082    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminRemoved');1083  }10841085  /**1086   * Check if user is in allow list.1087   *1088   * @param collectionId ID of collection1089   * @param user Account to check1090   * @example await getAdmins(1)1091   * @returns is user in allow list1092   */1093  async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {1094    return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();1095  }10961097  /**1098   * Adds an address to allow list1099   * @param signer keyring of signer1100   * @param collectionId ID of collection1101   * @param addressObj address to add to the allow list1102   * @returns ```true``` if extrinsic success, otherwise ```false```1103   */1104  async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1105    const result = await this.helper.executeExtrinsic(1106      signer,1107      'api.tx.unique.addToAllowList', [collectionId, addressObj],1108      true,1109    );11101111    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressAdded');1112  }11131114  /**1115   * Removes an address from allow list1116   *1117   * @param signer keyring of signer1118   * @param collectionId ID of collection1119   * @param addressObj address to remove from the allow list1120   * @returns ```true``` if extrinsic success, otherwise ```false```1121   */1122  async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1123    const result = await this.helper.executeExtrinsic(1124      signer,1125      'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1126      true,1127    );11281129    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressRemoved');1130  }11311132  /**1133   * Sets onchain permissions for selected collection.1134   *1135   * @param signer keyring of signer1136   * @param collectionId ID of collection1137   * @param permissions collection permissions object1138   * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1139   * @returns ```true``` if extrinsic success, otherwise ```false```1140   */1141  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1142    const result = await this.helper.executeExtrinsic(1143      signer,1144      'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1145      true,1146    );11471148    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPermissionSet');1149  }11501151  /**1152   * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1153   *1154   * @param signer keyring of signer1155   * @param collectionId ID of collection1156   * @param permissions nesting permissions object1157   * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1158   * @returns ```true``` if extrinsic success, otherwise ```false```1159   */1160  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1161    return await this.setPermissions(signer, collectionId, {nesting: permissions});1162  }11631164  /**1165   * Disables nesting for selected collection.1166   *1167   * @param signer keyring of signer1168   * @param collectionId ID of collection1169   * @example disableNesting(aliceKeyring, 10);1170   * @returns ```true``` if extrinsic success, otherwise ```false```1171   */1172  async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1173    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1174  }11751176  /**1177   * Sets onchain properties to the collection.1178   *1179   * @param signer keyring of signer1180   * @param collectionId ID of collection1181   * @param properties array of property objects1182   * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1183   * @returns ```true``` if extrinsic success, otherwise ```false```1184   */1185  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1186    const result = await this.helper.executeExtrinsic(1187      signer,1188      'api.tx.unique.setCollectionProperties', [collectionId, properties],1189      true,1190    );11911192    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1193  }11941195  /**1196   * Get collection properties.1197   *1198   * @param collectionId ID of collection1199   * @param propertyKeys optionally filter the returned properties to only these keys1200   * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1201   * @returns array of key-value pairs1202   */1203  async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1204    return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1205  }12061207  async getPropertiesConsumedSpace(collectionId: number): Promise<number> {1208    const api = this.helper.getApi();1209    const props = (await api.query.common.collectionProperties(collectionId)).toJSON();12101211    return (props! as any).consumedSpace;1212  }12131214  async getCollectionOptions(collectionId: number) {1215    return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1216  }12171218  /**1219   * Deletes onchain properties from the collection.1220   *1221   * @param signer keyring of signer1222   * @param collectionId ID of collection1223   * @param propertyKeys array of property keys to delete1224   * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1225   * @returns ```true``` if extrinsic success, otherwise ```false```1226   */1227  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1228    const result = await this.helper.executeExtrinsic(1229      signer,1230      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1231      true,1232    );12331234    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1235  }12361237  /**1238   * Changes the owner of the token.1239   *1240   * @param signer keyring of signer1241   * @param collectionId ID of collection1242   * @param tokenId ID of token1243   * @param addressObj address of a new owner1244   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1245   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1246   * @returns true if the token success, otherwise false1247   */1248  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1249    const result = await this.helper.executeExtrinsic(1250      signer,1251      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1252      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1253    );12541255    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1256  }12571258  /**1259   *1260   * Change ownership of a token(s) on behalf of the owner.1261   *1262   * @param signer keyring of signer1263   * @param collectionId ID of collection1264   * @param tokenId ID of token1265   * @param fromAddressObj address on behalf of which the token will be sent1266   * @param toAddressObj new token owner1267   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1268   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1269   * @returns true if the token success, otherwise false1270   */1271  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1272    const result = await this.helper.executeExtrinsic(1273      signer,1274      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1275      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1276    );1277    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1278  }12791280  /**1281   *1282   * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1283   *1284   * @param signer keyring of signer1285   * @param collectionId ID of collection1286   * @param tokenId ID of token1287   * @param amount amount of tokens to be burned. For NFT must be set to 1n1288   * @example burnToken(aliceKeyring, 10, 5);1289   * @returns ```true``` if the extrinsic is successful, otherwise ```false```1290   */1291  async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {1292    const burnResult = await this.helper.executeExtrinsic(1293      signer,1294      'api.tx.unique.burnItem', [collectionId, tokenId, amount],1295      true, // `Unable to burn token for ${label}`,1296    );1297    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1298    if(burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1299    return burnedTokens.success;1300  }13011302  /**1303   * Destroys a concrete instance of NFT on behalf of the owner1304   *1305   * @param signer keyring of signer1306   * @param collectionId ID of collection1307   * @param tokenId ID of token1308   * @param fromAddressObj address on behalf of which the token will be burnt1309   * @param amount amount of tokens to be burned. For NFT must be set to 1n1310   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1311   * @returns ```true``` if extrinsic success, otherwise ```false```1312   */1313  async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1314    const burnResult = await this.helper.executeExtrinsic(1315      signer,1316      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1317      true, // `Unable to burn token from for ${label}`,1318    );1319    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1320    return burnedTokens.success && burnedTokens.tokens.length > 0;1321  }13221323  /**1324   * Set, change, or remove approved address to transfer the ownership of the NFT.1325   *1326   * @param signer keyring of signer1327   * @param collectionId ID of collection1328   * @param tokenId ID of token1329   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1330   * @param amount amount of token to be approved. For NFT must be set to 1n1331   * @returns ```true``` if extrinsic success, otherwise ```false```1332   */1333  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1334    const approveResult = await this.helper.executeExtrinsic(1335      signer,1336      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1337      true, // `Unable to approve token for ${label}`,1338    );13391340    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1341  }13421343  /**1344   * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1345   *1346   * @param signer keyring of signer1347   * @param collectionId ID of collection1348   * @param tokenId ID of token1349   * @param fromAddressObj Signer's Ethereum address containing her tokens1350   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1351   * @param amount amount of token to be approved. For NFT must be set to 1n1352   * @returns ```true``` if extrinsic success, otherwise ```false```1353   */1354  async approveTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {1355    const approveResult = await this.helper.executeExtrinsic(1356      signer,1357      'api.tx.unique.approveFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1358      true, // `Unable to approve token for ${label}`,1359    );13601361    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1362  }13631364  /**1365   * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1366   *1367   * @param signer keyring of signer1368   * @param collectionId ID of collection1369   * @param tokenId ID of token1370   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1371   * @param amount amount of token to be approved. For NFT must be set to 1n1372   * @returns ```true``` if extrinsic success, otherwise ```false```1373   */1374  async approveTokenFromEth(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1375    const ethMirror = CrossAccountId.fromKeyring(signer).toEthereum();1376    return await this.approveTokenFrom(signer, collectionId, tokenId, ethMirror, toAddressObj, amount);1377  }13781379  /**1380   * Get the amount of token pieces approved to transfer or burn. Normally 0.1381   *1382   * @param collectionId ID of collection1383   * @param tokenId ID of token1384   * @param toAccountObj address which is approved to use token pieces1385   * @param fromAccountObj address which may have allowed the use of its owned tokens1386   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1387   * @returns number of approved to transfer pieces1388   */1389  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1390    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1391  }13921393  /**1394   * Get the last created token ID in a collection1395   *1396   * @param collectionId ID of collection1397   * @example getLastTokenId(10);1398   * @returns id of the last created token1399   */1400  async getLastTokenId(collectionId: number): Promise<number> {1401    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1402  }14031404  /**1405   * Check if token exists1406   *1407   * @param collectionId ID of collection1408   * @param tokenId ID of token1409   * @example doesTokenExist(10, 20);1410   * @returns true if the token exists, otherwise false1411   */1412  async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1413    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1414  }1415}14161417class NFTnRFT extends CollectionGroup {1418  /**1419   * Get tokens owned by account1420   *1421   * @param collectionId ID of collection1422   * @param addressObj tokens owner1423   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1424   * @returns array of token ids owned by account1425   */1426  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1427    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1428  }14291430  /**1431   * Get token data1432   *1433   * @param collectionId ID of collection1434   * @param tokenId ID of token1435   * @param propertyKeys optionally filter the token properties to only these keys1436   * @param blockHashAt optionally query the data at some block with this hash1437   * @example getToken(10, 5);1438   * @returns human readable token data1439   */1440  async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1441    properties: IProperty[];1442    owner: CrossAccountId;1443    normalizedOwner: CrossAccountId;1444  } | null> {1445    let tokenData;1446    if(typeof blockHashAt === 'undefined') {1447      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1448    }1449    else {1450      if(propertyKeys.length == 0) {1451        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1452        if(!collection) return null;1453        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1454      }1455      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1456    }1457    tokenData = tokenData.toHuman();1458    if(tokenData === null || tokenData.owner === null) return null;1459    const owner = {} as any;1460    for(const key of Object.keys(tokenData.owner)) {1461      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1462        ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1463        : tokenData.owner[key];1464    }1465    tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1466    return tokenData;1467  }14681469  /**1470   * Get token's owner1471   * @param collectionId ID of collection1472   * @param tokenId ID of token1473   * @param blockHashAt optionally query the data at the block with this hash1474   * @example getTokenOwner(10, 5);1475   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1476   */1477  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1478    let owner;1479    if(typeof blockHashAt === 'undefined') {1480      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1481    } else {1482      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1483    }1484    return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1485  }14861487  /**1488   * Recursively find the address that owns the token1489   * @param collectionId ID of collection1490   * @param tokenId ID of token1491   * @param blockHashAt1492   * @example getTokenTopmostOwner(10, 5);1493   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1494   */1495  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1496    let owner;1497    if(typeof blockHashAt === 'undefined') {1498      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1499    } else {1500      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1501    }15021503    if(owner === null) return null;15041505    return owner.toHuman();1506  }15071508  /**1509   * Nest one token into another1510   * @param signer keyring of signer1511   * @param tokenObj token to be nested1512   * @param rootTokenObj token to be parent1513   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1514   * @returns ```true``` if extrinsic success, otherwise ```false```1515   */1516  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1517    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1518    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1519    if(!result) {1520      throw Error('Unable to nest token!');1521    }1522    return result;1523  }15241525  /**1526     * Remove token from nested state1527     * @param signer keyring of signer1528     * @param tokenObj token to unnest1529     * @param rootTokenObj parent of a token1530     * @param toAddressObj address of a new token owner1531     * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1532     * @returns ```true``` if extrinsic success, otherwise ```false```1533     */1534  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1535    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1536    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1537    if(!result) {1538      throw Error('Unable to unnest token!');1539    }1540    return result;1541  }15421543  /**1544   * Set permissions to change token properties1545   *1546   * @param signer keyring of signer1547   * @param collectionId ID of collection1548   * @param permissions permissions to change a property by the collection admin or token owner1549   * @example setTokenPropertyPermissions(1550   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1551   * )1552   * @returns true if extrinsic success otherwise false1553   */1554  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1555    const result = await this.helper.executeExtrinsic(1556      signer,1557      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1558      true,1559    );15601561    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1562  }15631564  /**1565   * Get token property permissions.1566   *1567   * @param collectionId ID of collection1568   * @param propertyKeys optionally filter the returned property permissions to only these keys1569   * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1570   * @returns array of key-permission pairs1571   */1572  async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1573    return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1574  }15751576  /**1577   * Set token properties1578   *1579   * @param signer keyring of signer1580   * @param collectionId ID of collection1581   * @param tokenId ID of token1582   * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1583   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1584   * @returns ```true``` if extrinsic success, otherwise ```false```1585   */1586  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1587    const result = await this.helper.executeExtrinsic(1588      signer,1589      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1590      true,1591    );15921593    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1594  }15951596  /**1597   * Get properties, metadata assigned to a token.1598   *1599   * @param collectionId ID of collection1600   * @param tokenId ID of token1601   * @param propertyKeys optionally filter the returned properties to only these keys1602   * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1603   * @returns array of key-value pairs1604   */1605  async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1606    return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1607  }16081609  /**1610   * Delete the provided properties of a token1611   * @param signer keyring of signer1612   * @param collectionId ID of collection1613   * @param tokenId ID of token1614   * @param propertyKeys property keys to be deleted1615   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1616   * @returns ```true``` if extrinsic success, otherwise ```false```1617   */1618  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1619    const result = await this.helper.executeExtrinsic(1620      signer,1621      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1622      true,1623    );16241625    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1626  }16271628  /**1629   * Mint new collection1630   *1631   * @param signer keyring of signer1632   * @param collectionOptions basic collection options and properties1633   * @param mode NFT or RFT type of a collection1634   * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1635   * @returns object of the created collection1636   */1637  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1638    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1639    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1640    for(const key of ['name', 'description', 'tokenPrefix']) {1641      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);1642    }16431644    let flags = 0;1645    // convert CollectionFlags to number and join them in one number1646    if(collectionOptions.flags) {1647      for(let i = 0; i < collectionOptions.flags.length; i++){1648        const flag = collectionOptions.flags[i];1649        flags = flags | flag;1650      }1651    }1652    collectionOptions.flags = [flags];16531654    const creationResult = await this.helper.executeExtrinsic(1655      signer,1656      'api.tx.unique.createCollectionEx', [collectionOptions],1657      true, // errorLabel,1658    );1659    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1660  }16611662  getCollectionObject(_collectionId: number): any {1663    return null;1664  }16651666  getTokenObject(_collectionId: number, _tokenId: number): any {1667    return null;1668  }16691670  /**1671   * Tells whether the given `owner` approves the `operator`.1672   * @param collectionId ID of collection1673   * @param owner owner address1674   * @param operator operator addrees1675   * @returns true if operator is enabled1676   */1677  async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1678    return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1679  }16801681  /** Sets or unsets the approval of a given operator.1682   *  The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1683   *  @param operator Operator1684   *  @param approved Should operator status be granted or revoked?1685   *  @returns ```true``` if extrinsic success, otherwise ```false```1686   */1687  async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1688    const result = await this.helper.executeExtrinsic(1689      signer,1690      'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1691      true,1692    );1693    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1694  }1695}169616971698class NFTGroup extends NFTnRFT {1699  /**1700   * Get collection object1701   * @param collectionId ID of collection1702   * @example getCollectionObject(2);1703   * @returns instance of UniqueNFTCollection1704   */1705  getCollectionObject(collectionId: number): UniqueNFTCollection {1706    return new UniqueNFTCollection(collectionId, this.helper);1707  }17081709  /**1710   * Get token object1711   * @param collectionId ID of collection1712   * @param tokenId ID of token1713   * @example getTokenObject(10, 5);1714   * @returns instance of UniqueNFTToken1715   */1716  getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1717    return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1718  }17191720  /**1721   * Is token approved to transfer1722   * @param collectionId ID of collection1723   * @param tokenId ID of token1724   * @param toAccountObj address to be approved1725   * @returns ```true``` if extrinsic success, otherwise ```false```1726   */1727  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1728    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1729  }17301731  /**1732   * Changes the owner of the token.1733   *1734   * @param signer keyring of signer1735   * @param collectionId ID of collection1736   * @param tokenId ID of token1737   * @param addressObj address of a new owner1738   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1739   * @returns ```true``` if extrinsic success, otherwise ```false```1740   */1741  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1742    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1743  }17441745  /**1746   *1747   * Change ownership of a NFT on behalf of the owner.1748   *1749   * @param signer keyring of signer1750   * @param collectionId ID of collection1751   * @param tokenId ID of token1752   * @param fromAddressObj address on behalf of which the token will be sent1753   * @param toAddressObj new token owner1754   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1755   * @returns ```true``` if extrinsic success, otherwise ```false```1756   */1757  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1758    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1759  }17601761  /**1762   * Get tokens nested in the provided token1763   * @param collectionId ID of collection1764   * @param tokenId ID of token1765   * @param blockHashAt optionally query the data at the block with this hash1766   * @example getTokenChildren(10, 5);1767   * @returns tokens whose depth of nesting is <= 51768   */1769  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1770    let children;1771    if(typeof blockHashAt === 'undefined') {1772      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1773    } else {1774      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1775    }17761777    return children.toJSON().map((x: any) => ({collectionId: x.collection, tokenId: x.token}));1778  }17791780  /**1781   * Mint new collection1782   * @param signer keyring of signer1783   * @param collectionOptions Collection options1784   * @example1785   * mintCollection(aliceKeyring, {1786   *   name: 'New',1787   *   description: 'New collection',1788   *   tokenPrefix: 'NEW',1789   * })1790   * @returns object of the created collection1791   */1792  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1793    return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1794  }17951796  /**1797   * Mint new token1798   * @param signer keyring of signer1799   * @param data token data1800   * @returns created token object1801   */1802  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1803    const creationResult = await this.helper.executeExtrinsic(1804      signer,1805      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1806        NFT: {1807          properties: data.properties,1808        },1809      }],1810      true,1811    );1812    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1813    if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1814    if(createdTokens.tokens.length < 1) throw Error('No tokens minted');1815    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1816  }18171818  /**1819   * Mint multiple NFT tokens1820   * @param signer keyring of signer1821   * @param collectionId ID of collection1822   * @param tokens array of tokens with owner and properties1823   * @example1824   * mintMultipleTokens(aliceKeyring, 10, [{1825   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1826   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1827   *   },{1828   *     owner: {Ethereum: "0x9F0583DbB855d..."},1829   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1830   * }]);1831   * @returns ```true``` if extrinsic success, otherwise ```false```1832   */1833  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1834    const creationResult = await this.helper.executeExtrinsic(1835      signer,1836      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1837      true,1838    );1839    const collection = this.getCollectionObject(collectionId);1840    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1841  }18421843  /**1844   * Mint multiple NFT tokens with one owner1845   * @param signer keyring of signer1846   * @param collectionId ID of collection1847   * @param owner tokens owner1848   * @param tokens array of tokens with owner and properties1849   * @example1850   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1851   *   properties: [{1852   *   key: "gender",1853   *   value: "female",1854   *  },{1855   *   key: "age",1856   *   value: "33",1857   *  }],1858   * }]);1859   * @returns array of newly created tokens1860   */1861  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1862    const rawTokens = [];1863    for(const token of tokens) {1864      const raw = {NFT: {properties: token.properties}};1865      rawTokens.push(raw);1866    }1867    const creationResult = await this.helper.executeExtrinsic(1868      signer,1869      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1870      true,1871    );1872    const collection = this.getCollectionObject(collectionId);1873    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1874  }18751876  /**1877   * Set, change, or remove approved address to transfer the ownership of the NFT.1878   *1879   * @param signer keyring of signer1880   * @param collectionId ID of collection1881   * @param tokenId ID of token1882   * @param toAddressObj address to approve1883   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1884   * @returns ```true``` if extrinsic success, otherwise ```false```1885   */1886  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1887    return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1888  }1889}189018911892class RFTGroup extends NFTnRFT {1893  /**1894   * Get collection object1895   * @param collectionId ID of collection1896   * @example getCollectionObject(2);1897   * @returns instance of UniqueRFTCollection1898   */1899  getCollectionObject(collectionId: number): UniqueRFTCollection {1900    return new UniqueRFTCollection(collectionId, this.helper);1901  }19021903  /**1904   * Get token object1905   * @param collectionId ID of collection1906   * @param tokenId ID of token1907   * @example getTokenObject(10, 5);1908   * @returns instance of UniqueNFTToken1909   */1910  getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1911    return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1912  }19131914  /**1915   * Get top 10 token owners with the largest number of pieces1916   * @param collectionId ID of collection1917   * @param tokenId ID of token1918   * @example getTokenTop10Owners(10, 5);1919   * @returns array of top 10 owners1920   */1921  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1922    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1923  }19241925  /**1926   * Get number of pieces owned by address1927   * @param collectionId ID of collection1928   * @param tokenId ID of token1929   * @param addressObj address token owner1930   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1931   * @returns number of pieces ownerd by address1932   */1933  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1934    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1935  }19361937  /**1938   * Transfer pieces of token to another address1939   * @param signer keyring of signer1940   * @param collectionId ID of collection1941   * @param tokenId ID of token1942   * @param addressObj address of a new owner1943   * @param amount number of pieces to be transfered1944   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1945   * @returns ```true``` if extrinsic success, otherwise ```false```1946   */1947  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1948    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1949  }19501951  /**1952   * Change ownership of some pieces of RFT on behalf of the owner.1953   * @param signer keyring of signer1954   * @param collectionId ID of collection1955   * @param tokenId ID of token1956   * @param fromAddressObj address on behalf of which the token will be sent1957   * @param toAddressObj new token owner1958   * @param amount number of pieces to be transfered1959   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1960   * @returns ```true``` if extrinsic success, otherwise ```false```1961   */1962  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1963    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1964  }19651966  /**1967   * Mint new collection1968   * @param signer keyring of signer1969   * @param collectionOptions Collection options1970   * @example1971   * mintCollection(aliceKeyring, {1972   *   name: 'New',1973   *   description: 'New collection',1974   *   tokenPrefix: 'NEW',1975   * })1976   * @returns object of the created collection1977   */1978  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1979    return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1980  }19811982  /**1983   * Mint new token1984   * @param signer keyring of signer1985   * @param data token data1986   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1987   * @returns created token object1988   */1989  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1990    const creationResult = await this.helper.executeExtrinsic(1991      signer,1992      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1993        ReFungible: {1994          pieces: data.pieces,1995          properties: data.properties,1996        },1997      }],1998      true,1999    );2000    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);2001    if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');2002    if(createdTokens.tokens.length < 1) throw Error('No tokens minted');2003    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);2004  }20052006  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {2007    throw Error('Not implemented');2008    const creationResult = await this.helper.executeExtrinsic(2009      signer,2010      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],2011      true, // `Unable to mint RFT tokens for ${label}`,2012    );2013    const collection = this.getCollectionObject(collectionId);2014    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2015  }20162017  /**2018   * Mint multiple RFT tokens with one owner2019   * @param signer keyring of signer2020   * @param collectionId ID of collection2021   * @param owner tokens owner2022   * @param tokens array of tokens with properties and pieces2023   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);2024   * @returns array of newly created RFT tokens2025   */2026  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {2027    const rawTokens = [];2028    for(const token of tokens) {2029      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};2030      rawTokens.push(raw);2031    }2032    const creationResult = await this.helper.executeExtrinsic(2033      signer,2034      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2035      true,2036    );2037    const collection = this.getCollectionObject(collectionId);2038    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2039  }20402041  /**2042   * Destroys a concrete instance of RFT.2043   * @param signer keyring of signer2044   * @param collectionId ID of collection2045   * @param tokenId ID of token2046   * @param amount number of pieces to be burnt2047   * @example burnToken(aliceKeyring, 10, 5);2048   * @returns ```true``` if the extrinsic is successful, otherwise ```false```2049   */2050  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {2051    return await super.burnToken(signer, collectionId, tokenId, amount);2052  }20532054  /**2055   * Destroys a concrete instance of RFT on behalf of the owner.2056   * @param signer keyring of signer2057   * @param collectionId ID of collection2058   * @param tokenId ID of token2059   * @param fromAddressObj address on behalf of which the token will be burnt2060   * @param amount number of pieces to be burnt2061   * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)2062   * @returns ```true``` if extrinsic success, otherwise ```false```2063   */2064  async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2065    return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);2066  }20672068  /**2069   * Set, change, or remove approved address to transfer the ownership of the RFT.2070   *2071   * @param signer keyring of signer2072   * @param collectionId ID of collection2073   * @param tokenId ID of token2074   * @param toAddressObj address to approve2075   * @param amount number of pieces to be approved2076   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);2077   * @returns true if the token success, otherwise false2078   */2079  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {2080    return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);2081  }20822083  /**2084   * Get total number of pieces2085   * @param collectionId ID of collection2086   * @param tokenId ID of token2087   * @example getTokenTotalPieces(10, 5);2088   * @returns number of pieces2089   */2090  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {2091    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();2092  }20932094  /**2095   * Change number of token pieces. Signer must be the owner of all token pieces.2096   * @param signer keyring of signer2097   * @param collectionId ID of collection2098   * @param tokenId ID of token2099   * @param amount new number of pieces2100   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);2101   * @returns true if the repartion was success, otherwise false2102   */2103  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {2104    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);2105    const repartitionResult = await this.helper.executeExtrinsic(2106      signer,2107      'api.tx.unique.repartition', [collectionId, tokenId, amount],2108      true,2109    );2110    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');2111    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');2112  }2113}211421152116class FTGroup extends CollectionGroup {2117  /**2118   * Get collection object2119   * @param collectionId ID of collection2120   * @example getCollectionObject(2);2121   * @returns instance of UniqueFTCollection2122   */2123  getCollectionObject(collectionId: number): UniqueFTCollection {2124    return new UniqueFTCollection(collectionId, this.helper);2125  }21262127  /**2128   * Mint new fungible collection2129   * @param signer keyring of signer2130   * @param collectionOptions Collection options2131   * @param decimalPoints number of token decimals2132   * @example2133   * mintCollection(aliceKeyring, {2134   *   name: 'New',2135   *   description: 'New collection',2136   *   tokenPrefix: 'NEW',2137   * }, 18)2138   * @returns newly created fungible collection2139   */2140  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {2141    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object2142    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');2143    collectionOptions.mode = {fungible: decimalPoints};2144    for(const key of ['name', 'description', 'tokenPrefix']) {2145      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);2146    }2147    const creationResult = await this.helper.executeExtrinsic(2148      signer,2149      'api.tx.unique.createCollectionEx', [collectionOptions],2150      true,2151    );2152    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));2153  }21542155  /**2156   * Mint tokens2157   * @param signer keyring of signer2158   * @param collectionId ID of collection2159   * @param owner address owner of new tokens2160   * @param amount amount of tokens to be meanted2161   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);2162   * @returns ```true``` if extrinsic success, otherwise ```false```2163   */2164  async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {2165    const creationResult = await this.helper.executeExtrinsic(2166      signer,2167      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {2168        Fungible: {2169          value: amount,2170        },2171      }],2172      true, // `Unable to mint fungible tokens for ${label}`,2173    );2174    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2175  }21762177  /**2178   * Mint multiple Fungible tokens with one owner2179   * @param signer keyring of signer2180   * @param collectionId ID of collection2181   * @param owner tokens owner2182   * @param tokens array of tokens with properties and pieces2183   * @returns ```true``` if extrinsic success, otherwise ```false```2184   */2185  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: { value: bigint }[], owner: ICrossAccountId): Promise<boolean> {2186    const rawTokens = [];2187    for(const token of tokens) {2188      const raw = {Fungible: {Value: token.value}};2189      rawTokens.push(raw);2190    }2191    const creationResult = await this.helper.executeExtrinsic(2192      signer,2193      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2194      true,2195    );2196    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2197  }21982199  /**2200   * Get the top 10 owners with the largest balance for the Fungible collection2201   * @param collectionId ID of collection2202   * @example getTop10Owners(10);2203   * @returns array of ```ICrossAccountId```2204   */2205  async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2206    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2207  }22082209  /**2210   * Get account balance2211   * @param collectionId ID of collection2212   * @param addressObj address of owner2213   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2214   * @returns amount of fungible tokens owned by address2215   */2216  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2217    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2218  }22192220  /**2221   * Transfer tokens to address2222   * @param signer keyring of signer2223   * @param collectionId ID of collection2224   * @param toAddressObj address recipient2225   * @param amount amount of tokens to be sent2226   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2227   * @returns ```true``` if extrinsic success, otherwise ```false```2228   */2229  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2230    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2231  }22322233  /**2234   * Transfer some tokens on behalf of the owner.2235   * @param signer keyring of signer2236   * @param collectionId ID of collection2237   * @param fromAddressObj address on behalf of which tokens will be sent2238   * @param toAddressObj address where token to be sent2239   * @param amount number of tokens to be sent2240   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2241   * @returns ```true``` if extrinsic success, otherwise ```false```2242   */2243  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {2244    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2245  }22462247  /**2248   * Destroy some amount of tokens2249   * @param signer keyring of signer2250   * @param collectionId ID of collection2251   * @param amount amount of tokens to be destroyed2252   * @example burnTokens(aliceKeyring, 10, 1000n);2253   * @returns ```true``` if extrinsic success, otherwise ```false```2254   */2255  async burnTokens(signer: IKeyringPair, collectionId: number, amount = 1n): Promise<boolean> {2256    return await super.burnToken(signer, collectionId, 0, amount);2257  }22582259  /**2260   * Burn some tokens on behalf of the owner.2261   * @param signer keyring of signer2262   * @param collectionId ID of collection2263   * @param fromAddressObj address on behalf of which tokens will be burnt2264   * @param amount amount of tokens to be burnt2265   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2266   * @returns ```true``` if extrinsic success, otherwise ```false```2267   */2268  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2269    return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2270  }22712272  /**2273   * Get total collection supply2274   * @param collectionId2275   * @returns2276   */2277  async getTotalPieces(collectionId: number): Promise<bigint> {2278    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2279  }22802281  /**2282   * Set, change, or remove approved address to transfer tokens.2283   *2284   * @param signer keyring of signer2285   * @param collectionId ID of collection2286   * @param toAddressObj address to be approved2287   * @param amount amount of tokens to be approved2288   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2289   * @returns ```true``` if extrinsic success, otherwise ```false```2290   */2291  approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2292    return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2293  }22942295  /**2296   * Get amount of fungible tokens approved to transfer2297   * @param collectionId ID of collection2298   * @param fromAddressObj owner of tokens2299   * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2300   * @returns number of tokens approved for the transfer2301   */2302  getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2303    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2304  }2305}230623072308class ChainGroup extends HelperGroup<ChainHelperBase> {2309  /**2310   * Get system properties of a chain2311   * @example getChainProperties();2312   * @returns ss58Format, token decimals, and token symbol2313   */2314  getChainProperties(): IChainProperties {2315    const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2316    return {2317      ss58Format: properties.ss58Format.toJSON(),2318      tokenDecimals: properties.tokenDecimals.toJSON(),2319      tokenSymbol: properties.tokenSymbol.toJSON(),2320    };2321  }23222323  /**2324   * Get chain header2325   * @example getLatestBlockNumber();2326   * @returns the number of the last block2327   */2328  async getLatestBlockNumber(): Promise<number> {2329    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2330  }23312332  /**2333   * Get block hash by block number2334   * @param blockNumber number of block2335   * @example getBlockHashByNumber(12345);2336   * @returns hash of a block2337   */2338  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2339    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2340    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2341    return blockHash;2342  }23432344  // TODO add docs2345  async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2346    const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2347    if(!blockHash) return null;2348    return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2349  }23502351  /**2352   * Get latest relay block2353   * @returns {number} relay block2354   */2355  async getRelayBlockNumber(): Promise<bigint> {2356    const blockNumber = (await this.helper.callRpc('api.query.parachainSystem.validationData')).toJSON().relayParentNumber;2357    return BigInt(blockNumber);2358  }23592360  /**2361   * Get account nonce2362   * @param address substrate address2363   * @example getNonce("5GrwvaEF5zXb26Fz...");2364   * @returns number, account's nonce2365   */2366  async getNonce(address: TSubstrateAccount): Promise<number> {2367    return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2368  }2369}23702371export class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2372  /**2373 * Get substrate address balance2374 * @param address substrate address2375 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2376 * @returns amount of tokens on address2377 */2378  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2379    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2380  }23812382  /**2383   * Transfer tokens to substrate address2384   * @param signer keyring of signer2385   * @param address substrate address of a recipient2386   * @param amount amount of tokens to be transfered2387   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2388   * @returns ```true``` if extrinsic success, otherwise ```false```2389   */2390  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2391    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}`*/);23922393    let transfer = {from: null, to: null, amount: 0n} as any;2394    result.result.events.forEach(({event: {data, method, section}}) => {2395      if((section === 'balances') && (method === 'Transfer')) {2396        transfer = {2397          from: this.helper.address.normalizeSubstrate(data[0]),2398          to: this.helper.address.normalizeSubstrate(data[1]),2399          amount: BigInt(data[2]),2400        };2401      }2402    });2403    const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2404      && this.helper.address.normalizeSubstrate(address) === transfer.to2405      && BigInt(amount) === transfer.amount;2406    return isSuccess;2407  }24082409  /**2410   * Get full substrate balance including free, frozen, and reserved2411   * @param address substrate address2412   * @returns2413   */2414  async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2415    const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2416    return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2417  }24182419  /**2420   * Get total issuance2421   * @returns2422   */2423  async getTotalIssuance(): Promise<bigint> {2424    const total = (await this.helper.callRpc('api.query.balances.totalIssuance', []));2425    return total.toBigInt();2426  }24272428  async getLocked(address: TSubstrateAccount): Promise<{ id: string, amount: bigint, reason: string }[]> {2429    const locks = (await this.helper.callRpc('api.query.balances.locks', [address])).toHuman();2430    return locks.map((lock: any) => ({id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}));2431  }2432  async getFrozen(address: TSubstrateAccount): Promise<{ id: string, amount: bigint }[]> {2433    const locks = (await this.helper.api!.query.balances.freezes(address)) as unknown as Array<any>;2434    return locks.map(lock => ({id: lock.id.toUtf8(), amount: lock.amount.toBigInt()}));2435  }2436}24372438export class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2439  /**2440   * Get ethereum address balance2441   * @param address ethereum address2442   * @example getEthereum("0x9F0583DbB855d...")2443   * @returns amount of tokens on address2444   */2445  async getEthereum(address: TEthereumAccount): Promise<bigint> {2446    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2447  }24482449  /**2450   * Transfer tokens to address2451   * @param signer keyring of signer2452   * @param address Ethereum address of a recipient2453   * @param amount amount of tokens to be transfered2454   * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2455   * @returns ```true``` if extrinsic success, otherwise ```false```2456   */2457  async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2458    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);24592460    let transfer = {from: null, to: null, amount: 0n} as any;2461    result.result.events.forEach(({event: {data, method, section}}) => {2462      if((section === 'balances') && (method === 'Transfer')) {2463        transfer = {2464          from: data[0].toString(),2465          to: data[1].toString(),2466          amount: BigInt(data[2]),2467        };2468      }2469    });2470    const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2471      && address === transfer.to2472      && BigInt(amount) === transfer.amount;2473    return isSuccess;2474  }2475}24762477class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2478  subBalanceGroup: SubstrateBalanceGroup<T>;2479  ethBalanceGroup: EthereumBalanceGroup<T>;24802481  constructor(helper: T) {2482    super(helper);2483    this.subBalanceGroup = new SubstrateBalanceGroup(helper);2484    this.ethBalanceGroup = new EthereumBalanceGroup(helper);2485  }24862487  getCollectionCreationPrice(): bigint {2488    return 2n * this.getOneTokenNominal();2489  }2490  /**2491   * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2492   * @example getOneTokenNominal()2493   * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2494   */2495  getOneTokenNominal(): bigint {2496    const chainProperties = this.helper.chain.getChainProperties();2497    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2498  }24992500  /**2501   * Get substrate address balance2502   * @param address substrate address2503   * @example getSubstrate("5GrwvaEF5zXb26Fz...")2504   * @returns amount of tokens on address2505   */2506  getSubstrate(address: TSubstrateAccount): Promise<bigint> {2507    return this.subBalanceGroup.getSubstrate(address);2508  }25092510  /**2511   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2512   * @param address substrate address2513   * @returns2514   */2515  getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2516    return this.subBalanceGroup.getSubstrateFull(address);2517  }25182519  /**2520   * Get total issuance2521   * @returns2522   */2523  getTotalIssuance(): Promise<bigint> {2524    return this.subBalanceGroup.getTotalIssuance();2525  }25262527  /**2528   * Get locked balances2529   * @param address substrate address2530   * @returns locked balances with reason via api.query.balances.locks2531   * @deprecated all the methods should switch to getFrozen2532   */2533  getLocked(address: TSubstrateAccount) {2534    return this.subBalanceGroup.getLocked(address);2535  }25362537  /**2538   * Get frozen balances2539   * @param address substrate address2540   * @returns frozen balances with id via api.query.balances.freezes2541   */2542  getFrozen(address: TSubstrateAccount) {2543    return this.subBalanceGroup.getFrozen(address);2544  }25452546  /**2547   * Get ethereum address balance2548   * @param address ethereum address2549   * @example getEthereum("0x9F0583DbB855d...")2550   * @returns amount of tokens on address2551   */2552  getEthereum(address: TEthereumAccount): Promise<bigint> {2553    return this.ethBalanceGroup.getEthereum(address);2554  }25552556  async setBalanceSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint) {2557    await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceSetBalance', [address, amount], true);2558  }25592560  /**2561   * Transfer tokens to substrate address2562   * @param signer keyring of signer2563   * @param address substrate address of a recipient2564   * @param amount amount of tokens to be transfered2565   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2566   * @returns ```true``` if extrinsic success, otherwise ```false```2567   */2568  transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2569    return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2570  }25712572  async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2573    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);25742575    let transfer = {from: null, to: null, amount: 0n} as any;2576    result.result.events.forEach(({event: {data, method, section}}) => {2577      if((section === 'balances') && (method === 'Transfer')) {2578        transfer = {2579          from: this.helper.address.normalizeSubstrate(data[0]),2580          to: this.helper.address.normalizeSubstrate(data[1]),2581          amount: BigInt(data[2]),2582        };2583      }2584    });2585    let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2586    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2587    isSuccess = isSuccess && BigInt(amount) === transfer.amount;2588    return isSuccess;2589  }25902591  /**2592   * Transfer tokens with the unlock period2593   * @param signer signers Keyring2594   * @param address Substrate address of recipient2595   * @param schedule Schedule params2596   * @example vestedTransfer(signer, recepient.address, 20000, 100, 10, 50 * nominal); // total amount of vested tokens will be 100 * 50 = 50002597   */2598  async vestedTransfer(signer: TSigner, address: TSubstrateAccount, schedule: { start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }): Promise<void> {2599    const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.vestedTransfer', [address, schedule]);2600    const event = result.result.events2601      .find(e => e.event.section === 'vesting' &&2602        e.event.method === 'VestingScheduleAdded' &&2603        e.event.data[0].toHuman() === signer.address);2604    if(!event) throw Error('Cannot find transfer in events');2605  }26062607  /**2608   * Get schedule for recepient of vested transfer2609   * @param address Substrate address of recipient2610   * @returns2611   */2612  async getVestingSchedules(address: TSubstrateAccount): Promise<{ start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }[]> {2613    const schedule = (await this.helper.callRpc('api.query.vesting.vestingSchedules', [address])).toJSON();2614    return schedule.map((schedule: any) => ({2615      start: BigInt(schedule.start),2616      period: BigInt(schedule.period),2617      periodCount: BigInt(schedule.periodCount),2618      perPeriod: BigInt(schedule.perPeriod),2619    }));2620  }26212622  /**2623   * Claim vested tokens2624   * @param signer signers Keyring2625   */2626  async claim(signer: TSigner) {2627    const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.claim', []);2628    const event = result.result.events2629      .find(e => e.event.section === 'vesting' &&2630        e.event.method === 'Claimed' &&2631        e.event.data[0].toHuman() === signer.address);2632    if(!event) throw Error('Cannot find claim in events');2633  }2634}26352636class AddressGroup extends HelperGroup<ChainHelperBase> {2637  /**2638   * Normalizes the address to the specified ss58 format, by default ```42```.2639   * @param address substrate address2640   * @param ss58Format format for address conversion, by default ```42```2641   * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2642   * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2643   */2644  normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2645    return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2646  }26472648  /**2649   * Get address in the connected chain format2650   * @param address substrate address2651   * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2652   * @returns address in chain format2653   */2654  normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2655    return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2656  }26572658  /**2659   * Get substrate mirror of an ethereum address2660   * @param ethAddress ethereum address2661   * @param toChainFormat false for normalized account2662   * @example ethToSubstrate('0x9F0583DbB855d...')2663   * @returns substrate mirror of a provided ethereum address2664   */2665  ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat = false): TSubstrateAccount {2666    return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2667  }26682669  /**2670   * Get ethereum mirror of a substrate address2671   * @param subAddress substrate account2672   * @example substrateToEth("5DnSF6RRjwteE3BrC...")2673   * @returns ethereum mirror of a provided substrate address2674   */2675  substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2676    return CrossAccountId.translateSubToEth(subAddress);2677  }26782679  /**2680   * Encode key to substrate address2681   * @param key key for encoding address2682   * @param ss58Format prefix for encoding to the address of the corresponding network2683   * @returns encoded substrate address2684   */2685  encodeSubstrateAddress(key: Uint8Array | string | bigint, ss58Format = 42): string {2686    const u8a: Uint8Array = typeof key === 'string'2687      ? hexToU8a(key)2688      : typeof key === 'bigint'2689        ? hexToU8a(key.toString(16))2690        : key;26912692    if(ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2693      throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`);2694    }26952696    const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2697    if(!allowedDecodedLengths.includes(u8a.length)) {2698      throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2699    }27002701    const u8aPrefix = ss58Format < 642702      ? new Uint8Array([ss58Format])2703      : new Uint8Array([2704        ((ss58Format & 0xfc) >> 2) | 0x40,2705        (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2706      ]);27072708    const input = u8aConcat(u8aPrefix, u8a);27092710    return base58Encode(u8aConcat(2711      input,2712      blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2713    ));2714  }27152716  /**2717   * Restore substrate address from bigint representation2718   * @param number decimal representation of substrate address2719   * @returns substrate address2720   */2721  restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2722    if(this.helper.api === null) {2723      throw 'Not connected';2724    }2725    const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2726    if(res === undefined || res === null) {2727      throw 'Restore address error';2728    }2729    return res.toString();2730  }27312732  /**2733   * Convert etherium cross account id to substrate cross account id2734   * @param ethCrossAccount etherium cross account2735   * @returns substrate cross account id2736   */2737  convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2738    if(ethCrossAccount.sub === '0') {2739      return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2740    }27412742    const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2743    return {Substrate: ss58};2744  }27452746  paraSiblingSovereignAccount(paraid: number) {2747    // We are getting a *sibling* parachain sovereign account,2748    // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2749    const siblingPrefix = '0x7369626c';27502751    const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2752    const suffix = '000000000000000000000000000000000000000000000000';27532754    return siblingPrefix + encodedParaId + suffix;2755  }2756}275727582759class StakingGroup extends HelperGroup<UniqueHelper> {2760  /**2761   * Stake tokens for App Promotion2762   * @param signer keyring of signer2763   * @param amountToStake amount of tokens to stake2764   * @param label extra label for log2765   * @returns2766   */2767  async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2768    if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2769    const _stakeResult = await this.helper.executeExtrinsic(2770      signer, 'api.tx.appPromotion.stake',2771      [amountToStake], true,2772    );2773    // TODO extract info from stakeResult2774    return true;2775  }27762777  /**2778   * Unstake all staked tokens2779   * @param signer keyring of signer2780   * @param amountToUnstake amount of tokens to unstake2781   * @param label extra label for log2782   * @returns block hash where unstake happened2783   */2784  async unstakeAll(signer: TSigner, label?: string): Promise<string> {2785    if(typeof label === 'undefined') label = `${signer.address}`;2786    const unstakeResult = await this.helper.executeExtrinsic(2787      signer, 'api.tx.appPromotion.unstakeAll',2788      [], true,2789    );2790    return unstakeResult.blockHash;2791  }27922793  /**2794   * Unstake the part of a staked tokens2795   * @param signer keyring of signer2796   * @param amount amount of tokens to unstake2797   * @param label extra label for log2798   * @returns block hash where unstake happened2799   */2800  async unstakePartial(signer: TSigner, amount: bigint, label?: string): Promise<string> {2801    if(typeof label === 'undefined') label = `${signer.address}`;2802    const unstakeResult = await this.helper.executeExtrinsic(2803      signer, 'api.tx.appPromotion.unstakePartial',2804      [amount], true,2805    );2806    return unstakeResult.blockHash;2807  }28082809  /**2810   * Get total number of active stakes2811   * @param address substrate address2812   * @returns {number}2813   */2814  async getStakesNumber(address: ICrossAccountId): Promise<number> {2815    if('Ethereum' in address) throw Error('only substrate address');2816    return (await this.helper.callRpc('api.query.appPromotion.stakesPerAccount', [address.Substrate])).toNumber();2817  }28182819  /**2820   * Get total staked amount for address2821   * @param address substrate or ethereum address2822   * @returns total staked amount2823   */2824  async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2825    if(address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2826    return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2827  }28282829  /**2830   * Get total staked per block2831   * @param address substrate or ethereum address2832   * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2833   */2834  async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2835    const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2836    return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => ({2837      block: block.toBigInt(),2838      amount: amount.toBigInt(),2839    }));2840  }28412842  /**2843   * Get total pending unstake amount for address2844   * @param address substrate or ethereum address2845   * @returns total pending unstake amount2846   */2847  async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2848    return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2849  }28502851  /**2852   * Get pending unstake amount per block for address2853   * @param address substrate or ethereum address2854   * @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 block2855   */2856  async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2857    const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2858    const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => ({2859      block: block.toBigInt(),2860      amount: amount.toBigInt(),2861    }));2862    return result;2863  }2864}286528662867class PreimageGroup extends HelperGroup<UniqueHelper> {2868  async getPreimageInfo(h256: string) {2869    return (await this.helper.callRpc('api.query.preimage.statusFor', [h256])).toJSON();2870  }28712872  /**2873   * Create a preimage from an API call.2874   * @param signer keyring of the signer.2875   * @param call an extrinsic call2876   * @example await notePreimageFromCall(preimageMaker,2877   *   helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd])2878   * );2879   * @returns promise of extrinsic execution.2880   */2881  notePreimageFromCall(signer: TSigner, call: any, returnPreimageHash = false) {2882    return this.notePreimage(signer, call.method.toHex(), returnPreimageHash);2883  }28842885  /**2886   * Create a preimage with a hex or a byte array.2887   * @param signer keyring of the signer.2888   * @param bytes preimage encoded in hex or a byte array, e.g. an extrinsic call.2889   * @example await notePreimage(preimageMaker,2890   *   helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex()2891   * );2892   * @returns promise of extrinsic execution.2893   */2894  async notePreimage(signer: TSigner, bytes: string | Uint8Array, returnPreimageHash = false) {2895    const promise = this.helper.executeExtrinsic(signer, 'api.tx.preimage.notePreimage', [bytes]);2896    if(returnPreimageHash) {2897      const result = await promise;2898      const events = result.result.events.filter(x => x.event.method === 'Noted' && x.event.section === 'preimage');2899      const preimageHash = events[0].event.data[0].toHuman();2900      return preimageHash;2901    }2902    return promise;2903  }29042905  /**2906   * Delete an existing preimage and return the deposit.2907   * @param signer keyring of the signer - either the owner or the preimage manager (sudo).2908   * @param h256 hash of the preimage.2909   * @returns promise of extrinsic execution.2910   */2911  unnotePreimage(signer: TSigner, h256: string) {2912    return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unnotePreimage', [h256]);2913  }29142915  /**2916   * Request a preimage be uploaded to the chain without paying any fees or deposits.2917   * @param signer keyring of the signer - either the owner or the preimage manager (sudo).2918   * @param h256 hash of the preimage.2919   * @returns promise of extrinsic execution.2920   */2921  requestPreimage(signer: TSigner, h256: string) {2922    return this.helper.executeExtrinsic(signer, 'api.tx.preimage.requestPreimage', [h256]);2923  }29242925  /**2926   * Clear a previously made request for a preimage.2927   * @param signer keyring of the signer - either the owner or the preimage manager (sudo).2928   * @param h256 hash of the preimage.2929   * @returns promise of extrinsic execution.2930   */2931  unrequestPreimage(signer: TSigner, h256: string) {2932    return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]);2933  }2934}29352936class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {2937  async batch(signer: TSigner, txs: any[]) {2938    return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batch', [txs]);2939  }29402941  async batchAll(signer: TSigner, txs: any[]) {2942    return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batchAll', [txs]);2943  }29442945  batchAllCall(txs: any[]) {2946    return this.helper.constructApiCall('api.tx.utility.batchAll', [txs]);2947  }2948}29492950export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;2951export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;29522953export class UniqueHelper extends ChainHelperBase {2954  balance: BalanceGroup<UniqueHelper>;2955  collection: CollectionGroup;2956  nft: NFTGroup;2957  rft: RFTGroup;2958  ft: FTGroup;2959  staking: StakingGroup;2960  preimage: PreimageGroup;2961  utility: UtilityGroup<UniqueHelper>;29622963  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {2964    super(logger, options.helperBase ?? UniqueHelper);29652966    this.balance = new BalanceGroup(this);2967    this.collection = new CollectionGroup(this);2968    this.nft = new NFTGroup(this);2969    this.rft = new RFTGroup(this);2970    this.ft = new FTGroup(this);2971    this.staking = new StakingGroup(this);2972    this.preimage = new PreimageGroup(this);2973    this.utility = new UtilityGroup(this);2974  }2975}29762977export class UniqueBaseCollection {2978  helper: UniqueHelper;2979  collectionId: number;29802981  constructor(collectionId: number, uniqueHelper: UniqueHelper) {2982    this.collectionId = collectionId;2983    this.helper = uniqueHelper;2984  }29852986  async getData() {2987    return await this.helper.collection.getData(this.collectionId);2988  }29892990  async getLastTokenId() {2991    return await this.helper.collection.getLastTokenId(this.collectionId);2992  }29932994  async doesTokenExist(tokenId: number) {2995    return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);2996  }29972998  async getAdmins() {2999    return await this.helper.collection.getAdmins(this.collectionId);3000  }30013002  async getAllowList() {3003    return await this.helper.collection.getAllowList(this.collectionId);3004  }30053006  async getEffectiveLimits() {3007    return await this.helper.collection.getEffectiveLimits(this.collectionId);3008  }30093010  async getProperties(propertyKeys?: string[] | null) {3011    return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3012  }30133014  async getPropertiesConsumedSpace() {3015    return await this.helper.collection.getPropertiesConsumedSpace(this.collectionId);3016  }30173018  async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3019    return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3020  }30213022  async getOptions() {3023    return await this.helper.collection.getCollectionOptions(this.collectionId);3024  }30253026  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {3027    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);3028  }30293030  async confirmSponsorship(signer: TSigner) {3031    return await this.helper.collection.confirmSponsorship(signer, this.collectionId);3032  }30333034  async removeSponsor(signer: TSigner) {3035    return await this.helper.collection.removeSponsor(signer, this.collectionId);3036  }30373038  async setLimits(signer: TSigner, limits: ICollectionLimits) {3039    return await this.helper.collection.setLimits(signer, this.collectionId, limits);3040  }30413042  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {3043    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);3044  }30453046  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3047    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);3048  }30493050  async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {3051    return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);3052  }30533054  async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {3055    return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);3056  }30573058  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3059    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);3060  }30613062  async setProperties(signer: TSigner, properties: IProperty[]) {3063    return await this.helper.collection.setProperties(signer, this.collectionId, properties);3064  }30653066  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3067    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);3068  }30693070  async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {3071    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);3072  }30733074  async enableNesting(signer: TSigner, permissions: INestingPermissions) {3075    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);3076  }30773078  async disableNesting(signer: TSigner) {3079    return await this.helper.collection.disableNesting(signer, this.collectionId);3080  }30813082  async burn(signer: TSigner) {3083    return await this.helper.collection.burn(signer, this.collectionId);3084  }3085}30863087export class UniqueNFTCollection extends UniqueBaseCollection {3088  getTokenObject(tokenId: number) {3089    return new UniqueNFToken(tokenId, this);3090  }30913092  async getTokensByAddress(addressObj: ICrossAccountId) {3093    return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);3094  }30953096  async getToken(tokenId: number, blockHashAt?: string) {3097    return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);3098  }30993100  async getTokenOwner(tokenId: number, blockHashAt?: string) {3101    return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3102  }31033104  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3105    return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3106  }31073108  async getTokenChildren(tokenId: number, blockHashAt?: string) {3109    return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);3110  }31113112  async getPropertyPermissions(propertyKeys: string[] | null = null) {3113    return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);3114  }31153116  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3117    return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3118  }31193120  async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {3121    const api = this.helper.getApi();3122    const props = (await api.query.nonfungible.tokenProperties(this.collectionId, tokenId)).toJSON();31233124    return (props! as any).consumedSpace;3125  }31263127  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {3128    return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);3129  }31303131  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3132    return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);3133  }31343135  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {3136    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);3137  }31383139  async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {3140    return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);3141  }31423143  async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3144    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});3145  }31463147  async mintMultipleTokens(signer: TSigner, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]) {3148    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);3149  }31503151  async burnToken(signer: TSigner, tokenId: number) {3152    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);3153  }31543155  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {3156    return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);3157  }31583159  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3160    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);3161  }31623163  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3164    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3165  }31663167  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3168    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3169  }31703171  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3172    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3173  }31743175  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3176    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3177  }3178}31793180export class UniqueRFTCollection extends UniqueBaseCollection {3181  getTokenObject(tokenId: number) {3182    return new UniqueRFToken(tokenId, this);3183  }31843185  async getToken(tokenId: number, blockHashAt?: string) {3186    return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3187  }31883189  async getTokenOwner(tokenId: number, blockHashAt?: string) {3190    return await this.helper.rft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3191  }31923193  async getTokensByAddress(addressObj: ICrossAccountId) {3194    return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3195  }31963197  async getTop10TokenOwners(tokenId: number) {3198    return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3199  }32003201  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3202    return await this.helper.rft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3203  }32043205  async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3206    return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3207  }32083209  async getTokenTotalPieces(tokenId: number) {3210    return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3211  }32123213  async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3214    return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3215  }32163217  async getPropertyPermissions(propertyKeys: string[] | null = null) {3218    return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3219  }32203221  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3222    return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3223  }32243225  async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {3226    const api = this.helper.getApi();3227    const props = (await api.query.refungible.tokenProperties(this.collectionId, tokenId)).toJSON();32283229    return (props! as any).consumedSpace;3230  }32313232  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount = 1n) {3233    return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3234  }32353236  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {3237    return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3238  }32393240  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {3241    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3242  }32433244  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3245    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3246  }32473248  async mintToken(signer: TSigner, pieces = 1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3249    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3250  }32513252  async mintMultipleTokens(signer: TSigner, tokens: { pieces: bigint, owner: ICrossAccountId, properties?: IProperty[] }[]) {3253    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3254  }32553256  async burnToken(signer: TSigner, tokenId: number, amount = 1n) {3257    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3258  }32593260  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n) {3261    return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3262  }32633264  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3265    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3266  }32673268  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3269    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3270  }32713272  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3273    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3274  }32753276  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3277    return await this.helper.rft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3278  }32793280  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3281    return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3282  }3283}32843285export class UniqueFTCollection extends UniqueBaseCollection {3286  async getBalance(addressObj: ICrossAccountId) {3287    return await this.helper.ft.getBalance(this.collectionId, addressObj);3288  }32893290  async getTotalPieces() {3291    return await this.helper.ft.getTotalPieces(this.collectionId);3292  }32933294  async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3295    return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3296  }32973298  async getTop10Owners() {3299    return await this.helper.ft.getTop10Owners(this.collectionId);3300  }33013302  async mint(signer: TSigner, amount = 1n, owner: ICrossAccountId = {Substrate: signer.address}) {3303    return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3304  }33053306  async mintWithOneOwner(signer: TSigner, tokens: { value: bigint }[], owner: ICrossAccountId = {Substrate: signer.address}) {3307    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3308  }33093310  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {3311    return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3312  }33133314  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {3315    return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3316  }33173318  async burnTokens(signer: TSigner, amount = 1n) {3319    return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3320  }33213322  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {3323    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3324  }33253326  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {3327    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3328  }3329}33303331export class UniqueBaseToken {3332  collection: UniqueNFTCollection | UniqueRFTCollection;3333  collectionId: number;3334  tokenId: number;33353336  constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3337    this.collection = collection;3338    this.collectionId = collection.collectionId;3339    this.tokenId = tokenId;3340  }33413342  async getNextSponsored(addressObj: ICrossAccountId) {3343    return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3344  }33453346  async getProperties(propertyKeys?: string[] | null) {3347    return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3348  }33493350  async getTokenPropertiesConsumedSpace() {3351    return await this.collection.getTokenPropertiesConsumedSpace(this.tokenId);3352  }33533354  async setProperties(signer: TSigner, properties: IProperty[]) {3355    return await this.collection.setTokenProperties(signer, this.tokenId, properties);3356  }33573358  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3359    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3360  }33613362  async doesExist() {3363    return await this.collection.doesTokenExist(this.tokenId);3364  }33653366  nestingAccount() {3367    return this.collection.helper.util.getTokenAccount(this);3368  }3369}33703371export class UniqueNFToken extends UniqueBaseToken {3372  collection: UniqueNFTCollection;33733374  constructor(tokenId: number, collection: UniqueNFTCollection) {3375    super(tokenId, collection);3376    this.collection = collection;3377  }33783379  async getData(blockHashAt?: string) {3380    return await this.collection.getToken(this.tokenId, blockHashAt);3381  }33823383  async getOwner(blockHashAt?: string) {3384    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3385  }33863387  async getTopmostOwner(blockHashAt?: string) {3388    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3389  }33903391  async getChildren(blockHashAt?: string) {3392    return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3393  }33943395  async nest(signer: TSigner, toTokenObj: IToken) {3396    return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3397  }33983399  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3400    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3401  }34023403  async transfer(signer: TSigner, addressObj: ICrossAccountId) {3404    return await this.collection.transferToken(signer, this.tokenId, addressObj);3405  }34063407  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3408    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3409  }34103411  async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3412    return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3413  }34143415  async isApproved(toAddressObj: ICrossAccountId) {3416    return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3417  }34183419  async burn(signer: TSigner) {3420    return await this.collection.burnToken(signer, this.tokenId);3421  }34223423  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3424    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3425  }3426}34273428export class UniqueRFToken extends UniqueBaseToken {3429  collection: UniqueRFTCollection;34303431  constructor(tokenId: number, collection: UniqueRFTCollection) {3432    super(tokenId, collection);3433    this.collection = collection;3434  }34353436  async getData(blockHashAt?: string) {3437    return await this.collection.getToken(this.tokenId, blockHashAt);3438  }34393440  async getOwner(blockHashAt?: string) {3441    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3442  }34433444  async getTop10Owners() {3445    return await this.collection.getTop10TokenOwners(this.tokenId);3446  }34473448  async getTopmostOwner(blockHashAt?: string) {3449    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3450  }34513452  async nest(signer: TSigner, toTokenObj: IToken) {3453    return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3454  }34553456  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3457    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3458  }34593460  async getBalance(addressObj: ICrossAccountId) {3461    return await this.collection.getTokenBalance(this.tokenId, addressObj);3462  }34633464  async getTotalPieces() {3465    return await this.collection.getTokenTotalPieces(this.tokenId);3466  }34673468  async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3469    return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3470  }34713472  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount = 1n) {3473    return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3474  }34753476  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {3477    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3478  }34793480  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {3481    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3482  }34833484  async repartition(signer: TSigner, amount: bigint) {3485    return await this.collection.repartitionToken(signer, this.tokenId, amount);3486  }34873488  async burn(signer: TSigner, amount = 1n) {3489    return await this.collection.burnToken(signer, this.tokenId, amount);3490  }34913492  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {3493    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3494  }3495}