git.delta.rocks / unique-network / refs/commits / 383d45a29a75

difftreelog

source

tests/src/util/playgrounds/unique.ts168.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  IForeignAssetMetadata,43  IEthCrossAccountId,44  IPhasicEvent,45} from './types';46import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';47import type {Vec} from '@polkadot/types-codec';48import {FrameSystemEventRecord, PalletDemocracyConviction} from '@polkadot/types/lookup';4950export class CrossAccountId {51  Substrate!: TSubstrateAccount;52  Ethereum!: TEthereumAccount;5354  constructor(account: ICrossAccountId) {55    if('Substrate' in account) this.Substrate = account.Substrate;56    else this.Ethereum = account.Ethereum;57  }5859  static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {60    switch (domain) {61      case 'Substrate': return new CrossAccountId({Substrate: account.address});62      case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();63    }64  }6566  static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {67    if('substrate' in address) return new CrossAccountId({Substrate: address.substrate});68    else return new CrossAccountId({Ethereum: address.ethereum});69  }7071  static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {72    return encodeAddress(decodeAddress(address), ss58Format);73  }7475  static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {76    return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});77  }7879  withNormalizedSubstrate(ss58Format = 42): CrossAccountId {80    if(this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);81    return this;82  }8384  static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {85    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));86  }8788  toEthereum(): CrossAccountId {89    if(this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});90    return this;91  }9293  static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {94    return evmToAddress(address, ss58Format);95  }9697  toSubstrate(ss58Format?: number): CrossAccountId {98    if(this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});99    return this;100  }101102  toLowerCase(): CrossAccountId {103    if(this.Substrate) this.Substrate = this.Substrate.toLowerCase();104    if(this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();105    return this;106  }107}108109const nesting = {110  toChecksumAddress(address: string): string {111    if(typeof address === 'undefined') return '';112113    if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);114115    address = address.toLowerCase().replace(/^0x/i, '');116    const addressHash = keccakAsHex(address).replace(/^0x/i, '');117    const checksumAddress = ['0x'];118119    for(let i = 0; i < address.length; i++) {120      // If ith character is 8 to f then make it uppercase121      if(parseInt(addressHash[i], 16) > 7) {122        checksumAddress.push(address[i].toUpperCase());123      } else {124        checksumAddress.push(address[i]);125      }126    }127    return checksumAddress.join('');128  },129  tokenIdToAddress(collectionId: number, tokenId: number) {130    return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8, '0')}${tokenId.toString(16).padStart(8, '0')}`);131  },132};133134class UniqueUtil {135  static transactionStatus = {136    NOT_READY: 'NotReady',137    FAIL: 'Fail',138    SUCCESS: 'Success',139  };140141  static chainLogType = {142    EXTRINSIC: 'extrinsic',143    RPC: 'rpc',144  };145146  static getTokenAccount(token: IToken): CrossAccountId {147    return new CrossAccountId({Ethereum: this.getTokenAddress(token)});148  }149150  static getTokenAddress(token: IToken): string {151    return nesting.tokenIdToAddress(token.collectionId, token.tokenId);152  }153154  static getDefaultLogger(): ILogger {155    return {156      log(msg: any, level = 'INFO') {157        console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));158      },159      level: {160        ERROR: 'ERROR',161        WARNING: 'WARNING',162        INFO: 'INFO',163      },164    };165  }166167  static vec2str(arr: string[] | number[]) {168    return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');169  }170171  static str2vec(string: string) {172    if(typeof string !== 'string') return string;173    return Array.from(string).map(x => x.charCodeAt(0));174  }175176  static fromSeed(seed: string, ss58Format = 42) {177    const keyring = new Keyring({type: 'sr25519', ss58Format});178    return keyring.addFromUri(seed);179  }180181  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {182    if(creationResult.status !== this.transactionStatus.SUCCESS) {183      throw Error('Unable to create collection!');184    }185186    let collectionId = null;187    creationResult.result.events.forEach(({event: {data, method, section}}) => {188      if((section === 'common') && (method === 'CollectionCreated')) {189        collectionId = parseInt(data[0].toString(), 10);190      }191    });192193    if(collectionId === null) {194      throw Error('No CollectionCreated event was found!');195    }196197    return collectionId;198  }199200  static extractTokensFromCreationResult(creationResult: ITransactionResult): {201    success: boolean,202    tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],203  } {204    if(creationResult.status !== this.transactionStatus.SUCCESS) {205      throw Error('Unable to create tokens!');206    }207    let success = false;208    const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];209    creationResult.result.events.forEach(({event: {data, method, section}}) => {210      if(method === 'ExtrinsicSuccess') {211        success = true;212      } else if((section === 'common') && (method === 'ItemCreated')) {213        tokens.push({214          collectionId: parseInt(data[0].toString(), 10),215          tokenId: parseInt(data[1].toString(), 10),216          owner: data[2].toHuman(),217          amount: data[3].toBigInt(),218        });219      }220    });221    return {success, tokens};222  }223224  static extractTokensFromBurnResult(burnResult: ITransactionResult): {225    success: boolean,226    tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],227  } {228    if(burnResult.status !== this.transactionStatus.SUCCESS) {229      throw Error('Unable to burn tokens!');230    }231    let success = false;232    const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];233    burnResult.result.events.forEach(({event: {data, method, section}}) => {234      if(method === 'ExtrinsicSuccess') {235        success = true;236      } else if((section === 'common') && (method === 'ItemDestroyed')) {237        tokens.push({238          collectionId: parseInt(data[0].toString(), 10),239          tokenId: parseInt(data[1].toString(), 10),240          owner: data[2].toHuman(),241          amount: data[3].toBigInt(),242        });243      }244    });245    return {success, tokens};246  }247248  static findCollectionInEvents(events: { event: IEvent }[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {249    let eventId = null;250    events.forEach(({event: {data, method, section}}) => {251      if((section === expectedSection) && (method === expectedMethod)) {252        eventId = parseInt(data[0].toString(), 10);253      }254    });255256    if(eventId === null) {257      throw Error(`No ${expectedMethod} event was found!`);258    }259    return eventId === collectionId;260  }261262  static isTokenTransferSuccess(events: { event: IEvent }[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {263    const normalizeAddress = (address: string | ICrossAccountId) => {264      if(typeof address === 'string') return address;265      const obj = {} as any;266      Object.keys(address).forEach(k => {267        obj[k.toLocaleLowerCase()] = (address as any)[k];268      });269      if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);270      if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();271      return address;272    };273    let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;274    events.forEach(({event: {data, method, section}}) => {275      if((section === 'common') && (method === 'Transfer')) {276        const hData = (data as any).toJSON();277        transfer = {278          collectionId: hData[0],279          tokenId: hData[1],280          from: normalizeAddress(hData[2]),281          to: normalizeAddress(hData[3]),282          amount: BigInt(hData[4]),283        };284      }285    });286    let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;287    isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);288    isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);289    isSuccess = isSuccess && amount === transfer.amount;290    return isSuccess;291  }292293  static bigIntToDecimals(number: bigint, decimals = 18) {294    const numberStr = number.toString();295    const dotPos = numberStr.length - decimals;296297    if(dotPos <= 0) {298      return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;299    } else {300      const intPart = numberStr.substring(0, dotPos);301      const fractPart = numberStr.substring(dotPos);302      return intPart + '.' + fractPart;303    }304  }305}306307class UniqueEventHelper {308  private static extractIndex(index: any): [number, number] | string {309    if(index.toRawType() === '[u8;2]') return [index[0], index[1]];310    return index.toJSON();311  }312313  private static extractSub(data: any, subTypes: any): { [key: string]: any } {314    let obj: any = {};315    let index = 0;316317    if(data.entries) {318      for(const [key, value] of data.entries()) {319        obj[key] = this.extractData(value, subTypes[index]);320        index++;321      }322    } else obj = data.toJSON();323324    return obj;325  }326327  private static toHuman(data: any) {328    return data && data.toHuman ? data.toHuman() : `${data}`;329  }330331  private static extractData(data: any, type: any): any {332    if(!type) return this.toHuman(data);333    if(['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();334    if(['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();335    if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);336    return this.toHuman(data);337  }338339  public static extractEvents(events: { event: any, phase: any }[]): IEvent[] {340    const parsedEvents: IEvent[] = [];341342    events.forEach((record) => {343      const {event, phase} = record;344      const types = event.typeDef;345346      const eventData: IEvent = {347        section: event.section.toString(),348        method: event.method.toString(),349        index: this.extractIndex(event.index),350        data: [],351        phase: phase.toJSON(),352      };353354      event.data.forEach((val: any, index: number) => {355        eventData.data.push(this.extractData(val, types[index]));356      });357358      parsedEvents.push(eventData);359    });360361    return parsedEvents;362  }363}364const InvalidTypeSymbol = Symbol('Invalid type');365// eslint-disable-next-line @typescript-eslint/no-unused-vars366export type Invalid<ErrorMessage> =367  | ((368    invalidType: typeof InvalidTypeSymbol,369    ..._: typeof InvalidTypeSymbol[]370  ) => typeof InvalidTypeSymbol)371  | null372  | undefined;373// Has slightly better error messages than Get374type Get2<T, P extends string, E> =375  P extends `${infer Key}.${infer Key2}` ? Key extends keyof T ? Key2 extends keyof T[Key] ? T[Key][Key2] : E : E : E;376type ForceFunction<T> = T extends (...args: any) => any ? T : (...args: any) => Invalid<'not a function'>;377378export class ChainHelperBase {379  helperBase: any;380381  transactionStatus = UniqueUtil.transactionStatus;382  chainLogType = UniqueUtil.chainLogType;383  util: typeof UniqueUtil;384  eventHelper: typeof UniqueEventHelper;385  logger: ILogger;386  api: ApiPromise | null;387  forcedNetwork: TNetworks | null;388  network: TNetworks | null;389  wsEndpoint: string | null;390  chainLog: IUniqueHelperLog[];391  children: ChainHelperBase[];392  address: AddressGroup;393  chain: ChainGroup;394395  constructor(logger?: ILogger, helperBase?: any) {396    this.helperBase = helperBase;397398    this.util = UniqueUtil;399    this.eventHelper = UniqueEventHelper;400    if(typeof logger == 'undefined') logger = this.util.getDefaultLogger();401    this.logger = logger;402    this.api = null;403    this.forcedNetwork = null;404    this.network = null;405    this.wsEndpoint = null;406    this.chainLog = [];407    this.children = [];408    this.address = new AddressGroup(this);409    this.chain = new ChainGroup(this);410  }411412  clone(helperCls: ChainHelperBaseConstructor, options: { [key: string]: any } = {}) {413    Object.setPrototypeOf(helperCls.prototype, this);414    const newHelper = new helperCls(this.logger, options);415416    newHelper.api = this.api;417    newHelper.network = this.network;418    newHelper.forceNetwork = this.forceNetwork;419420    this.children.push(newHelper);421422    return newHelper;423  }424425  getEndpoint(): string {426    if(this.wsEndpoint === null) throw Error('No connection was established');427    return this.wsEndpoint;428  }429430  getApi(): ApiPromise {431    if(this.api === null) throw Error('API not initialized');432    return this.api;433  }434435  async subscribeEvents(expectedEvents: { section: string, names: string[] }[]) {436    const collectedEvents: IEvent[] = [];437    const unsubscribe = await this.getApi().query.system.events((events: Vec<FrameSystemEventRecord>) => {438      const ievents = this.eventHelper.extractEvents(events);439      ievents.forEach((event) => {440        expectedEvents.forEach((e => {441          if(event.section === e.section && e.names.includes(event.method)) {442            collectedEvents.push(event);443          }444        }));445      });446    });447    return {unsubscribe: unsubscribe as any, collectedEvents};448  }449450  clearChainLog(): void {451    this.chainLog = [];452  }453454  forceNetwork(value: TNetworks): void {455    this.forcedNetwork = value;456  }457458  async connect(wsEndpoint: string, listeners?: IApiListeners) {459    if(this.api !== null) throw Error('Already connected');460    const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);461    this.wsEndpoint = wsEndpoint;462    this.api = api;463    this.network = network;464  }465466  async disconnect() {467    for(const child of this.children) {468      child.clearApi();469    }470471    if(this.api === null) return;472    await this.api.disconnect();473    this.clearApi();474  }475476  clearApi() {477    this.api = null;478    this.network = null;479  }480481  static async detectNetwork(api: ApiPromise): Promise<TNetworks> {482    const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;483    const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];484485    if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;486487    if(['quartz', 'unique', 'sapphire'].indexOf(spec.specName) > -1) return spec.specName;488    return 'opal';489  }490491  static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {492    if(!wsEndpoint) throw new Error('wsEndpoint was not set');493    const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});494    await api.isReady;495496    const network = await this.detectNetwork(api);497498    await api.disconnect();499500    return network;501  }502503  static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{504    api: ApiPromise;505    network: TNetworks;506  }> {507    if(typeof network === 'undefined' || network === null) network = 'opal';508    if(!wsEndpoint) throw new Error('wsEndpoint was not set');509    const supportedRPC = {510      opal: {511        unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,512      },513      quartz: {514        unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,515      },516      unique: {517        unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,518      },519      rococo: {},520      westend: {},521      moonbeam: {},522      moonriver: {},523      acala: {},524      karura: {},525      westmint: {},526    };527    if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);528    const rpc = supportedRPC[network];529530    // TODO: investigate how to replace rpc in runtime531    // api._rpcCore.addUserInterfaces(rpc);532533    const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});534535    await api.isReadyOrError;536537    if(typeof listeners === 'undefined') listeners = {};538    for(const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {539      if(!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;540      api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);541    }542543    return {api, network};544  }545546  getTransactionStatus(data: { events: { event: IEvent }[], status: any }) {547    const {events, status} = data;548    if(status.isReady) {549      return this.transactionStatus.NOT_READY;550    }551    if(status.isBroadcast) {552      return this.transactionStatus.NOT_READY;553    }554    if(status.isInBlock || status.isFinalized) {555      const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');556      if(errors.length > 0) {557        return this.transactionStatus.FAIL;558      }559      if(events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {560        return this.transactionStatus.SUCCESS;561      }562    }563564    return this.transactionStatus.FAIL;565  }566567  signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {568    const sign = (callback: any) => {569      if(options !== null) return transaction.signAndSend(sender, options, callback);570      return transaction.signAndSend(sender, callback);571    };572    // eslint-disable-next-line no-async-promise-executor573    return new Promise(async (resolve, reject) => {574      try {575        const unsub = await sign((result: any) => {576          const status = this.getTransactionStatus(result);577578          if(status === this.transactionStatus.SUCCESS) {579            this.logger.log(`${label} successful`);580            unsub();581            resolve({result, status, blockHash: result.status.asInBlock.toHuman()});582          } else if(status === this.transactionStatus.FAIL) {583            let moduleError = null;584585            if(result.hasOwnProperty('dispatchError')) {586              const dispatchError = result['dispatchError'];587588              if(dispatchError) {589                if(dispatchError.isModule) {590                  const modErr = dispatchError.asModule;591                  const errorMeta = dispatchError.registry.findMetaError(modErr);592593                  moduleError = `${errorMeta.section}.${errorMeta.name}`;594                } else if(dispatchError.isToken) {595                  moduleError = `Token: ${dispatchError.asToken}`;596                } else {597                  // May be [object Object] in case of unhandled non-unit enum598                  moduleError = `Misc: ${dispatchError.toHuman()}`;599                }600              } else {601                this.logger.log(result, this.logger.level.ERROR);602              }603            }604605            this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);606            unsub();607            reject({status, moduleError, result});608          }609        });610      } catch (e) {611        this.logger.log(e, this.logger.level.ERROR);612        reject(e);613      }614    });615  }616617  async signTransactionWithoutSending(signer: TSigner, tx: any) {618    const api = this.getApi();619    const signingInfo = await api.derive.tx.signingInfo(signer.address);620621    tx.sign(signer, {622      blockHash: api.genesisHash,623      genesisHash: api.genesisHash,624      runtimeVersion: api.runtimeVersion,625      nonce: signingInfo.nonce,626    });627628    return tx.toHex();629  }630631  async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {632    const api = this.getApi();633    const signingInfo = await api.derive.tx.signingInfo(signer.address);634635    // We need to sign the tx because636    // unsigned transactions does not have an inclusion fee637    tx.sign(signer, {638      blockHash: api.genesisHash,639      genesisHash: api.genesisHash,640      runtimeVersion: api.runtimeVersion,641      nonce: signingInfo.nonce,642    });643644    if(len === null) {645      return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;646    } else {647      return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;648    }649  }650651  constructApiCall(apiCall: string, params: any[]) {652    if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);653    let call = this.getApi() as any;654    for(const part of apiCall.slice(4).split('.')) {655      call = call[part];656      if(!call) {657        const advice = part.includes('_') ? ' Looks like it needs to be converted to camel case.' : '';658        throw Error(`Function ${part} of api call ${apiCall} not found.${advice}`);659      }660    }661    return call(...params);662  }663664  encodeApiCall(apiCall: string, params: any[]) {665    return this.constructApiCall(apiCall, params).method.toHex();666  }667668  async executeExtrinsic<669    E extends string,670    V extends (671      ...args: any) => any = ForceFunction<672        Get2<673          AugmentedSubmittables<'promise'>,674          E, (...args: any) => Invalid<'not found'>675        >676      >677  >(678    sender: TSigner,679    extrinsic: `api.tx.${E}`,680    params: Parameters<V>,681    expectSuccess = true,682    options: Partial<SignerOptions> | null = null,/*, failureMessage='expected success'*/683  ): Promise<ITransactionResult> {684    if(this.api === null) throw Error('API not initialized');685686    const startTime = (new Date()).getTime();687    let result: ITransactionResult;688    let events: IEvent[] = [];689    try {690      result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;691      events = this.eventHelper.extractEvents(result.result.events);692      const errorEvent = events.find((event) => event.method == 'ExecutedFailed' || event.method == 'CreatedFailed');693      if(errorEvent)694        throw Error(errorEvent.method + ': ' + extrinsic);695    }696    catch (e) {697      if(!(e as object).hasOwnProperty('status')) throw e;698      result = e as ITransactionResult;699    }700701    const endTime = (new Date()).getTime();702703    const log = {704      executedAt: endTime,705      executionTime: endTime - startTime,706      type: this.chainLogType.EXTRINSIC,707      status: result.status,708      call: extrinsic,709      signer: this.getSignerAddress(sender),710      params,711    } as IUniqueHelperLog;712713    let errorMessage = '';714715    if(result.status !== this.transactionStatus.SUCCESS) {716      if(result.moduleError) {717        errorMessage = typeof result.moduleError === 'string'718          ? result.moduleError719          : `${Object.keys(result.moduleError)[0]}: ${Object.values(result.moduleError)[0]}`;720        log.moduleError = errorMessage;721      }722      else if(result.result.dispatchError) log.dispatchError = result.result.dispatchError;723    }724    if(events.length > 0) log.events = events;725726    this.chainLog.push(log);727728    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {729      if(result.moduleError) throw Error(`${errorMessage}`);730      else if(result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));731    }732    return result as any;733  }734  executeExtrinsicUncheckedWeight<735      E extends string,736      V extends (737         ...args: any) => any = ForceFunction<738            Get2<739               AugmentedSubmittables<'promise'>,740               E, (...args: any) => Invalid<'not found'>741            >742         >743   >(744    sender: TSigner,745    extrinsic: `api.tx.${E}`,746    params: Parameters<V>,747    expectSuccess = true,748    options: Partial<SignerOptions> | null = null,/*, failureMessage='expected success'*/749  ): Promise<ITransactionResult> {750    throw new Error('executeExtrinsicUncheckedWeight only supported in sudo');751  }752753  async callRpc754  // TODO: make it strongly typed, or use api.query/api.rpc directly755  // <756  // K extends 'rpc' | 'query',757  // E extends string,758  // V extends (...args: any) => any = ForceFunction<759  //   Get2<760  //     K extends 'rpc' ? DecoratedRpc<'promise', RpcInterface> : QueryableStorage<'promise'>,761  //     E, (...args: any) => Invalid<'not found'>762  //   >763  // >,764  // P = Parameters<V>,765  // >766  (rpc: string, params?: any[]): Promise<any> {767768    if(typeof params === 'undefined') params = [] as any;769    if(this.api === null) throw Error('API not initialized');770    if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);771772    const startTime = (new Date()).getTime();773    let result;774    let error = null;775    const log = {776      type: this.chainLogType.RPC,777      call: rpc,778      params,779    } as any as IUniqueHelperLog;780781    try {782      result = await this.constructApiCall(rpc, params as any);783    }784    catch (e) {785      error = e;786    }787788    const endTime = (new Date()).getTime();789790    log.executedAt = endTime;791    log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';792    log.executionTime = endTime - startTime;793794    this.chainLog.push(log);795796    if(error !== null) throw error;797798    return result;799  }800801  getSignerAddress(signer: IKeyringPair | string): string {802    if(typeof signer === 'string') return signer;803    return signer.address;804  }805806  fetchAllPalletNames(): string[] {807    if(this.api === null) throw Error('API not initialized');808    return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()).sort();809  }810811  fetchMissingPalletNames(requiredPallets: readonly string[]): string[] {812    const palletNames = this.fetchAllPalletNames();813    return requiredPallets.filter(p => !palletNames.includes(p));814  }815}816817818export class HelperGroup<T extends ChainHelperBase> {819  helper: T;820821  constructor(uniqueHelper: T) {822    this.helper = uniqueHelper;823  }824}825826827class CollectionGroup extends HelperGroup<UniqueHelper> {828  /**829 * Get number of blocks when sponsored transaction is available.830 *831 * @param collectionId ID of collection832 * @param tokenId ID of token833 * @param addressObj address for which the sponsorship is checked834 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});835 * @returns number of blocks or null if sponsorship hasn't been set836 */837  async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {838    return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();839  }840841  /**842   * Get the number of created collections.843   *844   * @returns number of created collections845   */846  async getTotalCount(): Promise<number> {847    return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();848  }849850  /**851   * Get information about the collection with additional data,852   * including the number of tokens it contains, its administrators,853   * the normalized address of the collection's owner, and decoded name and description.854   *855   * @param collectionId ID of collection856   * @example await getData(2)857   * @returns collection information object858   */859  async getData(collectionId: number): Promise<{860    id: number;861    name: string;862    description: string;863    tokensCount: number;864    admins: CrossAccountId[];865    normalizedOwner: TSubstrateAccount;866    raw: any867  } | null> {868    const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);869    const humanCollection = collection.toHuman(), collectionData = {870      id: collectionId, name: null, description: null, tokensCount: 0, admins: [],871      raw: humanCollection,872    } as any, jsonCollection = collection.toJSON();873    if(humanCollection === null) return null;874    collectionData.raw.limits = jsonCollection.limits;875    collectionData.raw.permissions = jsonCollection.permissions;876    collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);877    for(const key of ['name', 'description']) {878      collectionData[key] = this.helper.util.vec2str(humanCollection[key]);879    }880881    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))882      ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)883      : 0;884    collectionData.admins = await this.getAdmins(collectionId);885886    return collectionData;887  }888889  /**890   * Get the addresses of the collection's administrators, optionally normalized.891   *892   * @param collectionId ID of collection893   * @param normalize whether to normalize the addresses to the default ss58 format894   * @example await getAdmins(1)895   * @returns array of administrators896   */897  async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {898    const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();899900    return normalize901      ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())902      : admins;903  }904905  /**906   * Get the addresses added to the collection allow-list, optionally normalized.907   * @param collectionId ID of collection908   * @param normalize whether to normalize the addresses to the default ss58 format909   * @example await getAllowList(1)910   * @returns array of allow-listed addresses911   */912  async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {913    const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();914    return normalize915      ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())916      : allowListed;917  }918919  /**920   * Get the effective limits of the collection instead of null for default values921   *922   * @param collectionId ID of collection923   * @example await getEffectiveLimits(2)924   * @returns object of collection limits925   */926  async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {927    return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();928  }929930  /**931   * Burns the collection if the signer has sufficient permissions and collection is empty.932   *933   * @param signer keyring of signer934   * @param collectionId ID of collection935   * @example await helper.collection.burn(aliceKeyring, 3);936   * @returns ```true``` if extrinsic success, otherwise ```false```937   */938  async burn(signer: TSigner, collectionId: number): Promise<boolean> {939    const result = await this.helper.executeExtrinsic(940      signer,941      'api.tx.unique.destroyCollection', [collectionId],942      true,943    );944945    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');946  }947948  /**949   * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.950   *951   * @param signer keyring of signer952   * @param collectionId ID of collection953   * @param sponsorAddress Sponsor substrate address954   * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")955   * @returns ```true``` if extrinsic success, otherwise ```false```956   */957  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {958    const result = await this.helper.executeExtrinsic(959      signer,960      'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],961      true,962    );963964    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorSet');965  }966967  /**968   * Confirms consent to sponsor the collection on behalf of the signer.969   *970   * @param signer keyring of signer971   * @param collectionId ID of collection972   * @example confirmSponsorship(aliceKeyring, 10)973   * @returns ```true``` if extrinsic success, otherwise ```false```974   */975  async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {976    const result = await this.helper.executeExtrinsic(977      signer,978      'api.tx.unique.confirmSponsorship', [collectionId],979      true,980    );981982    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'SponsorshipConfirmed');983  }984985  /**986   * Removes the sponsor of a collection, regardless if it consented or not.987   *988   * @param signer keyring of signer989   * @param collectionId ID of collection990   * @example removeSponsor(aliceKeyring, 10)991   * @returns ```true``` if extrinsic success, otherwise ```false```992   */993  async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {994    const result = await this.helper.executeExtrinsic(995      signer,996      'api.tx.unique.removeCollectionSponsor', [collectionId],997      true,998    );9991000    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorRemoved');1001  }10021003  /**1004   * Sets the limits of the collection. At least one limit must be specified for a correct call.1005   *1006   * @param signer keyring of signer1007   * @param collectionId ID of collection1008   * @param limits collection limits object1009   * @example1010   * await setLimits(1011   *   aliceKeyring,1012   *   10,1013   *   {1014   *     sponsorTransferTimeout: 0,1015   *     ownerCanDestroy: false1016   *   }1017   * )1018   * @returns ```true``` if extrinsic success, otherwise ```false```1019   */1020  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {1021    const result = await this.helper.executeExtrinsic(1022      signer,1023      'api.tx.unique.setCollectionLimits', [collectionId, limits],1024      true,1025    );10261027    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionLimitSet');1028  }10291030  /**1031   * Changes the owner of the collection to the new Substrate address.1032   *1033   * @param signer keyring of signer1034   * @param collectionId ID of collection1035   * @param ownerAddress substrate address of new owner1036   * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")1037   * @returns ```true``` if extrinsic success, otherwise ```false```1038   */1039  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {1040    const result = await this.helper.executeExtrinsic(1041      signer,1042      'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],1043      true,1044    );10451046    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionOwnerChanged');1047  }10481049  /**1050   * Adds a collection administrator.1051   *1052   * @param signer keyring of signer1053   * @param collectionId ID of collection1054   * @param adminAddressObj Administrator address (substrate or ethereum)1055   * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1056   * @returns ```true``` if extrinsic success, otherwise ```false```1057   */1058  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1059    const result = await this.helper.executeExtrinsic(1060      signer,1061      'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],1062      true,1063    );10641065    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminAdded');1066  }10671068  /**1069   * Removes a collection administrator.1070   *1071   * @param signer keyring of signer1072   * @param collectionId ID of collection1073   * @param adminAddressObj Administrator address (substrate or ethereum)1074   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1075   * @returns ```true``` if extrinsic success, otherwise ```false```1076   */1077  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1078    const result = await this.helper.executeExtrinsic(1079      signer,1080      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],1081      true,1082    );10831084    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminRemoved');1085  }10861087  /**1088   * Check if user is in allow list.1089   *1090   * @param collectionId ID of collection1091   * @param user Account to check1092   * @example await getAdmins(1)1093   * @returns is user in allow list1094   */1095  async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {1096    return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();1097  }10981099  /**1100   * Adds an address to allow list1101   * @param signer keyring of signer1102   * @param collectionId ID of collection1103   * @param addressObj address to add to the allow list1104   * @returns ```true``` if extrinsic success, otherwise ```false```1105   */1106  async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1107    const result = await this.helper.executeExtrinsic(1108      signer,1109      'api.tx.unique.addToAllowList', [collectionId, addressObj],1110      true,1111    );11121113    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressAdded');1114  }11151116  /**1117   * Removes an address from allow list1118   *1119   * @param signer keyring of signer1120   * @param collectionId ID of collection1121   * @param addressObj address to remove from the allow list1122   * @returns ```true``` if extrinsic success, otherwise ```false```1123   */1124  async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1125    const result = await this.helper.executeExtrinsic(1126      signer,1127      'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1128      true,1129    );11301131    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressRemoved');1132  }11331134  /**1135   * Sets onchain permissions for selected collection.1136   *1137   * @param signer keyring of signer1138   * @param collectionId ID of collection1139   * @param permissions collection permissions object1140   * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1141   * @returns ```true``` if extrinsic success, otherwise ```false```1142   */1143  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1144    const result = await this.helper.executeExtrinsic(1145      signer,1146      'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1147      true,1148    );11491150    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPermissionSet');1151  }11521153  /**1154   * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1155   *1156   * @param signer keyring of signer1157   * @param collectionId ID of collection1158   * @param permissions nesting permissions object1159   * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1160   * @returns ```true``` if extrinsic success, otherwise ```false```1161   */1162  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1163    return await this.setPermissions(signer, collectionId, {nesting: permissions});1164  }11651166  /**1167   * Disables nesting for selected collection.1168   *1169   * @param signer keyring of signer1170   * @param collectionId ID of collection1171   * @example disableNesting(aliceKeyring, 10);1172   * @returns ```true``` if extrinsic success, otherwise ```false```1173   */1174  async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1175    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1176  }11771178  /**1179   * Sets onchain properties to the collection.1180   *1181   * @param signer keyring of signer1182   * @param collectionId ID of collection1183   * @param properties array of property objects1184   * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1185   * @returns ```true``` if extrinsic success, otherwise ```false```1186   */1187  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1188    const result = await this.helper.executeExtrinsic(1189      signer,1190      'api.tx.unique.setCollectionProperties', [collectionId, properties],1191      true,1192    );11931194    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1195  }11961197  /**1198   * Get collection properties.1199   *1200   * @param collectionId ID of collection1201   * @param propertyKeys optionally filter the returned properties to only these keys1202   * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1203   * @returns array of key-value pairs1204   */1205  async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1206    return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1207  }12081209  async getPropertiesConsumedSpace(collectionId: number): Promise<number> {1210    const api = this.helper.getApi();1211    const props = (await api.query.common.collectionProperties(collectionId)).toJSON();12121213    return (props! as any).consumedSpace;1214  }12151216  async getCollectionOptions(collectionId: number) {1217    return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1218  }12191220  /**1221   * Deletes onchain properties from the collection.1222   *1223   * @param signer keyring of signer1224   * @param collectionId ID of collection1225   * @param propertyKeys array of property keys to delete1226   * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1227   * @returns ```true``` if extrinsic success, otherwise ```false```1228   */1229  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1230    const result = await this.helper.executeExtrinsic(1231      signer,1232      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1233      true,1234    );12351236    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1237  }12381239  /**1240   * Changes the owner of the token.1241   *1242   * @param signer keyring of signer1243   * @param collectionId ID of collection1244   * @param tokenId ID of token1245   * @param addressObj address of a new owner1246   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1247   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1248   * @returns true if the token success, otherwise false1249   */1250  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1251    const result = await this.helper.executeExtrinsic(1252      signer,1253      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1254      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1255    );12561257    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1258  }12591260  /**1261   *1262   * Change ownership of a token(s) on behalf of the owner.1263   *1264   * @param signer keyring of signer1265   * @param collectionId ID of collection1266   * @param tokenId ID of token1267   * @param fromAddressObj address on behalf of which the token will be sent1268   * @param toAddressObj new token owner1269   * @param amount amount of tokens to be transfered. For NFT must be set to 1n1270   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1271   * @returns true if the token success, otherwise false1272   */1273  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1274    const result = await this.helper.executeExtrinsic(1275      signer,1276      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1277      true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1278    );1279    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1280  }12811282  /**1283   *1284   * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1285   *1286   * @param signer keyring of signer1287   * @param collectionId ID of collection1288   * @param tokenId ID of token1289   * @param amount amount of tokens to be burned. For NFT must be set to 1n1290   * @example burnToken(aliceKeyring, 10, 5);1291   * @returns ```true``` if the extrinsic is successful, otherwise ```false```1292   */1293  async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {1294    const burnResult = await this.helper.executeExtrinsic(1295      signer,1296      'api.tx.unique.burnItem', [collectionId, tokenId, amount],1297      true, // `Unable to burn token for ${label}`,1298    );1299    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1300    if(burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1301    return burnedTokens.success;1302  }13031304  /**1305   * Destroys a concrete instance of NFT on behalf of the owner1306   *1307   * @param signer keyring of signer1308   * @param collectionId ID of collection1309   * @param tokenId ID of token1310   * @param fromAddressObj address on behalf of which the token will be burnt1311   * @param amount amount of tokens to be burned. For NFT must be set to 1n1312   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1313   * @returns ```true``` if extrinsic success, otherwise ```false```1314   */1315  async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1316    const burnResult = await this.helper.executeExtrinsic(1317      signer,1318      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1319      true, // `Unable to burn token from for ${label}`,1320    );1321    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1322    return burnedTokens.success && burnedTokens.tokens.length > 0;1323  }13241325  /**1326   * Set, change, or remove approved address to transfer the ownership of the NFT.1327   *1328   * @param signer keyring of signer1329   * @param collectionId ID of collection1330   * @param tokenId ID of token1331   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1332   * @param amount amount of token to be approved. For NFT must be set to 1n1333   * @returns ```true``` if extrinsic success, otherwise ```false```1334   */1335  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1336    const approveResult = await this.helper.executeExtrinsic(1337      signer,1338      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1339      true, // `Unable to approve token for ${label}`,1340    );13411342    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1343  }13441345  /**1346   * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1347   *1348   * @param signer keyring of signer1349   * @param collectionId ID of collection1350   * @param tokenId ID of token1351   * @param fromAddressObj Signer's Ethereum address containing her tokens1352   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1353   * @param amount amount of token to be approved. For NFT must be set to 1n1354   * @returns ```true``` if extrinsic success, otherwise ```false```1355   */1356  async approveTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {1357    const approveResult = await this.helper.executeExtrinsic(1358      signer,1359      'api.tx.unique.approveFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1360      true, // `Unable to approve token for ${label}`,1361    );13621363    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1364  }13651366  /**1367   * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1368   *1369   * @param signer keyring of signer1370   * @param collectionId ID of collection1371   * @param tokenId ID of token1372   * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1373   * @param amount amount of token to be approved. For NFT must be set to 1n1374   * @returns ```true``` if extrinsic success, otherwise ```false```1375   */1376  async approveTokenFromEth(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1377    const ethMirror = CrossAccountId.fromKeyring(signer).toEthereum();1378    return await this.approveTokenFrom(signer, collectionId, tokenId, ethMirror, toAddressObj, amount);1379  }13801381  /**1382   * Get the amount of token pieces approved to transfer or burn. Normally 0.1383   *1384   * @param collectionId ID of collection1385   * @param tokenId ID of token1386   * @param toAccountObj address which is approved to use token pieces1387   * @param fromAccountObj address which may have allowed the use of its owned tokens1388   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1389   * @returns number of approved to transfer pieces1390   */1391  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1392    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1393  }13941395  /**1396   * Get the last created token ID in a collection1397   *1398   * @param collectionId ID of collection1399   * @example getLastTokenId(10);1400   * @returns id of the last created token1401   */1402  async getLastTokenId(collectionId: number): Promise<number> {1403    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1404  }14051406  /**1407   * Check if token exists1408   *1409   * @param collectionId ID of collection1410   * @param tokenId ID of token1411   * @example doesTokenExist(10, 20);1412   * @returns true if the token exists, otherwise false1413   */1414  async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1415    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1416  }1417}14181419class NFTnRFT extends CollectionGroup {1420  /**1421   * Get tokens owned by account1422   *1423   * @param collectionId ID of collection1424   * @param addressObj tokens owner1425   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1426   * @returns array of token ids owned by account1427   */1428  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1429    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1430  }14311432  /**1433   * Get token data1434   *1435   * @param collectionId ID of collection1436   * @param tokenId ID of token1437   * @param propertyKeys optionally filter the token properties to only these keys1438   * @param blockHashAt optionally query the data at some block with this hash1439   * @example getToken(10, 5);1440   * @returns human readable token data1441   */1442  async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1443    properties: IProperty[];1444    owner: CrossAccountId;1445    normalizedOwner: CrossAccountId;1446  } | null> {1447    let tokenData;1448    if(typeof blockHashAt === 'undefined') {1449      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1450    }1451    else {1452      if(propertyKeys.length == 0) {1453        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1454        if(!collection) return null;1455        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1456      }1457      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1458    }1459    tokenData = tokenData.toHuman();1460    if(tokenData === null || tokenData.owner === null) return null;1461    const owner = {} as any;1462    for(const key of Object.keys(tokenData.owner)) {1463      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1464        ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1465        : tokenData.owner[key];1466    }1467    tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1468    return tokenData;1469  }14701471  /**1472   * Get token's owner1473   * @param collectionId ID of collection1474   * @param tokenId ID of token1475   * @param blockHashAt optionally query the data at the block with this hash1476   * @example getTokenOwner(10, 5);1477   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1478   */1479  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1480    let owner;1481    if(typeof blockHashAt === 'undefined') {1482      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1483    } else {1484      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1485    }1486    return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1487  }14881489  /**1490   * Recursively find the address that owns the token1491   * @param collectionId ID of collection1492   * @param tokenId ID of token1493   * @param blockHashAt1494   * @example getTokenTopmostOwner(10, 5);1495   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1496   */1497  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1498    let owner;1499    if(typeof blockHashAt === 'undefined') {1500      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1501    } else {1502      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1503    }15041505    if(owner === null) return null;15061507    return owner.toHuman();1508  }15091510  /**1511   * Nest one token into another1512   * @param signer keyring of signer1513   * @param tokenObj token to be nested1514   * @param rootTokenObj token to be parent1515   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1516   * @returns ```true``` if extrinsic success, otherwise ```false```1517   */1518  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1519    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1520    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1521    if(!result) {1522      throw Error('Unable to nest token!');1523    }1524    return result;1525  }15261527  /**1528     * Remove token from nested state1529     * @param signer keyring of signer1530     * @param tokenObj token to unnest1531     * @param rootTokenObj parent of a token1532     * @param toAddressObj address of a new token owner1533     * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1534     * @returns ```true``` if extrinsic success, otherwise ```false```1535     */1536  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1537    const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1538    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1539    if(!result) {1540      throw Error('Unable to unnest token!');1541    }1542    return result;1543  }15441545  /**1546   * Set permissions to change token properties1547   *1548   * @param signer keyring of signer1549   * @param collectionId ID of collection1550   * @param permissions permissions to change a property by the collection admin or token owner1551   * @example setTokenPropertyPermissions(1552   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1553   * )1554   * @returns true if extrinsic success otherwise false1555   */1556  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1557    const result = await this.helper.executeExtrinsic(1558      signer,1559      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1560      true,1561    );15621563    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1564  }15651566  /**1567   * Get token property permissions.1568   *1569   * @param collectionId ID of collection1570   * @param propertyKeys optionally filter the returned property permissions to only these keys1571   * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1572   * @returns array of key-permission pairs1573   */1574  async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1575    return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1576  }15771578  /**1579   * Set token properties1580   *1581   * @param signer keyring of signer1582   * @param collectionId ID of collection1583   * @param tokenId ID of token1584   * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1585   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1586   * @returns ```true``` if extrinsic success, otherwise ```false```1587   */1588  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1589    const result = await this.helper.executeExtrinsic(1590      signer,1591      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1592      true,1593    );15941595    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1596  }15971598  /**1599   * Get properties, metadata assigned to a token.1600   *1601   * @param collectionId ID of collection1602   * @param tokenId ID of token1603   * @param propertyKeys optionally filter the returned properties to only these keys1604   * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1605   * @returns array of key-value pairs1606   */1607  async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1608    return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1609  }16101611  /**1612   * Delete the provided properties of a token1613   * @param signer keyring of signer1614   * @param collectionId ID of collection1615   * @param tokenId ID of token1616   * @param propertyKeys property keys to be deleted1617   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1618   * @returns ```true``` if extrinsic success, otherwise ```false```1619   */1620  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1621    const result = await this.helper.executeExtrinsic(1622      signer,1623      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1624      true,1625    );16261627    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1628  }16291630  /**1631   * Mint new collection1632   *1633   * @param signer keyring of signer1634   * @param collectionOptions basic collection options and properties1635   * @param mode NFT or RFT type of a collection1636   * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1637   * @returns object of the created collection1638   */1639  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1640    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1641    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1642    for(const key of ['name', 'description', 'tokenPrefix']) {1643      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);1644    }16451646    let flags = 0;1647    // convert CollectionFlags to number and join them in one number1648    if(collectionOptions.flags) {1649      for(let i = 0; i < collectionOptions.flags.length; i++){1650        const flag = collectionOptions.flags[i];1651        flags = flags | flag;1652      }1653    }1654    collectionOptions.flags = [flags];16551656    const creationResult = await this.helper.executeExtrinsic(1657      signer,1658      'api.tx.unique.createCollectionEx', [collectionOptions],1659      true, // errorLabel,1660    );1661    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1662  }16631664  getCollectionObject(_collectionId: number): any {1665    return null;1666  }16671668  getTokenObject(_collectionId: number, _tokenId: number): any {1669    return null;1670  }16711672  /**1673   * Tells whether the given `owner` approves the `operator`.1674   * @param collectionId ID of collection1675   * @param owner owner address1676   * @param operator operator addrees1677   * @returns true if operator is enabled1678   */1679  async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1680    return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1681  }16821683  /** Sets or unsets the approval of a given operator.1684   *  The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1685   *  @param operator Operator1686   *  @param approved Should operator status be granted or revoked?1687   *  @returns ```true``` if extrinsic success, otherwise ```false```1688   */1689  async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1690    const result = await this.helper.executeExtrinsic(1691      signer,1692      'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1693      true,1694    );1695    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1696  }1697}169816991700class NFTGroup extends NFTnRFT {1701  /**1702   * Get collection object1703   * @param collectionId ID of collection1704   * @example getCollectionObject(2);1705   * @returns instance of UniqueNFTCollection1706   */1707  getCollectionObject(collectionId: number): UniqueNFTCollection {1708    return new UniqueNFTCollection(collectionId, this.helper);1709  }17101711  /**1712   * Get token object1713   * @param collectionId ID of collection1714   * @param tokenId ID of token1715   * @example getTokenObject(10, 5);1716   * @returns instance of UniqueNFTToken1717   */1718  getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1719    return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1720  }17211722  /**1723   * Is token approved to transfer1724   * @param collectionId ID of collection1725   * @param tokenId ID of token1726   * @param toAccountObj address to be approved1727   * @returns ```true``` if extrinsic success, otherwise ```false```1728   */1729  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1730    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1731  }17321733  /**1734   * Changes the owner of the token.1735   *1736   * @param signer keyring of signer1737   * @param collectionId ID of collection1738   * @param tokenId ID of token1739   * @param addressObj address of a new owner1740   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1741   * @returns ```true``` if extrinsic success, otherwise ```false```1742   */1743  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1744    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1745  }17461747  /**1748   *1749   * Change ownership of a NFT on behalf of the owner.1750   *1751   * @param signer keyring of signer1752   * @param collectionId ID of collection1753   * @param tokenId ID of token1754   * @param fromAddressObj address on behalf of which the token will be sent1755   * @param toAddressObj new token owner1756   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1757   * @returns ```true``` if extrinsic success, otherwise ```false```1758   */1759  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1760    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1761  }17621763  /**1764   * Get tokens nested in the provided token1765   * @param collectionId ID of collection1766   * @param tokenId ID of token1767   * @param blockHashAt optionally query the data at the block with this hash1768   * @example getTokenChildren(10, 5);1769   * @returns tokens whose depth of nesting is <= 51770   */1771  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1772    let children;1773    if(typeof blockHashAt === 'undefined') {1774      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1775    } else {1776      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1777    }17781779    return children.toJSON().map((x: any) => ({collectionId: x.collection, tokenId: x.token}));1780  }17811782  /**1783   * Mint new collection1784   * @param signer keyring of signer1785   * @param collectionOptions Collection options1786   * @example1787   * mintCollection(aliceKeyring, {1788   *   name: 'New',1789   *   description: 'New collection',1790   *   tokenPrefix: 'NEW',1791   * })1792   * @returns object of the created collection1793   */1794  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1795    return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1796  }17971798  /**1799   * Mint new token1800   * @param signer keyring of signer1801   * @param data token data1802   * @returns created token object1803   */1804  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1805    const creationResult = await this.helper.executeExtrinsic(1806      signer,1807      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1808        NFT: {1809          properties: data.properties,1810        },1811      }],1812      true,1813    );1814    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1815    if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1816    if(createdTokens.tokens.length < 1) throw Error('No tokens minted');1817    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1818  }18191820  /**1821   * Mint multiple NFT tokens1822   * @param signer keyring of signer1823   * @param collectionId ID of collection1824   * @param tokens array of tokens with owner and properties1825   * @example1826   * mintMultipleTokens(aliceKeyring, 10, [{1827   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1828   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1829   *   },{1830   *     owner: {Ethereum: "0x9F0583DbB855d..."},1831   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1832   * }]);1833   * @returns ```true``` if extrinsic success, otherwise ```false```1834   */1835  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1836    const creationResult = await this.helper.executeExtrinsic(1837      signer,1838      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1839      true,1840    );1841    const collection = this.getCollectionObject(collectionId);1842    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1843  }18441845  /**1846   * Mint multiple NFT tokens with one owner1847   * @param signer keyring of signer1848   * @param collectionId ID of collection1849   * @param owner tokens owner1850   * @param tokens array of tokens with owner and properties1851   * @example1852   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1853   *   properties: [{1854   *   key: "gender",1855   *   value: "female",1856   *  },{1857   *   key: "age",1858   *   value: "33",1859   *  }],1860   * }]);1861   * @returns array of newly created tokens1862   */1863  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1864    const rawTokens = [];1865    for(const token of tokens) {1866      const raw = {NFT: {properties: token.properties}};1867      rawTokens.push(raw);1868    }1869    const creationResult = await this.helper.executeExtrinsic(1870      signer,1871      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1872      true,1873    );1874    const collection = this.getCollectionObject(collectionId);1875    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1876  }18771878  /**1879   * Set, change, or remove approved address to transfer the ownership of the NFT.1880   *1881   * @param signer keyring of signer1882   * @param collectionId ID of collection1883   * @param tokenId ID of token1884   * @param toAddressObj address to approve1885   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1886   * @returns ```true``` if extrinsic success, otherwise ```false```1887   */1888  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1889    return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1890  }1891}189218931894class RFTGroup extends NFTnRFT {1895  /**1896   * Get collection object1897   * @param collectionId ID of collection1898   * @example getCollectionObject(2);1899   * @returns instance of UniqueRFTCollection1900   */1901  getCollectionObject(collectionId: number): UniqueRFTCollection {1902    return new UniqueRFTCollection(collectionId, this.helper);1903  }19041905  /**1906   * Get token object1907   * @param collectionId ID of collection1908   * @param tokenId ID of token1909   * @example getTokenObject(10, 5);1910   * @returns instance of UniqueNFTToken1911   */1912  getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1913    return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1914  }19151916  /**1917   * Get top 10 token owners with the largest number of pieces1918   * @param collectionId ID of collection1919   * @param tokenId ID of token1920   * @example getTokenTop10Owners(10, 5);1921   * @returns array of top 10 owners1922   */1923  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1924    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1925  }19261927  /**1928   * Get number of pieces owned by address1929   * @param collectionId ID of collection1930   * @param tokenId ID of token1931   * @param addressObj address token owner1932   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1933   * @returns number of pieces ownerd by address1934   */1935  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1936    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1937  }19381939  /**1940   * Transfer pieces of token to another address1941   * @param signer keyring of signer1942   * @param collectionId ID of collection1943   * @param tokenId ID of token1944   * @param addressObj address of a new owner1945   * @param amount number of pieces to be transfered1946   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1947   * @returns ```true``` if extrinsic success, otherwise ```false```1948   */1949  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1950    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1951  }19521953  /**1954   * Change ownership of some pieces of RFT on behalf of the owner.1955   * @param signer keyring of signer1956   * @param collectionId ID of collection1957   * @param tokenId ID of token1958   * @param fromAddressObj address on behalf of which the token will be sent1959   * @param toAddressObj new token owner1960   * @param amount number of pieces to be transfered1961   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1962   * @returns ```true``` if extrinsic success, otherwise ```false```1963   */1964  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1965    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1966  }19671968  /**1969   * Mint new collection1970   * @param signer keyring of signer1971   * @param collectionOptions Collection options1972   * @example1973   * mintCollection(aliceKeyring, {1974   *   name: 'New',1975   *   description: 'New collection',1976   *   tokenPrefix: 'NEW',1977   * })1978   * @returns object of the created collection1979   */1980  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1981    return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1982  }19831984  /**1985   * Mint new token1986   * @param signer keyring of signer1987   * @param data token data1988   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1989   * @returns created token object1990   */1991  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1992    const creationResult = await this.helper.executeExtrinsic(1993      signer,1994      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1995        ReFungible: {1996          pieces: data.pieces,1997          properties: data.properties,1998        },1999      }],2000      true,2001    );2002    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);2003    if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');2004    if(createdTokens.tokens.length < 1) throw Error('No tokens minted');2005    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);2006  }20072008  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {2009    throw Error('Not implemented');2010    const creationResult = await this.helper.executeExtrinsic(2011      signer,2012      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],2013      true, // `Unable to mint RFT tokens for ${label}`,2014    );2015    const collection = this.getCollectionObject(collectionId);2016    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2017  }20182019  /**2020   * Mint multiple RFT tokens with one owner2021   * @param signer keyring of signer2022   * @param collectionId ID of collection2023   * @param owner tokens owner2024   * @param tokens array of tokens with properties and pieces2025   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);2026   * @returns array of newly created RFT tokens2027   */2028  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {2029    const rawTokens = [];2030    for(const token of tokens) {2031      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};2032      rawTokens.push(raw);2033    }2034    const creationResult = await this.helper.executeExtrinsic(2035      signer,2036      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2037      true,2038    );2039    const collection = this.getCollectionObject(collectionId);2040    return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2041  }20422043  /**2044   * Destroys a concrete instance of RFT.2045   * @param signer keyring of signer2046   * @param collectionId ID of collection2047   * @param tokenId ID of token2048   * @param amount number of pieces to be burnt2049   * @example burnToken(aliceKeyring, 10, 5);2050   * @returns ```true``` if the extrinsic is successful, otherwise ```false```2051   */2052  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {2053    return await super.burnToken(signer, collectionId, tokenId, amount);2054  }20552056  /**2057   * Destroys a concrete instance of RFT on behalf of the owner.2058   * @param signer keyring of signer2059   * @param collectionId ID of collection2060   * @param tokenId ID of token2061   * @param fromAddressObj address on behalf of which the token will be burnt2062   * @param amount number of pieces to be burnt2063   * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)2064   * @returns ```true``` if extrinsic success, otherwise ```false```2065   */2066  async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2067    return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);2068  }20692070  /**2071   * Set, change, or remove approved address to transfer the ownership of the RFT.2072   *2073   * @param signer keyring of signer2074   * @param collectionId ID of collection2075   * @param tokenId ID of token2076   * @param toAddressObj address to approve2077   * @param amount number of pieces to be approved2078   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);2079   * @returns true if the token success, otherwise false2080   */2081  approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {2082    return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);2083  }20842085  /**2086   * Get total number of pieces2087   * @param collectionId ID of collection2088   * @param tokenId ID of token2089   * @example getTokenTotalPieces(10, 5);2090   * @returns number of pieces2091   */2092  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {2093    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();2094  }20952096  /**2097   * Change number of token pieces. Signer must be the owner of all token pieces.2098   * @param signer keyring of signer2099   * @param collectionId ID of collection2100   * @param tokenId ID of token2101   * @param amount new number of pieces2102   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);2103   * @returns true if the repartion was success, otherwise false2104   */2105  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {2106    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);2107    const repartitionResult = await this.helper.executeExtrinsic(2108      signer,2109      'api.tx.unique.repartition', [collectionId, tokenId, amount],2110      true,2111    );2112    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');2113    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');2114  }2115}211621172118class FTGroup extends CollectionGroup {2119  /**2120   * Get collection object2121   * @param collectionId ID of collection2122   * @example getCollectionObject(2);2123   * @returns instance of UniqueFTCollection2124   */2125  getCollectionObject(collectionId: number): UniqueFTCollection {2126    return new UniqueFTCollection(collectionId, this.helper);2127  }21282129  /**2130   * Mint new fungible collection2131   * @param signer keyring of signer2132   * @param collectionOptions Collection options2133   * @param decimalPoints number of token decimals2134   * @example2135   * mintCollection(aliceKeyring, {2136   *   name: 'New',2137   *   description: 'New collection',2138   *   tokenPrefix: 'NEW',2139   * }, 18)2140   * @returns newly created fungible collection2141   */2142  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {2143    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object2144    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');2145    collectionOptions.mode = {fungible: decimalPoints};2146    for(const key of ['name', 'description', 'tokenPrefix']) {2147      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);2148    }2149    const creationResult = await this.helper.executeExtrinsic(2150      signer,2151      'api.tx.unique.createCollectionEx', [collectionOptions],2152      true,2153    );2154    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));2155  }21562157  /**2158   * Mint tokens2159   * @param signer keyring of signer2160   * @param collectionId ID of collection2161   * @param owner address owner of new tokens2162   * @param amount amount of tokens to be meanted2163   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);2164   * @returns ```true``` if extrinsic success, otherwise ```false```2165   */2166  async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {2167    const creationResult = await this.helper.executeExtrinsic(2168      signer,2169      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {2170        Fungible: {2171          value: amount,2172        },2173      }],2174      true, // `Unable to mint fungible tokens for ${label}`,2175    );2176    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2177  }21782179  /**2180   * Mint multiple Fungible tokens with one owner2181   * @param signer keyring of signer2182   * @param collectionId ID of collection2183   * @param owner tokens owner2184   * @param tokens array of tokens with properties and pieces2185   * @returns ```true``` if extrinsic success, otherwise ```false```2186   */2187  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: { value: bigint }[], owner: ICrossAccountId): Promise<boolean> {2188    const rawTokens = [];2189    for(const token of tokens) {2190      const raw = {Fungible: {Value: token.value}};2191      rawTokens.push(raw);2192    }2193    const creationResult = await this.helper.executeExtrinsic(2194      signer,2195      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2196      true,2197    );2198    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2199  }22002201  /**2202   * Get the top 10 owners with the largest balance for the Fungible collection2203   * @param collectionId ID of collection2204   * @example getTop10Owners(10);2205   * @returns array of ```ICrossAccountId```2206   */2207  async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2208    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2209  }22102211  /**2212   * Get account balance2213   * @param collectionId ID of collection2214   * @param addressObj address of owner2215   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2216   * @returns amount of fungible tokens owned by address2217   */2218  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2219    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2220  }22212222  /**2223   * Transfer tokens to address2224   * @param signer keyring of signer2225   * @param collectionId ID of collection2226   * @param toAddressObj address recipient2227   * @param amount amount of tokens to be sent2228   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2229   * @returns ```true``` if extrinsic success, otherwise ```false```2230   */2231  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2232    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2233  }22342235  /**2236   * Transfer some tokens on behalf of the owner.2237   * @param signer keyring of signer2238   * @param collectionId ID of collection2239   * @param fromAddressObj address on behalf of which tokens will be sent2240   * @param toAddressObj address where token to be sent2241   * @param amount number of tokens to be sent2242   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2243   * @returns ```true``` if extrinsic success, otherwise ```false```2244   */2245  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {2246    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2247  }22482249  /**2250   * Destroy some amount of tokens2251   * @param signer keyring of signer2252   * @param collectionId ID of collection2253   * @param amount amount of tokens to be destroyed2254   * @example burnTokens(aliceKeyring, 10, 1000n);2255   * @returns ```true``` if extrinsic success, otherwise ```false```2256   */2257  async burnTokens(signer: IKeyringPair, collectionId: number, amount = 1n): Promise<boolean> {2258    return await super.burnToken(signer, collectionId, 0, amount);2259  }22602261  /**2262   * Burn some tokens on behalf of the owner.2263   * @param signer keyring of signer2264   * @param collectionId ID of collection2265   * @param fromAddressObj address on behalf of which tokens will be burnt2266   * @param amount amount of tokens to be burnt2267   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2268   * @returns ```true``` if extrinsic success, otherwise ```false```2269   */2270  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2271    return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2272  }22732274  /**2275   * Get total collection supply2276   * @param collectionId2277   * @returns2278   */2279  async getTotalPieces(collectionId: number): Promise<bigint> {2280    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2281  }22822283  /**2284   * Set, change, or remove approved address to transfer tokens.2285   *2286   * @param signer keyring of signer2287   * @param collectionId ID of collection2288   * @param toAddressObj address to be approved2289   * @param amount amount of tokens to be approved2290   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2291   * @returns ```true``` if extrinsic success, otherwise ```false```2292   */2293  approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2294    return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2295  }22962297  /**2298   * Get amount of fungible tokens approved to transfer2299   * @param collectionId ID of collection2300   * @param fromAddressObj owner of tokens2301   * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2302   * @returns number of tokens approved for the transfer2303   */2304  getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2305    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2306  }2307}230823092310class ChainGroup extends HelperGroup<ChainHelperBase> {2311  /**2312   * Get system properties of a chain2313   * @example getChainProperties();2314   * @returns ss58Format, token decimals, and token symbol2315   */2316  getChainProperties(): IChainProperties {2317    const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2318    return {2319      ss58Format: properties.ss58Format.toJSON(),2320      tokenDecimals: properties.tokenDecimals.toJSON(),2321      tokenSymbol: properties.tokenSymbol.toJSON(),2322    };2323  }23242325  /**2326   * Get chain header2327   * @example getLatestBlockNumber();2328   * @returns the number of the last block2329   */2330  async getLatestBlockNumber(): Promise<number> {2331    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2332  }23332334  /**2335   * Get block hash by block number2336   * @param blockNumber number of block2337   * @example getBlockHashByNumber(12345);2338   * @returns hash of a block2339   */2340  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2341    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2342    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2343    return blockHash;2344  }23452346  // TODO add docs2347  async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2348    const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2349    if(!blockHash) return null;2350    return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2351  }23522353  /**2354   * Get latest relay block2355   * @returns {number} relay block2356   */2357  async getRelayBlockNumber(): Promise<bigint> {2358    const blockNumber = (await this.helper.callRpc('api.query.parachainSystem.validationData')).toJSON().relayParentNumber;2359    return BigInt(blockNumber);2360  }23612362  /**2363   * Get account nonce2364   * @param address substrate address2365   * @example getNonce("5GrwvaEF5zXb26Fz...");2366   * @returns number, account's nonce2367   */2368  async getNonce(address: TSubstrateAccount): Promise<number> {2369    return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2370  }2371}23722373export class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2374  /**2375 * Get substrate address balance2376 * @param address substrate address2377 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2378 * @returns amount of tokens on address2379 */2380  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2381    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2382  }23832384  /**2385   * Transfer tokens to substrate address2386   * @param signer keyring of signer2387   * @param address substrate address of a recipient2388   * @param amount amount of tokens to be transfered2389   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2390   * @returns ```true``` if extrinsic success, otherwise ```false```2391   */2392  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2393    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}`*/);23942395    let transfer = {from: null, to: null, amount: 0n} as any;2396    result.result.events.forEach(({event: {data, method, section}}) => {2397      if((section === 'balances') && (method === 'Transfer')) {2398        transfer = {2399          from: this.helper.address.normalizeSubstrate(data[0]),2400          to: this.helper.address.normalizeSubstrate(data[1]),2401          amount: BigInt(data[2]),2402        };2403      }2404    });2405    const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2406      && this.helper.address.normalizeSubstrate(address) === transfer.to2407      && BigInt(amount) === transfer.amount;2408    return isSuccess;2409  }24102411  /**2412   * Get full substrate balance including free, frozen, and reserved2413   * @param address substrate address2414   * @returns2415   */2416  async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2417    const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2418    return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2419  }24202421  /**2422   * Get total issuance2423   * @returns2424   */2425  async getTotalIssuance(): Promise<bigint> {2426    const total = (await this.helper.callRpc('api.query.balances.totalIssuance', []));2427    return total.toBigInt();2428  }24292430  async getLocked(address: TSubstrateAccount): Promise<{ id: string, amount: bigint, reason: string }[]> {2431    const locks = (await this.helper.callRpc('api.query.balances.locks', [address])).toHuman();2432    return locks.map((lock: any) => ({id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}));2433  }2434  async getFrozen(address: TSubstrateAccount): Promise<{ id: string, amount: bigint }[]> {2435    const locks = (await this.helper.api!.query.balances.freezes(address)) as unknown as Array<any>;2436    return locks.map(lock => ({id: lock.id.toUtf8(), amount: lock.amount.toBigInt()}));2437  }2438}24392440export class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2441  /**2442   * Get ethereum address balance2443   * @param address ethereum address2444   * @example getEthereum("0x9F0583DbB855d...")2445   * @returns amount of tokens on address2446   */2447  async getEthereum(address: TEthereumAccount): Promise<bigint> {2448    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2449  }24502451  /**2452   * Transfer tokens to address2453   * @param signer keyring of signer2454   * @param address Ethereum address of a recipient2455   * @param amount amount of tokens to be transfered2456   * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2457   * @returns ```true``` if extrinsic success, otherwise ```false```2458   */2459  async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2460    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);24612462    let transfer = {from: null, to: null, amount: 0n} as any;2463    result.result.events.forEach(({event: {data, method, section}}) => {2464      if((section === 'balances') && (method === 'Transfer')) {2465        transfer = {2466          from: data[0].toString(),2467          to: data[1].toString(),2468          amount: BigInt(data[2]),2469        };2470      }2471    });2472    const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2473      && address === transfer.to2474      && BigInt(amount) === transfer.amount;2475    return isSuccess;2476  }2477}24782479class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2480  subBalanceGroup: SubstrateBalanceGroup<T>;2481  ethBalanceGroup: EthereumBalanceGroup<T>;24822483  constructor(helper: T) {2484    super(helper);2485    this.subBalanceGroup = new SubstrateBalanceGroup(helper);2486    this.ethBalanceGroup = new EthereumBalanceGroup(helper);2487  }24882489  getCollectionCreationPrice(): bigint {2490    return 2n * this.getOneTokenNominal();2491  }2492  /**2493   * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2494   * @example getOneTokenNominal()2495   * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2496   */2497  getOneTokenNominal(): bigint {2498    const chainProperties = this.helper.chain.getChainProperties();2499    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2500  }25012502  /**2503   * Get substrate address balance2504   * @param address substrate address2505   * @example getSubstrate("5GrwvaEF5zXb26Fz...")2506   * @returns amount of tokens on address2507   */2508  getSubstrate(address: TSubstrateAccount): Promise<bigint> {2509    return this.subBalanceGroup.getSubstrate(address);2510  }25112512  /**2513   * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2514   * @param address substrate address2515   * @returns2516   */2517  getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2518    return this.subBalanceGroup.getSubstrateFull(address);2519  }25202521  /**2522   * Get total issuance2523   * @returns2524   */2525  getTotalIssuance(): Promise<bigint> {2526    return this.subBalanceGroup.getTotalIssuance();2527  }25282529  /**2530   * Get locked balances2531   * @param address substrate address2532   * @returns locked balances with reason via api.query.balances.locks2533   * @deprecated all the methods should switch to getFrozen2534   */2535  getLocked(address: TSubstrateAccount) {2536    return this.subBalanceGroup.getLocked(address);2537  }25382539  /**2540   * Get frozen balances2541   * @param address substrate address2542   * @returns frozen balances with id via api.query.balances.freezes2543   */2544  getFrozen(address: TSubstrateAccount) {2545    return this.subBalanceGroup.getFrozen(address);2546  }25472548  /**2549   * Get ethereum address balance2550   * @param address ethereum address2551   * @example getEthereum("0x9F0583DbB855d...")2552   * @returns amount of tokens on address2553   */2554  getEthereum(address: TEthereumAccount): Promise<bigint> {2555    return this.ethBalanceGroup.getEthereum(address);2556  }25572558  async setBalanceSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint) {2559    await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceSetBalance', [address, amount], true);2560  }25612562  /**2563   * Transfer tokens to substrate address2564   * @param signer keyring of signer2565   * @param address substrate address of a recipient2566   * @param amount amount of tokens to be transfered2567   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2568   * @returns ```true``` if extrinsic success, otherwise ```false```2569   */2570  transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2571    return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2572  }25732574  async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2575    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);25762577    let transfer = {from: null, to: null, amount: 0n} as any;2578    result.result.events.forEach(({event: {data, method, section}}) => {2579      if((section === 'balances') && (method === 'Transfer')) {2580        transfer = {2581          from: this.helper.address.normalizeSubstrate(data[0]),2582          to: this.helper.address.normalizeSubstrate(data[1]),2583          amount: BigInt(data[2]),2584        };2585      }2586    });2587    let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2588    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2589    isSuccess = isSuccess && BigInt(amount) === transfer.amount;2590    return isSuccess;2591  }25922593  /**2594   * Transfer tokens with the unlock period2595   * @param signer signers Keyring2596   * @param address Substrate address of recipient2597   * @param schedule Schedule params2598   * @example vestedTransfer(signer, recepient.address, 20000, 100, 10, 50 * nominal); // total amount of vested tokens will be 100 * 50 = 50002599   */2600  async vestedTransfer(signer: TSigner, address: TSubstrateAccount, schedule: { start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }): Promise<void> {2601    const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.vestedTransfer', [address, schedule]);2602    const event = result.result.events2603      .find(e => e.event.section === 'vesting' &&2604        e.event.method === 'VestingScheduleAdded' &&2605        e.event.data[0].toHuman() === signer.address);2606    if(!event) throw Error('Cannot find transfer in events');2607  }26082609  /**2610   * Get schedule for recepient of vested transfer2611   * @param address Substrate address of recipient2612   * @returns2613   */2614  async getVestingSchedules(address: TSubstrateAccount): Promise<{ start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }[]> {2615    const schedule = (await this.helper.callRpc('api.query.vesting.vestingSchedules', [address])).toJSON();2616    return schedule.map((schedule: any) => ({2617      start: BigInt(schedule.start),2618      period: BigInt(schedule.period),2619      periodCount: BigInt(schedule.periodCount),2620      perPeriod: BigInt(schedule.perPeriod),2621    }));2622  }26232624  /**2625   * Claim vested tokens2626   * @param signer signers Keyring2627   */2628  async claim(signer: TSigner) {2629    const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.claim', []);2630    const event = result.result.events2631      .find(e => e.event.section === 'vesting' &&2632        e.event.method === 'Claimed' &&2633        e.event.data[0].toHuman() === signer.address);2634    if(!event) throw Error('Cannot find claim in events');2635  }2636}26372638class AddressGroup extends HelperGroup<ChainHelperBase> {2639  /**2640   * Normalizes the address to the specified ss58 format, by default ```42```.2641   * @param address substrate address2642   * @param ss58Format format for address conversion, by default ```42```2643   * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2644   * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2645   */2646  normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2647    return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2648  }26492650  /**2651   * Get address in the connected chain format2652   * @param address substrate address2653   * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2654   * @returns address in chain format2655   */2656  normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2657    return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2658  }26592660  /**2661   * Get substrate mirror of an ethereum address2662   * @param ethAddress ethereum address2663   * @param toChainFormat false for normalized account2664   * @example ethToSubstrate('0x9F0583DbB855d...')2665   * @returns substrate mirror of a provided ethereum address2666   */2667  ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat = false): TSubstrateAccount {2668    return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2669  }26702671  /**2672   * Get ethereum mirror of a substrate address2673   * @param subAddress substrate account2674   * @example substrateToEth("5DnSF6RRjwteE3BrC...")2675   * @returns ethereum mirror of a provided substrate address2676   */2677  substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2678    return CrossAccountId.translateSubToEth(subAddress);2679  }26802681  /**2682   * Encode key to substrate address2683   * @param key key for encoding address2684   * @param ss58Format prefix for encoding to the address of the corresponding network2685   * @returns encoded substrate address2686   */2687  encodeSubstrateAddress(key: Uint8Array | string | bigint, ss58Format = 42): string {2688    const u8a: Uint8Array = typeof key === 'string'2689      ? hexToU8a(key)2690      : typeof key === 'bigint'2691        ? hexToU8a(key.toString(16))2692        : key;26932694    if(ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2695      throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`);2696    }26972698    const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2699    if(!allowedDecodedLengths.includes(u8a.length)) {2700      throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2701    }27022703    const u8aPrefix = ss58Format < 642704      ? new Uint8Array([ss58Format])2705      : new Uint8Array([2706        ((ss58Format & 0xfc) >> 2) | 0x40,2707        (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2708      ]);27092710    const input = u8aConcat(u8aPrefix, u8a);27112712    return base58Encode(u8aConcat(2713      input,2714      blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2715    ));2716  }27172718  /**2719   * Restore substrate address from bigint representation2720   * @param number decimal representation of substrate address2721   * @returns substrate address2722   */2723  restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2724    if(this.helper.api === null) {2725      throw 'Not connected';2726    }2727    const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2728    if(res === undefined || res === null) {2729      throw 'Restore address error';2730    }2731    return res.toString();2732  }27332734  /**2735   * Convert etherium cross account id to substrate cross account id2736   * @param ethCrossAccount etherium cross account2737   * @returns substrate cross account id2738   */2739  convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2740    if(ethCrossAccount.sub === '0') {2741      return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2742    }27432744    const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2745    return {Substrate: ss58};2746  }27472748  paraSiblingSovereignAccount(paraid: number) {2749    // We are getting a *sibling* parachain sovereign account,2750    // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2751    const siblingPrefix = '0x7369626c';27522753    const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2754    const suffix = '000000000000000000000000000000000000000000000000';27552756    return siblingPrefix + encodedParaId + suffix;2757  }2758}27592760class StakingGroup extends HelperGroup<UniqueHelper> {2761  /**2762   * Stake tokens for App Promotion2763   * @param signer keyring of signer2764   * @param amountToStake amount of tokens to stake2765   * @param label extra label for log2766   * @returns2767   */2768  async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2769    if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2770    const _stakeResult = await this.helper.executeExtrinsic(2771      signer, 'api.tx.appPromotion.stake',2772      [amountToStake], true,2773    );2774    // TODO extract info from stakeResult2775    return true;2776  }27772778  /**2779   * Unstake all staked tokens2780   * @param signer keyring of signer2781   * @param amountToUnstake amount of tokens to unstake2782   * @param label extra label for log2783   * @returns block hash where unstake happened2784   */2785  async unstakeAll(signer: TSigner, label?: string): Promise<string> {2786    if(typeof label === 'undefined') label = `${signer.address}`;2787    const unstakeResult = await this.helper.executeExtrinsic(2788      signer, 'api.tx.appPromotion.unstakeAll',2789      [], true,2790    );2791    return unstakeResult.blockHash;2792  }27932794  /**2795   * Unstake the part of a staked tokens2796   * @param signer keyring of signer2797   * @param amount amount of tokens to unstake2798   * @param label extra label for log2799   * @returns block hash where unstake happened2800   */2801  async unstakePartial(signer: TSigner, amount: bigint, label?: string): Promise<string> {2802    if(typeof label === 'undefined') label = `${signer.address}`;2803    const unstakeResult = await this.helper.executeExtrinsic(2804      signer, 'api.tx.appPromotion.unstakePartial',2805      [amount], true,2806    );2807    return unstakeResult.blockHash;2808  }28092810  /**2811   * Get total number of active stakes2812   * @param address substrate address2813   * @returns {number}2814   */2815  async getStakesNumber(address: ICrossAccountId): Promise<number> {2816    if('Ethereum' in address) throw Error('only substrate address');2817    return (await this.helper.callRpc('api.query.appPromotion.stakesPerAccount', [address.Substrate])).toNumber();2818  }28192820  /**2821   * Get total staked amount for address2822   * @param address substrate or ethereum address2823   * @returns total staked amount2824   */2825  async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2826    if(address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2827    return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2828  }28292830  /**2831   * Get total staked per block2832   * @param address substrate or ethereum address2833   * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2834   */2835  async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2836    const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2837    return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => ({2838      block: block.toBigInt(),2839      amount: amount.toBigInt(),2840    }));2841  }28422843  /**2844   * Get total pending unstake amount for address2845   * @param address substrate or ethereum address2846   * @returns total pending unstake amount2847   */2848  async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2849    return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2850  }28512852  /**2853   * Get pending unstake amount per block for address2854   * @param address substrate or ethereum address2855   * @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 block2856   */2857  async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2858    const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2859    const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => ({2860      block: block.toBigInt(),2861      amount: amount.toBigInt(),2862    }));2863    return result;2864  }2865}28662867class SchedulerGroup extends HelperGroup<UniqueHelper> {2868  constructor(helper: UniqueHelper) {2869    super(helper);2870  }28712872  cancelScheduled(signer: TSigner, scheduledId: string) {2873    return this.helper.executeExtrinsic(2874      signer,2875      'api.tx.scheduler.cancelNamed',2876      [scheduledId],2877      true,2878    );2879  }28802881  changePriority(signer: TSigner, scheduledId: string, priority: number) {2882    return this.helper.executeExtrinsic(2883      signer,2884      'api.tx.scheduler.changeNamedPriority',2885      [scheduledId, priority],2886      true,2887    );2888  }28892890  scheduleAt<T extends UniqueHelper>(2891    executionBlockNumber: number,2892    options: ISchedulerOptions = {},2893  ) {2894    return this.schedule<T>('schedule', executionBlockNumber, options);2895  }28962897  scheduleAfter<T extends UniqueHelper>(2898    blocksBeforeExecution: number,2899    options: ISchedulerOptions = {},2900  ) {2901    return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2902  }29032904  schedule<T extends UniqueHelper>(2905    scheduleFn: 'schedule' | 'scheduleAfter',2906    blocksNum: number,2907    options: ISchedulerOptions = {},2908  ) {2909    // eslint-disable-next-line @typescript-eslint/naming-convention2910    const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2911    return this.helper.clone(ScheduledHelperType, {2912      scheduleFn,2913      blocksNum,2914      options,2915    }) as T;2916  }2917}29182919class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {2920  //todo:collator documentation2921  addInvulnerable(signer: TSigner, address: string) {2922    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);2923  }29242925  removeInvulnerable(signer: TSigner, address: string) {2926    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);2927  }29282929  async getInvulnerables(): Promise<string[]> {2930    return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());2931  }29322933  /** and also total max invulnerables */2934  maxCollators(): number {2935    return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);2936  }29372938  async getDesiredCollators(): Promise<number> {2939    return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();2940  }29412942  setLicenseBond(signer: TSigner, amount: bigint) {2943    return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);2944  }29452946  async getLicenseBond(): Promise<bigint> {2947    return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();2948  }29492950  obtainLicense(signer: TSigner) {2951    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);2952  }29532954  releaseLicense(signer: TSigner) {2955    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);2956  }29572958  forceReleaseLicense(signer: TSigner, released: string) {2959    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);2960  }29612962  async hasLicense(address: string): Promise<bigint> {2963    return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();2964  }29652966  onboard(signer: TSigner) {2967    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);2968  }29692970  offboard(signer: TSigner) {2971    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);2972  }29732974  async getCandidates(): Promise<string[]> {2975    return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());2976  }2977}29782979class CollectiveGroup extends HelperGroup<UniqueHelper> {2980  /**2981   * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee'2982   */2983  private collective: string;29842985  constructor(helper: UniqueHelper, collective: string) {2986    super(helper);2987    this.collective = collective;2988  }29892990  /**2991   * Check the result of a proposal execution for the success of the underlying proposed extrinsic.2992   * @param events events of the proposal execution2993   * @returns proposal hash2994   */2995  private checkExecutedEvent(events: IPhasicEvent[]): string {2996    const executionEvents = events.filter(x =>2997      x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted'));29982999    if(executionEvents.length != 1) {3000      if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0)3001        throw new Error(`Disapproved by ${this.collective}`);3002      else3003        throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`);3004    }30053006    const result = (executionEvents[0].event.data as any).result;30073008    if(result.isErr) {3009      if(result.asErr.isModule) {3010        const error = result.asErr.asModule;3011        const metaError = this.helper.getApi()?.registry.findMetaError(error);3012        throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`);3013      } else {3014        throw new Error('Proposal execution failed with ' + result.asErr.toHuman());3015      }3016    }30173018    return (executionEvents[0].event.data as any).proposalHash;3019  }30203021  /**3022   * Returns an array of members' addresses.3023   */3024  async getMembers() {3025    return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman();3026  }30273028  /**3029   * Returns the optional address of the prime member of the collective.3030   */3031  async getPrimeMember() {3032    return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman();3033  }30343035  /**3036   * Returns an array of proposal hashes that are currently active for this collective.3037   */3038  async getProposals() {3039    return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman();3040  }30413042  /**3043   * Returns the call originally encoded under the specified hash.3044   * @param hash h256-encoded proposal3045   * @returns the optional call that the proposal hash stands for.3046   */3047  async getProposalCallOf(hash: string) {3048    return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman();3049  }30503051  /**3052   * Returns the total number of proposals so far.3053   */3054  async getTotalProposalsCount() {3055    return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber();3056  }30573058  /**3059   * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately.3060   * @param signer keyring of the proposer3061   * @param proposal constructed call to be executed if the proposal is successful3062   * @param voteThreshold minimal number of votes for the proposal to be verified and executed3063   * @param lengthBound byte length of the encoded call3064   * @returns promise of extrinsic execution and its result3065   */3066  async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) {3067    return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]);3068  }30693070  /**3071   * Casts a vote to either approve or reject a proposal.3072   * @param signer keyring of the voter3073   * @param proposalHash hash of the proposal to be voted for3074   * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors3075   * @param approve aye or nay3076   * @returns promise of extrinsic execution and its result3077   */3078  vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {3079    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]);3080  }30813082  /**3083   * Executes a call immediately as a member of the collective. Needed for the Member origin.3084   * @param signer keyring of the executor member3085   * @param proposal constructed call to be executed by the member3086   * @param lengthBound byte length of the encoded call3087   * @returns promise of extrinsic execution3088   */3089  async execute(signer: TSigner, proposal: any, lengthBound = 10000) {3090    const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]);3091    this.checkExecutedEvent(result.result.events);3092    return result;3093  }30943095  /**3096   * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing.3097   * @param signer keyring of the executor. Can be absolutely anyone.3098   * @param proposalHash hash of the proposal to close3099   * @param proposalIndex index of the proposal generated on its creation3100   * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call.3101   * @param lengthBound byte length of the encoded call3102   * @returns promise of extrinsic execution and its result3103   */3104  async close(3105    signer: TSigner,3106    proposalHash: string,3107    proposalIndex: number,3108    weightBound: [number, number] | any = [20_000_000_000, 1000_000],3109    lengthBound = 10_000,3110  ) {3111    const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [3112      proposalHash,3113      proposalIndex,3114      weightBound,3115      lengthBound,3116    ]);3117    this.checkExecutedEvent(result.result.events);3118    return result;3119  }31203121  /**3122   * Shut down a proposal, regardless of its current state.3123   * @param signer keyring of the disapprover. Must be root3124   * @param proposalHash hash of the proposal to close3125   * @returns promise of extrinsic execution and its result3126   */3127  disapproveProposal(signer: TSigner, proposalHash: string) {3128    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]);3129  }3130}31313132class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {3133  /**3134   * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership'3135   */3136  private membership: string;31373138  constructor(helper: UniqueHelper, membership: string) {3139    super(helper);3140    this.membership = membership;3141  }31423143  /**3144   * Returns an array of members' addresses according to the membership pallet's perception.3145   * Note that it does not recognize the original pallet's members set with `setMembers()`.3146   */3147  async getMembers() {3148    return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman();3149  }31503151  /**3152   * Returns the optional address of the prime member of the collective.3153   */3154  async getPrimeMember() {3155    return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman();3156  }31573158  /**3159   * Add a member to the collective.3160   * @param signer keyring of the setter. Must be root3161   * @param member address of the member to add3162   * @returns promise of extrinsic execution and its result3163   */3164  addMember(signer: TSigner, member: string) {3165    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]);3166  }31673168  addMemberCall(member: string) {3169    return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]);3170  }31713172  /**3173   * Remove a member from the collective.3174   * @param signer keyring of the setter. Must be root3175   * @param member address of the member to remove3176   * @returns promise of extrinsic execution and its result3177   */3178  removeMember(signer: TSigner, member: string) {3179    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]);3180  }31813182  removeMemberCall(member: string) {3183    return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]);3184  }31853186  /**3187   * Set members of the collective to the given list of addresses.3188   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3189   * @param members addresses of the members to set3190   * @returns promise of extrinsic execution and its result3191   */3192  resetMembers(signer: TSigner, members: string[]) {3193    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]);3194  }31953196  /**3197   * Set the collective's prime member to the given address.3198   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3199   * @param prime address of the prime member of the collective3200   * @returns promise of extrinsic execution and its result3201   */3202  setPrime(signer: TSigner, prime: string) {3203    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]);3204  }32053206  setPrimeCall(member: string) {3207    return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]);3208  }32093210  /**3211   * Remove the collective's prime member.3212   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3213   * @returns promise of extrinsic execution and its result3214   */3215  clearPrime(signer: TSigner) {3216    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []);3217  }32183219  clearPrimeCall() {3220    return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []);3221  }3222}32233224class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {3225  /**3226   * Pallet name to make an API call to. Examples: 'FellowshipCollective'3227   */3228  private collective: string;32293230  constructor(helper: UniqueHelper, collective: string) {3231    super(helper);3232    this.collective = collective;3233  }32343235  addMember(signer: TSigner, newMember: string) {3236    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]);3237  }32383239  addMemberCall(newMember: string) {3240    return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]);3241  }32423243  removeMember(signer: TSigner, member: string, minRank: number) {3244    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]);3245  }32463247  removeMemberCall(newMember: string, minRank: number) {3248    return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]);3249  }32503251  promote(signer: TSigner, member: string) {3252    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]);3253  }32543255  promoteCall(member: string) {3256    return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]);3257  }32583259  demote(signer: TSigner, member: string) {3260    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]);3261  }32623263  demoteCall(newMember: string) {3264    return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]);3265  }32663267  vote(signer: TSigner, pollIndex: number, aye: boolean) {3268    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]);3269  }32703271  async getMembers() {3272    return (await this.helper.getApi().query.fellowshipCollective.members.keys())3273      .map((key) => key.args[0].toString());3274  }32753276  async getMemberRank(member: string) {3277    return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank;3278  }3279}32803281class ReferendaGroup extends HelperGroup<UniqueHelper> {3282  /**3283   * Pallet name to make an API call to. Examples: 'FellowshipReferenda'3284   */3285  private referenda: string;32863287  constructor(helper: UniqueHelper, referenda: string) {3288    super(helper);3289    this.referenda = referenda;3290  }32913292  submit(3293    signer: TSigner,3294    proposalOrigin: string,3295    proposal: any,3296    enactmentMoment: any,3297  ) {3298    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [3299      {Origins: proposalOrigin},3300      proposal,3301      enactmentMoment,3302    ]);3303  }33043305  placeDecisionDeposit(signer: TSigner, referendumIndex: number) {3306    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]);3307  }33083309  cancel(signer: TSigner, referendumIndex: number) {3310    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]);3311  }33123313  cancelCall(referendumIndex: number) {3314    return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]);3315  }33163317  async referendumInfo(referendumIndex: number) {3318    return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON();3319  }33203321  async enactmentEventId(referendumIndex: number) {3322    const api = await this.helper.getApi();33233324    const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a();3325    return blake2AsHex(bytes, 256);3326  }3327}33283329export interface IFellowshipGroup {3330  collective: RankedCollectiveGroup;3331  referenda: ReferendaGroup;3332}33333334export interface ICollectiveGroup {3335  collective: CollectiveGroup;3336  membership: CollectiveMembershipGroup;3337}33383339class DemocracyGroup extends HelperGroup<UniqueHelper> {3340  // todo displace proposal into types?3341  propose(signer: TSigner, call: any, deposit: bigint) {3342    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3343  }33443345  proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) {3346    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]);3347  }33483349  proposeCall(call: any, deposit: bigint) {3350    return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3351  }33523353  second(signer: TSigner, proposalIndex: number) {3354    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]);3355  }33563357  externalPropose(signer: TSigner, proposalCall: any) {3358    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3359  }33603361  externalProposeMajority(signer: TSigner, proposalCall: any) {3362    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3363  }33643365  externalProposeDefault(signer: TSigner, proposalCall: any) {3366    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3367  }33683369  externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) {3370    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);3371  }33723373  externalProposeCall(proposalCall: any) {3374    return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3375  }33763377  externalProposeMajorityCall(proposalCall: any) {3378    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3379  }33803381  externalProposeDefaultCall(proposalCall: any) {3382    return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3383  }33843385  externalProposeDefaultWithPreimageCall(preimage: string) {3386    return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);3387  }33883389  // ... and blacklist external proposal hash.3390  vetoExternal(signer: TSigner, proposalHash: string) {3391    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]);3392  }33933394  vetoExternalCall(proposalHash: string) {3395    return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]);3396  }33973398  blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) {3399    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3400  }34013402  blacklistCall(proposalHash: string, referendumIndex: number | null = null) {3403    return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3404  }34053406  // proposal. CancelProposalOrigin (root or all techcom)3407  cancelProposal(signer: TSigner, proposalIndex: number) {3408    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]);3409  }34103411  cancelProposalCall(proposalIndex: number) {3412    return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]);3413  }34143415  clearPublicProposals(signer: TSigner) {3416    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []);3417  }34183419  fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) {3420    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3421  }34223423  fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) {3424    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3425  }34263427  // referendum. CancellationOrigin (TechCom member)3428  emergencyCancel(signer: TSigner, referendumIndex: number) {3429    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]);3430  }34313432  emergencyCancelCall(referendumIndex: number) {3433    return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]);3434  }34353436  vote(signer: TSigner, referendumIndex: number, vote: any) {3437    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]);3438  }34393440  removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) {3441    if(targetAccount) {3442      return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]);3443    } else {3444      return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]);3445    }3446  }34473448  unlock(signer: TSigner, targetAccount: string) {3449    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]);3450  }34513452  delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) {3453    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]);3454  }34553456  undelegate(signer: TSigner) {3457    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []);3458  }34593460  async referendumInfo(referendumIndex: number) {3461    return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON();3462  }34633464  async publicProposals() {3465    return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON();3466  }34673468  async findPublicProposal(proposalIndex: number) {3469    const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex);34703471    return proposalInfo ? proposalInfo[1] : null;3472  }34733474  async expectPublicProposal(proposalIndex: number) {3475    const proposal = await this.findPublicProposal(proposalIndex);34763477    if(proposal) {3478      return proposal;3479    } else {3480      throw Error(`Proposal #${proposalIndex} is expected to exist`);3481    }3482  }34833484  async getExternalProposal() {3485    return (await this.helper.callRpc('api.query.democracy.nextExternal', []));3486  }34873488  async expectExternalProposal() {3489    const proposal = await this.getExternalProposal();34903491    if(proposal) {3492      return proposal;3493    } else {3494      throw Error('An external proposal is expected to exist');3495    }3496  }34973498  /* setMetadata? */34993500  /* todo?3501  referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {3502    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);3503  }*/3504}35053506class PreimageGroup extends HelperGroup<UniqueHelper> {3507  async getPreimageInfo(h256: string) {3508    return (await this.helper.callRpc('api.query.preimage.statusFor', [h256])).toJSON();3509  }35103511  /**3512   * Create a preimage from an API call.3513   * @param signer keyring of the signer.3514   * @param call an extrinsic call3515   * @example await notePreimageFromCall(preimageMaker,3516   *   helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd])3517   * );3518   * @returns promise of extrinsic execution.3519   */3520  notePreimageFromCall(signer: TSigner, call: any, returnPreimageHash = false) {3521    return this.notePreimage(signer, call.method.toHex(), returnPreimageHash);3522  }35233524  /**3525   * Create a preimage with a hex or a byte array.3526   * @param signer keyring of the signer.3527   * @param bytes preimage encoded in hex or a byte array, e.g. an extrinsic call.3528   * @example await notePreimage(preimageMaker,3529   *   helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex()3530   * );3531   * @returns promise of extrinsic execution.3532   */3533  async notePreimage(signer: TSigner, bytes: string | Uint8Array, returnPreimageHash = false) {3534    const promise = this.helper.executeExtrinsic(signer, 'api.tx.preimage.notePreimage', [bytes]);3535    if(returnPreimageHash) {3536      const result = await promise;3537      const events = result.result.events.filter(x => x.event.method === 'Noted' && x.event.section === 'preimage');3538      const preimageHash = events[0].event.data[0].toHuman();3539      return preimageHash;3540    }3541    return promise;3542  }35433544  /**3545   * Delete an existing preimage and return the deposit.3546   * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3547   * @param h256 hash of the preimage.3548   * @returns promise of extrinsic execution.3549   */3550  unnotePreimage(signer: TSigner, h256: string) {3551    return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unnotePreimage', [h256]);3552  }35533554  /**3555   * Request a preimage be uploaded to the chain without paying any fees or deposits.3556   * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3557   * @param h256 hash of the preimage.3558   * @returns promise of extrinsic execution.3559   */3560  requestPreimage(signer: TSigner, h256: string) {3561    return this.helper.executeExtrinsic(signer, 'api.tx.preimage.requestPreimage', [h256]);3562  }35633564  /**3565   * Clear a previously made request for a preimage.3566   * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3567   * @param h256 hash of the preimage.3568   * @returns promise of extrinsic execution.3569   */3570  unrequestPreimage(signer: TSigner, h256: string) {3571    return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]);3572  }3573}35743575class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {3576  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {3577    await this.helper.executeExtrinsic(3578      signer,3579      'api.tx.foreignAssets.registerForeignAsset',3580      [ownerAddress, location, metadata],3581      true,3582    );3583  }35843585  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {3586    await this.helper.executeExtrinsic(3587      signer,3588      'api.tx.foreignAssets.updateForeignAsset',3589      [foreignAssetId, location, metadata],3590      true,3591    );3592  }3593}35943595export class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {3596  palletName: string;35973598  constructor(helper: T, palletName: string) {3599    super(helper);36003601    this.palletName = palletName;3602  }36033604  async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {3605    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);3606  }36073608  async setSafeXcmVersion(signer: TSigner, version: number) {3609    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);3610  }36113612  async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {3613    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);3614  }36153616  async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {3617    const destinationContent = {3618      parents: 0,3619      interior: {3620        X1: {3621          Parachain: destinationParaId,3622        },3623      },3624    };36253626    const beneficiaryContent = {3627      parents: 0,3628      interior: {3629        X1: {3630          AccountId32: {3631            network: 'Any',3632            id: targetAccount,3633          },3634        },3635      },3636    };36373638    const assetsContent = [3639      {3640        id: {3641          Concrete: {3642            parents: 0,3643            interior: 'Here',3644          },3645        },3646        fun: {3647          Fungible: amount,3648        },3649      },3650    ];36513652    let destination;3653    let beneficiary;3654    let assets;36553656    if(xcmVersion == 2) {3657      destination = {V1: destinationContent};3658      beneficiary = {V1: beneficiaryContent};3659      assets = {V1: assetsContent};36603661    } else if(xcmVersion == 3) {3662      destination = {V2: destinationContent};3663      beneficiary = {V2: beneficiaryContent};3664      assets = {V2: assetsContent};36653666    } else {3667      throw Error('Unknown XCM version: ' + xcmVersion);3668    }36693670    const feeAssetItem = 0;36713672    await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);3673  }36743675  async send(signer: IKeyringPair, destination: any, message: any) {3676    await this.helper.executeExtrinsic(3677      signer,3678      `api.tx.${this.palletName}.send`,3679      [3680        destination,3681        message,3682      ],3683      true,3684    );3685  }3686}36873688export class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3689  async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {3690    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);3691  }36923693  async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {3694    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);3695  }36963697  async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {3698    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);3699  }3700}3701370237033704export class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3705  async accounts(address: string, currencyId: any) {3706    const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;3707    return BigInt(free);3708  }3709}37103711export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {3712  async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {3713    await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);3714  }37153716  async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {3717    await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);3718  }37193720  async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {3721    await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);3722  }37233724  async account(assetId: string | number, address: string) {3725    const accountAsset = (3726      await this.helper.callRpc('api.query.assets.account', [assetId, address])3727    ).toJSON()! as any;37283729    if(accountAsset !== null) {3730      return BigInt(accountAsset['balance']);3731    } else {3732      return null;3733    }3734  }3735}37363737class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {3738  async batch(signer: TSigner, txs: any[]) {3739    return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batch', [txs]);3740  }37413742  async batchAll(signer: TSigner, txs: any[]) {3743    return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batchAll', [txs]);3744  }37453746  batchAllCall(txs: any[]) {3747    return this.helper.constructApiCall('api.tx.utility.batchAll', [txs]);3748  }3749}3750375137523753export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;3754export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;37553756export class UniqueHelper extends ChainHelperBase {3757  balance: BalanceGroup<UniqueHelper>;3758  collection: CollectionGroup;3759  nft: NFTGroup;3760  rft: RFTGroup;3761  ft: FTGroup;3762  staking: StakingGroup;3763  scheduler: SchedulerGroup;3764  collatorSelection: CollatorSelectionGroup;3765  council: ICollectiveGroup;3766  technicalCommittee: ICollectiveGroup;3767  fellowship: IFellowshipGroup;3768  democracy: DemocracyGroup;3769  preimage: PreimageGroup;3770  foreignAssets: ForeignAssetsGroup;3771  xcm: XcmGroup<UniqueHelper>;3772  xTokens: XTokensGroup<UniqueHelper>;3773  tokens: TokensGroup<UniqueHelper>;3774  utility: UtilityGroup<UniqueHelper>;37753776  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3777    super(logger, options.helperBase ?? UniqueHelper);37783779    this.balance = new BalanceGroup(this);3780    this.collection = new CollectionGroup(this);3781    this.nft = new NFTGroup(this);3782    this.rft = new RFTGroup(this);3783    this.ft = new FTGroup(this);3784    this.staking = new StakingGroup(this);3785    this.scheduler = new SchedulerGroup(this);3786    this.collatorSelection = new CollatorSelectionGroup(this);3787    this.council = {3788      collective: new CollectiveGroup(this, 'council'),3789      membership: new CollectiveMembershipGroup(this, 'councilMembership'),3790    };3791    this.technicalCommittee = {3792      collective: new CollectiveGroup(this, 'technicalCommittee'),3793      membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),3794    };3795    this.fellowship = {3796      collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),3797      referenda: new ReferendaGroup(this, 'fellowshipReferenda'),3798    };3799    this.democracy = new DemocracyGroup(this);3800    this.preimage = new PreimageGroup(this);3801    this.foreignAssets = new ForeignAssetsGroup(this);3802    this.xcm = new XcmGroup(this, 'polkadotXcm');3803    this.xTokens = new XTokensGroup(this);3804    this.tokens = new TokensGroup(this);3805    this.utility = new UtilityGroup(this);3806  }3807}38083809// eslint-disable-next-line @typescript-eslint/naming-convention3810function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {3811  return class extends Base {3812    scheduleFn: 'schedule' | 'scheduleAfter';3813    blocksNum: number;3814    options: ISchedulerOptions;38153816    constructor(...args: any[]) {3817      const logger = args[0] as ILogger;3818      const options = args[1] as {3819        scheduleFn: 'schedule' | 'scheduleAfter',3820        blocksNum: number,3821        options: ISchedulerOptions3822      };38233824      super(logger);38253826      this.scheduleFn = options.scheduleFn;3827      this.blocksNum = options.blocksNum;3828      this.options = options.options;3829    }38303831    executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {3832      const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);38333834      const mandatorySchedArgs = [3835        this.blocksNum,3836        this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,3837        this.options.priority ?? null,3838        scheduledTx,3839      ];38403841      let schedArgs;3842      let scheduleFn;38433844      if(this.options.scheduledId) {3845        schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];38463847        if(this.scheduleFn == 'schedule') {3848          scheduleFn = 'scheduleNamed';3849        } else if(this.scheduleFn == 'scheduleAfter') {3850          scheduleFn = 'scheduleNamedAfter';3851        }3852      } else {3853        schedArgs = mandatorySchedArgs;3854        scheduleFn = this.scheduleFn;3855      }38563857      const extrinsic = 'api.tx.scheduler.' + scheduleFn;38583859      return super.executeExtrinsic(3860        sender,3861        extrinsic as any,3862        schedArgs,3863        expectSuccess,3864      );3865    }3866  };3867}38683869export class UniqueBaseCollection {3870  helper: UniqueHelper;3871  collectionId: number;38723873  constructor(collectionId: number, uniqueHelper: UniqueHelper) {3874    this.collectionId = collectionId;3875    this.helper = uniqueHelper;3876  }38773878  async getData() {3879    return await this.helper.collection.getData(this.collectionId);3880  }38813882  async getLastTokenId() {3883    return await this.helper.collection.getLastTokenId(this.collectionId);3884  }38853886  async doesTokenExist(tokenId: number) {3887    return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);3888  }38893890  async getAdmins() {3891    return await this.helper.collection.getAdmins(this.collectionId);3892  }38933894  async getAllowList() {3895    return await this.helper.collection.getAllowList(this.collectionId);3896  }38973898  async getEffectiveLimits() {3899    return await this.helper.collection.getEffectiveLimits(this.collectionId);3900  }39013902  async getProperties(propertyKeys?: string[] | null) {3903    return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3904  }39053906  async getPropertiesConsumedSpace() {3907    return await this.helper.collection.getPropertiesConsumedSpace(this.collectionId);3908  }39093910  async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3911    return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3912  }39133914  async getOptions() {3915    return await this.helper.collection.getCollectionOptions(this.collectionId);3916  }39173918  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {3919    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);3920  }39213922  async confirmSponsorship(signer: TSigner) {3923    return await this.helper.collection.confirmSponsorship(signer, this.collectionId);3924  }39253926  async removeSponsor(signer: TSigner) {3927    return await this.helper.collection.removeSponsor(signer, this.collectionId);3928  }39293930  async setLimits(signer: TSigner, limits: ICollectionLimits) {3931    return await this.helper.collection.setLimits(signer, this.collectionId, limits);3932  }39333934  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {3935    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);3936  }39373938  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3939    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);3940  }39413942  async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {3943    return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);3944  }39453946  async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {3947    return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);3948  }39493950  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3951    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);3952  }39533954  async setProperties(signer: TSigner, properties: IProperty[]) {3955    return await this.helper.collection.setProperties(signer, this.collectionId, properties);3956  }39573958  async deleteProperties(signer: TSigner, propertyKeys: string[]) {3959    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);3960  }39613962  async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {3963    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);3964  }39653966  async enableNesting(signer: TSigner, permissions: INestingPermissions) {3967    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);3968  }39693970  async disableNesting(signer: TSigner) {3971    return await this.helper.collection.disableNesting(signer, this.collectionId);3972  }39733974  async burn(signer: TSigner) {3975    return await this.helper.collection.burn(signer, this.collectionId);3976  }39773978  scheduleAt<T extends UniqueHelper>(3979    executionBlockNumber: number,3980    options: ISchedulerOptions = {},3981  ) {3982    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3983    return new UniqueBaseCollection(this.collectionId, scheduledHelper);3984  }39853986  scheduleAfter<T extends UniqueHelper>(3987    blocksBeforeExecution: number,3988    options: ISchedulerOptions = {},3989  ) {3990    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3991    return new UniqueBaseCollection(this.collectionId, scheduledHelper);3992  }3993}39943995export class UniqueNFTCollection extends UniqueBaseCollection {3996  getTokenObject(tokenId: number) {3997    return new UniqueNFToken(tokenId, this);3998  }39994000  async getTokensByAddress(addressObj: ICrossAccountId) {4001    return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);4002  }40034004  async getToken(tokenId: number, blockHashAt?: string) {4005    return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);4006  }40074008  async getTokenOwner(tokenId: number, blockHashAt?: string) {4009    return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);4010  }40114012  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {4013    return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);4014  }40154016  async getTokenChildren(tokenId: number, blockHashAt?: string) {4017    return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);4018  }40194020  async getPropertyPermissions(propertyKeys: string[] | null = null) {4021    return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);4022  }40234024  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {4025    return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);4026  }40274028  async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {4029    const api = this.helper.getApi();4030    const props = (await api.query.nonfungible.tokenProperties(this.collectionId, tokenId)).toJSON();40314032    return (props! as any).consumedSpace;4033  }40344035  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {4036    return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);4037  }40384039  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4040    return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);4041  }40424043  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {4044    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);4045  }40464047  async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {4048    return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);4049  }40504051  async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {4052    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});4053  }40544055  async mintMultipleTokens(signer: TSigner, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]) {4056    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);4057  }40584059  async burnToken(signer: TSigner, tokenId: number) {4060    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);4061  }40624063  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {4064    return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);4065  }40664067  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {4068    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);4069  }40704071  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {4072    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);4073  }40744075  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {4076    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);4077  }40784079  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {4080    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);4081  }40824083  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4084    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);4085  }40864087  scheduleAt<T extends UniqueHelper>(4088    executionBlockNumber: number,4089    options: ISchedulerOptions = {},4090  ) {4091    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4092    return new UniqueNFTCollection(this.collectionId, scheduledHelper);4093  }40944095  scheduleAfter<T extends UniqueHelper>(4096    blocksBeforeExecution: number,4097    options: ISchedulerOptions = {},4098  ) {4099    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4100    return new UniqueNFTCollection(this.collectionId, scheduledHelper);4101  }4102}41034104export class UniqueRFTCollection extends UniqueBaseCollection {4105  getTokenObject(tokenId: number) {4106    return new UniqueRFToken(tokenId, this);4107  }41084109  async getToken(tokenId: number, blockHashAt?: string) {4110    return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);4111  }41124113  async getTokenOwner(tokenId: number, blockHashAt?: string) {4114    return await this.helper.rft.getTokenOwner(this.collectionId, tokenId, blockHashAt);4115  }41164117  async getTokensByAddress(addressObj: ICrossAccountId) {4118    return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);4119  }41204121  async getTop10TokenOwners(tokenId: number) {4122    return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);4123  }41244125  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {4126    return await this.helper.rft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);4127  }41284129  async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {4130    return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);4131  }41324133  async getTokenTotalPieces(tokenId: number) {4134    return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);4135  }41364137  async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4138    return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);4139  }41404141  async getPropertyPermissions(propertyKeys: string[] | null = null) {4142    return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);4143  }41444145  async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {4146    return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);4147  }41484149  async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {4150    const api = this.helper.getApi();4151    const props = (await api.query.refungible.tokenProperties(this.collectionId, tokenId)).toJSON();41524153    return (props! as any).consumedSpace;4154  }41554156  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount = 1n) {4157    return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);4158  }41594160  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4161    return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);4162  }41634164  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {4165    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);4166  }41674168  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {4169    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);4170  }41714172  async mintToken(signer: TSigner, pieces = 1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {4173    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});4174  }41754176  async mintMultipleTokens(signer: TSigner, tokens: { pieces: bigint, owner: ICrossAccountId, properties?: IProperty[] }[]) {4177    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);4178  }41794180  async burnToken(signer: TSigner, tokenId: number, amount = 1n) {4181    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);4182  }41834184  async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n) {4185    return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);4186  }41874188  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {4189    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);4190  }41914192  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {4193    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);4194  }41954196  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {4197    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);4198  }41994200  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {4201    return await this.helper.rft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);4202  }42034204  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4205    return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);4206  }42074208  scheduleAt<T extends UniqueHelper>(4209    executionBlockNumber: number,4210    options: ISchedulerOptions = {},4211  ) {4212    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4213    return new UniqueRFTCollection(this.collectionId, scheduledHelper);4214  }42154216  scheduleAfter<T extends UniqueHelper>(4217    blocksBeforeExecution: number,4218    options: ISchedulerOptions = {},4219  ) {4220    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4221    return new UniqueRFTCollection(this.collectionId, scheduledHelper);4222  }4223}42244225export class UniqueFTCollection extends UniqueBaseCollection {4226  async getBalance(addressObj: ICrossAccountId) {4227    return await this.helper.ft.getBalance(this.collectionId, addressObj);4228  }42294230  async getTotalPieces() {4231    return await this.helper.ft.getTotalPieces(this.collectionId);4232  }42334234  async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4235    return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);4236  }42374238  async getTop10Owners() {4239    return await this.helper.ft.getTop10Owners(this.collectionId);4240  }42414242  async mint(signer: TSigner, amount = 1n, owner: ICrossAccountId = {Substrate: signer.address}) {4243    return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);4244  }42454246  async mintWithOneOwner(signer: TSigner, tokens: { value: bigint }[], owner: ICrossAccountId = {Substrate: signer.address}) {4247    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);4248  }42494250  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4251    return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);4252  }42534254  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4255    return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);4256  }42574258  async burnTokens(signer: TSigner, amount = 1n) {4259    return await this.helper.ft.burnTokens(signer, this.collectionId, amount);4260  }42614262  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {4263    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);4264  }42654266  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4267    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);4268  }42694270  scheduleAt<T extends UniqueHelper>(4271    executionBlockNumber: number,4272    options: ISchedulerOptions = {},4273  ) {4274    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4275    return new UniqueFTCollection(this.collectionId, scheduledHelper);4276  }42774278  scheduleAfter<T extends UniqueHelper>(4279    blocksBeforeExecution: number,4280    options: ISchedulerOptions = {},4281  ) {4282    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4283    return new UniqueFTCollection(this.collectionId, scheduledHelper);4284  }4285}42864287export class UniqueBaseToken {4288  collection: UniqueNFTCollection | UniqueRFTCollection;4289  collectionId: number;4290  tokenId: number;42914292  constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {4293    this.collection = collection;4294    this.collectionId = collection.collectionId;4295    this.tokenId = tokenId;4296  }42974298  async getNextSponsored(addressObj: ICrossAccountId) {4299    return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);4300  }43014302  async getProperties(propertyKeys?: string[] | null) {4303    return await this.collection.getTokenProperties(this.tokenId, propertyKeys);4304  }43054306  async getTokenPropertiesConsumedSpace() {4307    return await this.collection.getTokenPropertiesConsumedSpace(this.tokenId);4308  }43094310  async setProperties(signer: TSigner, properties: IProperty[]) {4311    return await this.collection.setTokenProperties(signer, this.tokenId, properties);4312  }43134314  async deleteProperties(signer: TSigner, propertyKeys: string[]) {4315    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);4316  }43174318  async doesExist() {4319    return await this.collection.doesTokenExist(this.tokenId);4320  }43214322  nestingAccount() {4323    return this.collection.helper.util.getTokenAccount(this);4324  }43254326  scheduleAt<T extends UniqueHelper>(4327    executionBlockNumber: number,4328    options: ISchedulerOptions = {},4329  ) {4330    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4331    return new UniqueBaseToken(this.tokenId, scheduledCollection);4332  }43334334  scheduleAfter<T extends UniqueHelper>(4335    blocksBeforeExecution: number,4336    options: ISchedulerOptions = {},4337  ) {4338    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4339    return new UniqueBaseToken(this.tokenId, scheduledCollection);4340  }4341}43424343export class UniqueNFToken extends UniqueBaseToken {4344  collection: UniqueNFTCollection;43454346  constructor(tokenId: number, collection: UniqueNFTCollection) {4347    super(tokenId, collection);4348    this.collection = collection;4349  }43504351  async getData(blockHashAt?: string) {4352    return await this.collection.getToken(this.tokenId, blockHashAt);4353  }43544355  async getOwner(blockHashAt?: string) {4356    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);4357  }43584359  async getTopmostOwner(blockHashAt?: string) {4360    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);4361  }43624363  async getChildren(blockHashAt?: string) {4364    return await this.collection.getTokenChildren(this.tokenId, blockHashAt);4365  }43664367  async nest(signer: TSigner, toTokenObj: IToken) {4368    return await this.collection.nestToken(signer, this.tokenId, toTokenObj);4369  }43704371  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4372    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);4373  }43744375  async transfer(signer: TSigner, addressObj: ICrossAccountId) {4376    return await this.collection.transferToken(signer, this.tokenId, addressObj);4377  }43784379  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4380    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);4381  }43824383  async approve(signer: TSigner, toAddressObj: ICrossAccountId) {4384    return await this.collection.approveToken(signer, this.tokenId, toAddressObj);4385  }43864387  async isApproved(toAddressObj: ICrossAccountId) {4388    return await this.collection.isTokenApproved(this.tokenId, toAddressObj);4389  }43904391  async burn(signer: TSigner) {4392    return await this.collection.burnToken(signer, this.tokenId);4393  }43944395  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {4396    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);4397  }43984399  scheduleAt<T extends UniqueHelper>(4400    executionBlockNumber: number,4401    options: ISchedulerOptions = {},4402  ) {4403    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4404    return new UniqueNFToken(this.tokenId, scheduledCollection);4405  }44064407  scheduleAfter<T extends UniqueHelper>(4408    blocksBeforeExecution: number,4409    options: ISchedulerOptions = {},4410  ) {4411    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4412    return new UniqueNFToken(this.tokenId, scheduledCollection);4413  }4414}44154416export class UniqueRFToken extends UniqueBaseToken {4417  collection: UniqueRFTCollection;44184419  constructor(tokenId: number, collection: UniqueRFTCollection) {4420    super(tokenId, collection);4421    this.collection = collection;4422  }44234424  async getData(blockHashAt?: string) {4425    return await this.collection.getToken(this.tokenId, blockHashAt);4426  }44274428  async getOwner(blockHashAt?: string) {4429    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);4430  }44314432  async getTop10Owners() {4433    return await this.collection.getTop10TokenOwners(this.tokenId);4434  }44354436  async getTopmostOwner(blockHashAt?: string) {4437    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);4438  }44394440  async nest(signer: TSigner, toTokenObj: IToken) {4441    return await this.collection.nestToken(signer, this.tokenId, toTokenObj);4442  }44434444  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4445    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);4446  }44474448  async getBalance(addressObj: ICrossAccountId) {4449    return await this.collection.getTokenBalance(this.tokenId, addressObj);4450  }44514452  async getTotalPieces() {4453    return await this.collection.getTokenTotalPieces(this.tokenId);4454  }44554456  async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {4457    return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);4458  }44594460  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount = 1n) {4461    return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);4462  }44634464  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4465    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);4466  }44674468  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4469    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);4470  }44714472  async repartition(signer: TSigner, amount: bigint) {4473    return await this.collection.repartitionToken(signer, this.tokenId, amount);4474  }44754476  async burn(signer: TSigner, amount = 1n) {4477    return await this.collection.burnToken(signer, this.tokenId, amount);4478  }44794480  async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {4481    return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);4482  }44834484  scheduleAt<T extends UniqueHelper>(4485    executionBlockNumber: number,4486    options: ISchedulerOptions = {},4487  ) {4488    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4489    return new UniqueRFToken(this.tokenId, scheduledCollection);4490  }44914492  scheduleAfter<T extends UniqueHelper>(4493    blocksBeforeExecution: number,4494    options: ISchedulerOptions = {},4495  ) {4496    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4497    return new UniqueRFToken(this.tokenId, scheduledCollection);4498  }4499}