difftreelog
feat add bitintToDecimals and sovereign account util
in: master
1 file changed
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034/* eslint-disable @typescript-eslint/no-var-requires */5/* eslint-disable function-call-argument-newline */6/* eslint-disable no-prototype-builtins */78import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';11import {IKeyringPair} from '@polkadot/types/types';12import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata} from './types';1314export class CrossAccountId implements ICrossAccountId {15 Substrate?: TSubstrateAccount;16 Ethereum?: TEthereumAccount;1718 constructor(account: ICrossAccountId) {19 if (account.Substrate) this.Substrate = account.Substrate;20 if (account.Ethereum) this.Ethereum = account.Ethereum;21 }2223 static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {24 switch (domain) {25 case 'Substrate': return new CrossAccountId({Substrate: account.address});26 case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();27 }28 }2930 static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {31 return new CrossAccountId({Substrate: address.substrate, Ethereum: address.ethereum});32 }3334 static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {35 return encodeAddress(decodeAddress(address), ss58Format);36 }3738 static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {39 return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});40 }41 42 withNormalizedSubstrate(ss58Format = 42): CrossAccountId {43 if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);44 return this;45 }4647 static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {48 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));49 }5051 toEthereum(): CrossAccountId {52 if (this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});53 return this;54 }5556 static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {57 return evmToAddress(address, ss58Format);58 }5960 toSubstrate(ss58Format?: number): CrossAccountId {61 if (this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});62 return this;63 }64 65 toLowerCase(): CrossAccountId {66 if (this.Substrate) this.Substrate = this.Substrate.toLowerCase();67 if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();68 return this;69 }70}7172const nesting = {73 toChecksumAddress(address: string): string {74 if (typeof address === 'undefined') return '';7576 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);7778 address = address.toLowerCase().replace(/^0x/i,'');79 const addressHash = keccakAsHex(address).replace(/^0x/i,'');80 const checksumAddress = ['0x'];8182 for (let i = 0; i < address.length; i++) {83 // If ith character is 8 to f then make it uppercase84 if (parseInt(addressHash[i], 16) > 7) {85 checksumAddress.push(address[i].toUpperCase());86 } else {87 checksumAddress.push(address[i]);88 }89 }90 return checksumAddress.join('');91 },92 tokenIdToAddress(collectionId: number, tokenId: number) {93 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);94 },95};9697class UniqueUtil {98 static transactionStatus = {99 NOT_READY: 'NotReady',100 FAIL: 'Fail',101 SUCCESS: 'Success',102 };103104 static chainLogType = {105 EXTRINSIC: 'extrinsic',106 RPC: 'rpc',107 };108109 static getTokenAccount(token: IToken): CrossAccountId {110 return new CrossAccountId({Ethereum: this.getTokenAddress(token)});111 }112113 static getTokenAddress(token: IToken): string {114 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);115 }116117 static getDefaultLogger(): ILogger {118 return {119 log(msg: any, level = 'INFO') {120 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));121 },122 level: {123 ERROR: 'ERROR',124 WARNING: 'WARNING',125 INFO: 'INFO',126 },127 };128 }129130 static vec2str(arr: string[] | number[]) {131 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');132 }133134 static str2vec(string: string) {135 if (typeof string !== 'string') return string;136 return Array.from(string).map(x => x.charCodeAt(0));137 }138139 static fromSeed(seed: string, ss58Format = 42) {140 const keyring = new Keyring({type: 'sr25519', ss58Format});141 return keyring.addFromUri(seed);142 }143144 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {145 if (creationResult.status !== this.transactionStatus.SUCCESS) {146 throw Error('Unable to create collection!');147 }148149 let collectionId = null;150 creationResult.result.events.forEach(({event: {data, method, section}}) => {151 if ((section === 'common') && (method === 'CollectionCreated')) {152 collectionId = parseInt(data[0].toString(), 10);153 }154 });155156 if (collectionId === null) {157 throw Error('No CollectionCreated event was found!');158 }159160 return collectionId;161 }162163 static extractTokensFromCreationResult(creationResult: ITransactionResult): {164 success: boolean, 165 tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],166 } {167 if (creationResult.status !== this.transactionStatus.SUCCESS) {168 throw Error('Unable to create tokens!');169 }170 let success = false;171 const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];172 creationResult.result.events.forEach(({event: {data, method, section}}) => {173 if (method === 'ExtrinsicSuccess') {174 success = true;175 } else if ((section === 'common') && (method === 'ItemCreated')) {176 tokens.push({177 collectionId: parseInt(data[0].toString(), 10),178 tokenId: parseInt(data[1].toString(), 10),179 owner: data[2].toHuman(),180 amount: data[3].toBigInt(),181 });182 }183 });184 return {success, tokens};185 }186187 static extractTokensFromBurnResult(burnResult: ITransactionResult): {188 success: boolean, 189 tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],190 } {191 if (burnResult.status !== this.transactionStatus.SUCCESS) {192 throw Error('Unable to burn tokens!');193 }194 let success = false;195 const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];196 burnResult.result.events.forEach(({event: {data, method, section}}) => {197 if (method === 'ExtrinsicSuccess') {198 success = true;199 } else if ((section === 'common') && (method === 'ItemDestroyed')) {200 tokens.push({201 collectionId: parseInt(data[0].toString(), 10),202 tokenId: parseInt(data[1].toString(), 10),203 owner: data[2].toHuman(),204 amount: data[3].toBigInt(),205 });206 }207 });208 return {success, tokens};209 }210211 static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {212 let eventId = null;213 events.forEach(({event: {data, method, section}}) => {214 if ((section === expectedSection) && (method === expectedMethod)) {215 eventId = parseInt(data[0].toString(), 10);216 }217 });218219 if (eventId === null) {220 throw Error(`No ${expectedMethod} event was found!`);221 }222 return eventId === collectionId;223 }224225 static isTokenTransferSuccess(events: {event: IEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {226 const normalizeAddress = (address: string | ICrossAccountId) => {227 if(typeof address === 'string') return address;228 const obj = {} as any;229 Object.keys(address).forEach(k => {230 obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];231 });232 if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);233 if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();234 return address;235 };236 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;237 events.forEach(({event: {data, method, section}}) => {238 if ((section === 'common') && (method === 'Transfer')) {239 const hData = (data as any).toJSON();240 transfer = {241 collectionId: hData[0],242 tokenId: hData[1],243 from: normalizeAddress(hData[2]),244 to: normalizeAddress(hData[3]),245 amount: BigInt(hData[4]),246 };247 }248 });249 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;250 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);251 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);252 isSuccess = isSuccess && amount === transfer.amount;253 return isSuccess;254 }255}256257class UniqueEventHelper {258 private static extractIndex(index: any): [number, number] | string {259 if(index.toRawType() === '[u8;2]') return [index[0], index[1]];260 return index.toJSON();261 }262263 private static extractSub(data: any, subTypes: any): {[key: string]: any} {264 let obj: any = {};265 let index = 0;266267 if (data.entries) {268 for(const [key, value] of data.entries()) {269 obj[key] = this.extractData(value, subTypes[index]);270 index++;271 }272 } else obj = data.toJSON();273274 return obj;275 }276 277 private static extractData(data: any, type: any): any {278 if(!type) return data.toHuman();279 if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();280 if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();281 if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);282 return data.toHuman();283 }284285 public static extractEvents(records: ITransactionResult): IEvent[] {286 const parsedEvents: IEvent[] = [];287288 records.result.events.forEach((record) => {289 const {event, phase} = record;290 const types = (event as any).typeDef;291292 const eventData: IEvent = {293 section: event.section.toString(),294 method: event.method.toString(),295 index: this.extractIndex(event.index),296 data: [],297 phase: phase.toJSON(),298 };299300 event.data.forEach((val: any, index: number) => {301 eventData.data.push(this.extractData(val, types[index]));302 });303304 parsedEvents.push(eventData);305 });306307 return parsedEvents;308 }309}310311export class ChainHelperBase {312 helperBase: any;313314 transactionStatus = UniqueUtil.transactionStatus;315 chainLogType = UniqueUtil.chainLogType;316 util: typeof UniqueUtil;317 eventHelper: typeof UniqueEventHelper;318 logger: ILogger;319 api: ApiPromise | null;320 forcedNetwork: TNetworks | null;321 network: TNetworks | null;322 chainLog: IUniqueHelperLog[];323 children: ChainHelperBase[];324 address: AddressGroup;325 chain: ChainGroup;326327 constructor(logger?: ILogger, helperBase?: any) {328 this.helperBase = helperBase;329330 this.util = UniqueUtil;331 this.eventHelper = UniqueEventHelper;332 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();333 this.logger = logger;334 this.api = null;335 this.forcedNetwork = null;336 this.network = null;337 this.chainLog = [];338 this.children = [];339 this.address = new AddressGroup(this);340 this.chain = new ChainGroup(this);341 }342343 clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {344 Object.setPrototypeOf(helperCls.prototype, this);345 const newHelper = new helperCls(this.logger, options);346347 newHelper.api = this.api;348 newHelper.network = this.network;349 newHelper.forceNetwork = this.forceNetwork;350351 this.children.push(newHelper);352353 return newHelper;354 }355356 getApi(): ApiPromise {357 if(this.api === null) throw Error('API not initialized');358 return this.api;359 }360361 clearChainLog(): void {362 this.chainLog = [];363 }364365 forceNetwork(value: TNetworks): void {366 this.forcedNetwork = value;367 }368369 async connect(wsEndpoint: string, listeners?: IApiListeners) {370 if (this.api !== null) throw Error('Already connected');371 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);372 this.api = api;373 this.network = network;374 }375376 async disconnect() {377 for (const child of this.children) {378 child.clearApi();379 }380381 if (this.api === null) return;382 await this.api.disconnect();383 this.clearApi();384 }385386 clearApi() {387 this.api = null;388 this.network = null;389 }390391 static async detectNetwork(api: ApiPromise): Promise<TNetworks> {392 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;393 const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];394395 if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;396397 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;398 return 'opal';399 }400401 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {402 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});403 await api.isReady;404405 const network = await this.detectNetwork(api);406407 await api.disconnect();408409 return network;410 }411412 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{413 api: ApiPromise;414 network: TNetworks;415 }> {416 console.log('createConnection network = ', network);417 if(typeof network === 'undefined' || network === null) network = 'opal';418 const supportedRPC = {419 opal: {420 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,421 },422 quartz: {423 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,424 },425 unique: {426 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,427 },428 rococo: {},429 westend: {},430 moonbeam: {},431 moonriver: {},432 acala: {},433 karura: {},434 westmint: {},435 };436 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);437 const rpc = supportedRPC[network];438439 // TODO: investigate how to replace rpc in runtime440 // api._rpcCore.addUserInterfaces(rpc);441442 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});443444 await api.isReadyOrError;445446 if (typeof listeners === 'undefined') listeners = {};447 for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {448 if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;449 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);450 }451452 return {api, network};453 }454455 getTransactionStatus(data: {events: {event: IEvent}[], status: any}) {456 const {events, status} = data;457 if (status.isReady) {458 return this.transactionStatus.NOT_READY;459 }460 if (status.isBroadcast) {461 return this.transactionStatus.NOT_READY;462 }463 if (status.isInBlock || status.isFinalized) {464 const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');465 if (errors.length > 0) {466 return this.transactionStatus.FAIL;467 }468 if (events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {469 return this.transactionStatus.SUCCESS;470 }471 }472473 return this.transactionStatus.FAIL;474 }475476 signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {477 const sign = (callback: any) => {478 if(options !== null) return transaction.signAndSend(sender, options, callback);479 return transaction.signAndSend(sender, callback);480 };481 // eslint-disable-next-line no-async-promise-executor482 return new Promise(async (resolve, reject) => {483 try {484 const unsub = await sign((result: any) => {485 const status = this.getTransactionStatus(result);486487 if (status === this.transactionStatus.SUCCESS) {488 this.logger.log(`${label} successful`);489 unsub();490 resolve({result, status});491 } else if (status === this.transactionStatus.FAIL) {492 let moduleError = null;493494 if (result.hasOwnProperty('dispatchError')) {495 const dispatchError = result['dispatchError'];496497 if (dispatchError) {498 if (dispatchError.isModule) {499 const modErr = dispatchError.asModule;500 const errorMeta = dispatchError.registry.findMetaError(modErr);501502 moduleError = `${errorMeta.section}.${errorMeta.name}`;503 } else {504 moduleError = dispatchError.toHuman();505 }506 } else {507 this.logger.log(result, this.logger.level.ERROR);508 }509 }510511 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);512 unsub();513 reject({status, moduleError, result});514 }515 });516 } catch (e) {517 this.logger.log(e, this.logger.level.ERROR);518 reject(e);519 }520 });521 }522523 constructApiCall(apiCall: string, params: any[]) {524 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);525 let call = this.getApi() as any;526 for(const part of apiCall.slice(4).split('.')) {527 call = call[part];528 }529 return call(...params);530 }531532 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {533 if(this.api === null) throw Error('API not initialized');534 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);535536 const startTime = (new Date()).getTime();537 let result: ITransactionResult;538 let events: IEvent[] = [];539 try {540 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;541 events = this.eventHelper.extractEvents(result);542 }543 catch(e) {544 if(!(e as object).hasOwnProperty('status')) throw e;545 result = e as ITransactionResult;546 }547548 const endTime = (new Date()).getTime();549550 const log = {551 executedAt: endTime,552 executionTime: endTime - startTime,553 type: this.chainLogType.EXTRINSIC,554 status: result.status,555 call: extrinsic,556 signer: this.getSignerAddress(sender),557 params,558 } as IUniqueHelperLog;559560 if(result.status !== this.transactionStatus.SUCCESS) {561 if (result.moduleError) log.moduleError = result.moduleError;562 else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;563 }564 if(events.length > 0) log.events = events;565566 this.chainLog.push(log);567568 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {569 if (result.moduleError) throw Error(`${result.moduleError}`);570 else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));571 }572 return result;573 }574575 async callRpc(rpc: string, params?: any[]) {576 if(typeof params === 'undefined') params = [];577 if(this.api === null) throw Error('API not initialized');578 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);579580 const startTime = (new Date()).getTime();581 let result;582 let error = null;583 const log = {584 type: this.chainLogType.RPC,585 call: rpc,586 params,587 } as IUniqueHelperLog;588589 try {590 result = await this.constructApiCall(rpc, params);591 }592 catch(e) {593 error = e;594 }595596 const endTime = (new Date()).getTime();597598 log.executedAt = endTime;599 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';600 log.executionTime = endTime - startTime;601602 this.chainLog.push(log);603604 if(error !== null) throw error;605606 return result;607 }608609 getSignerAddress(signer: IKeyringPair | string): string {610 if(typeof signer === 'string') return signer;611 return signer.address;612 }613614 fetchAllPalletNames(): string[] {615 if(this.api === null) throw Error('API not initialized');616 return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());617 }618619 fetchMissingPalletNames(requiredPallets: string[]): string[] {620 const palletNames = this.fetchAllPalletNames();621 return requiredPallets.filter(p => !palletNames.includes(p));622 }623}624625626class HelperGroup<T extends ChainHelperBase> {627 helper: T;628629 constructor(uniqueHelper: T) {630 this.helper = uniqueHelper;631 }632}633634635class CollectionGroup extends HelperGroup<UniqueHelper> {636 /**637 * Get number of blocks when sponsored transaction is available.638 *639 * @param collectionId ID of collection640 * @param tokenId ID of token641 * @param addressObj address for which the sponsorship is checked642 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});643 * @returns number of blocks or null if sponsorship hasn't been set644 */645 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {646 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();647 }648649 /**650 * Get the number of created collections.651 *652 * @returns number of created collections653 */654 async getTotalCount(): Promise<number> {655 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();656 }657658 /**659 * Get information about the collection with additional data,660 * including the number of tokens it contains, its administrators,661 * the normalized address of the collection's owner, and decoded name and description.662 *663 * @param collectionId ID of collection664 * @example await getData(2)665 * @returns collection information object666 */667 async getData(collectionId: number): Promise<{668 id: number;669 name: string;670 description: string;671 tokensCount: number;672 admins: CrossAccountId[];673 normalizedOwner: TSubstrateAccount;674 raw: any675 } | null> {676 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);677 const humanCollection = collection.toHuman(), collectionData = {678 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],679 raw: humanCollection,680 } as any, jsonCollection = collection.toJSON();681 if (humanCollection === null) return null;682 collectionData.raw.limits = jsonCollection.limits;683 collectionData.raw.permissions = jsonCollection.permissions;684 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);685 for (const key of ['name', 'description']) {686 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);687 }688689 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))690 ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)691 : 0;692 collectionData.admins = await this.getAdmins(collectionId);693694 return collectionData;695 }696697 /**698 * Get the addresses of the collection's administrators, optionally normalized.699 *700 * @param collectionId ID of collection701 * @param normalize whether to normalize the addresses to the default ss58 format702 * @example await getAdmins(1)703 * @returns array of administrators704 */705 async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {706 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();707708 return normalize709 ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())710 : admins;711 }712713 /**714 * Get the addresses added to the collection allow-list, optionally normalized.715 * @param collectionId ID of collection716 * @param normalize whether to normalize the addresses to the default ss58 format717 * @example await getAllowList(1)718 * @returns array of allow-listed addresses719 */720 async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {721 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();722 return normalize723 ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())724 : allowListed;725 }726727 /**728 * Get the effective limits of the collection instead of null for default values729 *730 * @param collectionId ID of collection731 * @example await getEffectiveLimits(2)732 * @returns object of collection limits733 */734 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {735 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();736 }737738 /**739 * Burns the collection if the signer has sufficient permissions and collection is empty.740 *741 * @param signer keyring of signer742 * @param collectionId ID of collection743 * @example await helper.collection.burn(aliceKeyring, 3);744 * @returns ```true``` if extrinsic success, otherwise ```false```745 */746 async burn(signer: TSigner, collectionId: number): Promise<boolean> {747 const result = await this.helper.executeExtrinsic(748 signer,749 'api.tx.unique.destroyCollection', [collectionId],750 true,751 );752753 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');754 }755756 /**757 * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.758 *759 * @param signer keyring of signer760 * @param collectionId ID of collection761 * @param sponsorAddress Sponsor substrate address762 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")763 * @returns ```true``` if extrinsic success, otherwise ```false```764 */765 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {766 const result = await this.helper.executeExtrinsic(767 signer,768 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],769 true,770 );771772 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet');773 }774775 /**776 * Confirms consent to sponsor the collection on behalf of the signer.777 *778 * @param signer keyring of signer779 * @param collectionId ID of collection780 * @example confirmSponsorship(aliceKeyring, 10)781 * @returns ```true``` if extrinsic success, otherwise ```false```782 */783 async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {784 const result = await this.helper.executeExtrinsic(785 signer,786 'api.tx.unique.confirmSponsorship', [collectionId],787 true,788 );789790 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed');791 }792793 /**794 * Removes the sponsor of a collection, regardless if it consented or not.795 *796 * @param signer keyring of signer797 * @param collectionId ID of collection798 * @example removeSponsor(aliceKeyring, 10)799 * @returns ```true``` if extrinsic success, otherwise ```false```800 */801 async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {802 const result = await this.helper.executeExtrinsic(803 signer,804 'api.tx.unique.removeCollectionSponsor', [collectionId],805 true,806 );807808 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorRemoved');809 }810811 /**812 * Sets the limits of the collection. At least one limit must be specified for a correct call.813 *814 * @param signer keyring of signer815 * @param collectionId ID of collection816 * @param limits collection limits object817 * @example818 * await setLimits(819 * aliceKeyring,820 * 10,821 * {822 * sponsorTransferTimeout: 0,823 * ownerCanDestroy: false824 * }825 * )826 * @returns ```true``` if extrinsic success, otherwise ```false```827 */828 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {829 const result = await this.helper.executeExtrinsic(830 signer,831 'api.tx.unique.setCollectionLimits', [collectionId, limits],832 true,833 );834835 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet');836 }837838 /**839 * Changes the owner of the collection to the new Substrate address.840 *841 * @param signer keyring of signer842 * @param collectionId ID of collection843 * @param ownerAddress substrate address of new owner844 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")845 * @returns ```true``` if extrinsic success, otherwise ```false```846 */847 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {848 const result = await this.helper.executeExtrinsic(849 signer,850 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],851 true,852 );853854 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged');855 }856857 /**858 * Adds a collection administrator.859 *860 * @param signer keyring of signer861 * @param collectionId ID of collection862 * @param adminAddressObj Administrator address (substrate or ethereum)863 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})864 * @returns ```true``` if extrinsic success, otherwise ```false```865 */866 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {867 const result = await this.helper.executeExtrinsic(868 signer,869 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],870 true,871 );872873 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded');874 }875876 /**877 * Removes a collection administrator.878 *879 * @param signer keyring of signer880 * @param collectionId ID of collection881 * @param adminAddressObj Administrator address (substrate or ethereum)882 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})883 * @returns ```true``` if extrinsic success, otherwise ```false```884 */885 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {886 const result = await this.helper.executeExtrinsic(887 signer,888 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],889 true,890 );891892 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved');893 }894895 /**896 * Check if user is in allow list.897 * 898 * @param collectionId ID of collection899 * @param user Account to check900 * @example await getAdmins(1)901 * @returns is user in allow list902 */903 async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {904 return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();905 }906907 /**908 * Adds an address to allow list909 * @param signer keyring of signer910 * @param collectionId ID of collection911 * @param addressObj address to add to the allow list912 * @returns ```true``` if extrinsic success, otherwise ```false```913 */914 async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {915 const result = await this.helper.executeExtrinsic(916 signer,917 'api.tx.unique.addToAllowList', [collectionId, addressObj],918 true,919 );920921 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');922 }923924 /**925 * Removes an address from allow list926 *927 * @param signer keyring of signer928 * @param collectionId ID of collection929 * @param addressObj address to remove from the allow list930 * @returns ```true``` if extrinsic success, otherwise ```false```931 */932 async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {933 const result = await this.helper.executeExtrinsic(934 signer,935 'api.tx.unique.removeFromAllowList', [collectionId, addressObj],936 true,937 );938939 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved');940 }941942 /**943 * Sets onchain permissions for selected collection.944 *945 * @param signer keyring of signer946 * @param collectionId ID of collection947 * @param permissions collection permissions object948 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});949 * @returns ```true``` if extrinsic success, otherwise ```false```950 */951 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {952 const result = await this.helper.executeExtrinsic(953 signer,954 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],955 true,956 );957958 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet');959 }960961 /**962 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.963 *964 * @param signer keyring of signer965 * @param collectionId ID of collection966 * @param permissions nesting permissions object967 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});968 * @returns ```true``` if extrinsic success, otherwise ```false```969 */970 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {971 return await this.setPermissions(signer, collectionId, {nesting: permissions});972 }973974 /**975 * Disables nesting for selected collection.976 *977 * @param signer keyring of signer978 * @param collectionId ID of collection979 * @example disableNesting(aliceKeyring, 10);980 * @returns ```true``` if extrinsic success, otherwise ```false```981 */982 async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {983 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});984 }985986 /**987 * Sets onchain properties to the collection.988 *989 * @param signer keyring of signer990 * @param collectionId ID of collection991 * @param properties array of property objects992 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);993 * @returns ```true``` if extrinsic success, otherwise ```false```994 */995 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {996 const result = await this.helper.executeExtrinsic(997 signer,998 'api.tx.unique.setCollectionProperties', [collectionId, properties],999 true,1000 );10011002 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1003 }10041005 /**1006 * Get collection properties.1007 * 1008 * @param collectionId ID of collection1009 * @param propertyKeys optionally filter the returned properties to only these keys1010 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1011 * @returns array of key-value pairs1012 */1013 async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1014 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1015 }10161017 /**1018 * Deletes onchain properties from the collection.1019 *1020 * @param signer keyring of signer1021 * @param collectionId ID of collection1022 * @param propertyKeys array of property keys to delete1023 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1024 * @returns ```true``` if extrinsic success, otherwise ```false```1025 */1026 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1027 const result = await this.helper.executeExtrinsic(1028 signer,1029 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1030 true,1031 );10321033 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1034 }10351036 /**1037 * Changes the owner of the token.1038 *1039 * @param signer keyring of signer1040 * @param collectionId ID of collection1041 * @param tokenId ID of token1042 * @param addressObj address of a new owner1043 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1044 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1045 * @returns true if the token success, otherwise false1046 */1047 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1048 const result = await this.helper.executeExtrinsic(1049 signer,1050 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1051 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1052 );10531054 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1055 }10561057 /**1058 *1059 * Change ownership of a token(s) on behalf of the owner.1060 *1061 * @param signer keyring of signer1062 * @param collectionId ID of collection1063 * @param tokenId ID of token1064 * @param fromAddressObj address on behalf of which the token will be sent1065 * @param toAddressObj new token owner1066 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1067 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1068 * @returns true if the token success, otherwise false1069 */1070 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1071 const result = await this.helper.executeExtrinsic(1072 signer,1073 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1074 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1075 );1076 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1077 }10781079 /**1080 *1081 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1082 *1083 * @param signer keyring of signer1084 * @param collectionId ID of collection1085 * @param tokenId ID of token1086 * @param amount amount of tokens to be burned. For NFT must be set to 1n1087 * @example burnToken(aliceKeyring, 10, 5);1088 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1089 */1090 async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1091 const burnResult = await this.helper.executeExtrinsic(1092 signer,1093 'api.tx.unique.burnItem', [collectionId, tokenId, amount],1094 true, // `Unable to burn token for ${label}`,1095 );1096 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1097 if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1098 return burnedTokens.success;1099 }11001101 /**1102 * Destroys a concrete instance of NFT on behalf of the owner1103 *1104 * @param signer keyring of signer1105 * @param collectionId ID of collection1106 * @param tokenId ID of token1107 * @param fromAddressObj address on behalf of which the token will be burnt1108 * @param amount amount of tokens to be burned. For NFT must be set to 1n1109 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1110 * @returns ```true``` if extrinsic success, otherwise ```false```1111 */1112 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1113 const burnResult = await this.helper.executeExtrinsic(1114 signer,1115 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1116 true, // `Unable to burn token from for ${label}`,1117 );1118 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1119 return burnedTokens.success && burnedTokens.tokens.length > 0;1120 }11211122 /**1123 * Set, change, or remove approved address to transfer the ownership of the NFT.1124 *1125 * @param signer keyring of signer1126 * @param collectionId ID of collection1127 * @param tokenId ID of token1128 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1129 * @param amount amount of token to be approved. For NFT must be set to 1n1130 * @returns ```true``` if extrinsic success, otherwise ```false```1131 */1132 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1133 const approveResult = await this.helper.executeExtrinsic(1134 signer,1135 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1136 true, // `Unable to approve token for ${label}`,1137 );11381139 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1140 }11411142 /**1143 * Get the amount of token pieces approved to transfer or burn. Normally 0.1144 *1145 * @param collectionId ID of collection1146 * @param tokenId ID of token1147 * @param toAccountObj address which is approved to use token pieces1148 * @param fromAccountObj address which may have allowed the use of its owned tokens1149 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1150 * @returns number of approved to transfer pieces1151 */1152 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1153 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1154 }11551156 /**1157 * Get the last created token ID in a collection1158 *1159 * @param collectionId ID of collection1160 * @example getLastTokenId(10);1161 * @returns id of the last created token1162 */1163 async getLastTokenId(collectionId: number): Promise<number> {1164 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1165 }11661167 /**1168 * Check if token exists1169 *1170 * @param collectionId ID of collection1171 * @param tokenId ID of token1172 * @example doesTokenExist(10, 20);1173 * @returns true if the token exists, otherwise false1174 */1175 async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1176 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1177 }1178}11791180class NFTnRFT extends CollectionGroup {1181 /**1182 * Get tokens owned by account1183 *1184 * @param collectionId ID of collection1185 * @param addressObj tokens owner1186 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1187 * @returns array of token ids owned by account1188 */1189 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1190 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1191 }11921193 /**1194 * Get token data1195 *1196 * @param collectionId ID of collection1197 * @param tokenId ID of token1198 * @param propertyKeys optionally filter the token properties to only these keys1199 * @param blockHashAt optionally query the data at some block with this hash1200 * @example getToken(10, 5);1201 * @returns human readable token data1202 */1203 async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1204 properties: IProperty[];1205 owner: CrossAccountId;1206 normalizedOwner: CrossAccountId;1207 }| null> {1208 let tokenData;1209 if(typeof blockHashAt === 'undefined') {1210 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1211 }1212 else {1213 if(propertyKeys.length == 0) {1214 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1215 if(!collection) return null;1216 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1217 }1218 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1219 }1220 tokenData = tokenData.toHuman();1221 if (tokenData === null || tokenData.owner === null) return null;1222 const owner = {} as any;1223 for (const key of Object.keys(tokenData.owner)) {1224 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate' 1225 ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key]) 1226 : tokenData.owner[key];1227 }1228 tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1229 return tokenData;1230 }12311232 /**1233 * Set permissions to change token properties1234 *1235 * @param signer keyring of signer1236 * @param collectionId ID of collection1237 * @param permissions permissions to change a property by the collection admin or token owner1238 * @example setTokenPropertyPermissions(1239 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1240 * )1241 * @returns true if extrinsic success otherwise false1242 */1243 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1244 const result = await this.helper.executeExtrinsic(1245 signer,1246 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1247 true,1248 );12491250 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1251 }12521253 /**1254 * Get token property permissions.1255 * 1256 * @param collectionId ID of collection1257 * @param propertyKeys optionally filter the returned property permissions to only these keys1258 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1259 * @returns array of key-permission pairs1260 */1261 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1262 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1263 }12641265 /**1266 * Set token properties1267 *1268 * @param signer keyring of signer1269 * @param collectionId ID of collection1270 * @param tokenId ID of token1271 * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1272 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1273 * @returns ```true``` if extrinsic success, otherwise ```false```1274 */1275 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1276 const result = await this.helper.executeExtrinsic(1277 signer,1278 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1279 true,1280 );12811282 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1283 }12841285 /**1286 * Get properties, metadata assigned to a token.1287 * 1288 * @param collectionId ID of collection1289 * @param tokenId ID of token1290 * @param propertyKeys optionally filter the returned properties to only these keys1291 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1292 * @returns array of key-value pairs1293 */1294 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1295 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1296 }12971298 /**1299 * Delete the provided properties of a token1300 * @param signer keyring of signer1301 * @param collectionId ID of collection1302 * @param tokenId ID of token1303 * @param propertyKeys property keys to be deleted1304 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1305 * @returns ```true``` if extrinsic success, otherwise ```false```1306 */1307 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1308 const result = await this.helper.executeExtrinsic(1309 signer,1310 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1311 true,1312 );13131314 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1315 }13161317 /**1318 * Mint new collection1319 *1320 * @param signer keyring of signer1321 * @param collectionOptions basic collection options and properties1322 * @param mode NFT or RFT type of a collection1323 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1324 * @returns object of the created collection1325 */1326 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1327 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1328 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1329 for (const key of ['name', 'description', 'tokenPrefix']) {1330 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);1331 }1332 const creationResult = await this.helper.executeExtrinsic(1333 signer,1334 'api.tx.unique.createCollectionEx', [collectionOptions],1335 true, // errorLabel,1336 );1337 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1338 }13391340 getCollectionObject(_collectionId: number): any {1341 return null;1342 }13431344 getTokenObject(_collectionId: number, _tokenId: number): any {1345 return null;1346 }1347}134813491350class NFTGroup extends NFTnRFT {1351 /**1352 * Get collection object1353 * @param collectionId ID of collection1354 * @example getCollectionObject(2);1355 * @returns instance of UniqueNFTCollection1356 */1357 getCollectionObject(collectionId: number): UniqueNFTCollection {1358 return new UniqueNFTCollection(collectionId, this.helper);1359 }13601361 /**1362 * Get token object1363 * @param collectionId ID of collection1364 * @param tokenId ID of token1365 * @example getTokenObject(10, 5);1366 * @returns instance of UniqueNFTToken1367 */1368 getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1369 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1370 }13711372 /**1373 * Get token's owner1374 * @param collectionId ID of collection1375 * @param tokenId ID of token1376 * @param blockHashAt optionally query the data at the block with this hash1377 * @example getTokenOwner(10, 5);1378 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1379 */1380 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1381 let owner;1382 if (typeof blockHashAt === 'undefined') {1383 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1384 } else {1385 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1386 }1387 return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1388 }13891390 /**1391 * Is token approved to transfer1392 * @param collectionId ID of collection1393 * @param tokenId ID of token1394 * @param toAccountObj address to be approved1395 * @returns ```true``` if extrinsic success, otherwise ```false```1396 */1397 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1398 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1399 }14001401 /**1402 * Changes the owner of the token.1403 *1404 * @param signer keyring of signer1405 * @param collectionId ID of collection1406 * @param tokenId ID of token1407 * @param addressObj address of a new owner1408 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1409 * @returns ```true``` if extrinsic success, otherwise ```false```1410 */1411 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1412 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1413 }14141415 /**1416 *1417 * Change ownership of a NFT on behalf of the owner.1418 *1419 * @param signer keyring of signer1420 * @param collectionId ID of collection1421 * @param tokenId ID of token1422 * @param fromAddressObj address on behalf of which the token will be sent1423 * @param toAddressObj new token owner1424 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1425 * @returns ```true``` if extrinsic success, otherwise ```false```1426 */1427 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1428 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1429 }14301431 /**1432 * Recursively find the address that owns the token1433 * @param collectionId ID of collection1434 * @param tokenId ID of token1435 * @param blockHashAt1436 * @example getTokenTopmostOwner(10, 5);1437 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1438 */1439 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1440 let owner;1441 if (typeof blockHashAt === 'undefined') {1442 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1443 } else {1444 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1445 }14461447 if (owner === null) return null;14481449 return owner.toHuman();1450 }14511452 /**1453 * Get tokens nested in the provided token1454 * @param collectionId ID of collection1455 * @param tokenId ID of token1456 * @param blockHashAt optionally query the data at the block with this hash1457 * @example getTokenChildren(10, 5);1458 * @returns tokens whose depth of nesting is <= 51459 */1460 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1461 let children;1462 if(typeof blockHashAt === 'undefined') {1463 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1464 } else {1465 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1466 }14671468 return children.toJSON().map((x: any) => {1469 return {collectionId: x.collection, tokenId: x.token};1470 });1471 }14721473 /**1474 * Nest one token into another1475 * @param signer keyring of signer1476 * @param tokenObj token to be nested1477 * @param rootTokenObj token to be parent1478 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1479 * @returns ```true``` if extrinsic success, otherwise ```false```1480 */1481 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1482 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1483 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1484 if(!result) {1485 throw Error('Unable to nest token!');1486 }1487 return result;1488 }14891490 /**1491 * Remove token from nested state1492 * @param signer keyring of signer1493 * @param tokenObj token to unnest1494 * @param rootTokenObj parent of a token1495 * @param toAddressObj address of a new token owner1496 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1497 * @returns ```true``` if extrinsic success, otherwise ```false```1498 */1499 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1500 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1501 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1502 if(!result) {1503 throw Error('Unable to unnest token!');1504 }1505 return result;1506 }15071508 /**1509 * Mint new collection1510 * @param signer keyring of signer1511 * @param collectionOptions Collection options1512 * @example1513 * mintCollection(aliceKeyring, {1514 * name: 'New',1515 * description: 'New collection',1516 * tokenPrefix: 'NEW',1517 * })1518 * @returns object of the created collection1519 */1520 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1521 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1522 }15231524 /**1525 * Mint new token1526 * @param signer keyring of signer1527 * @param data token data1528 * @returns created token object1529 */1530 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1531 const creationResult = await this.helper.executeExtrinsic(1532 signer,1533 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1534 nft: {1535 properties: data.properties,1536 },1537 }],1538 true,1539 );1540 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1541 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1542 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1543 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1544 }15451546 /**1547 * Mint multiple NFT tokens1548 * @param signer keyring of signer1549 * @param collectionId ID of collection1550 * @param tokens array of tokens with owner and properties1551 * @example1552 * mintMultipleTokens(aliceKeyring, 10, [{1553 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1554 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1555 * },{1556 * owner: {Ethereum: "0x9F0583DbB855d..."},1557 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1558 * }]);1559 * @returns ```true``` if extrinsic success, otherwise ```false```1560 */1561 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1562 const creationResult = await this.helper.executeExtrinsic(1563 signer,1564 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1565 true,1566 );1567 const collection = this.getCollectionObject(collectionId);1568 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1569 }15701571 /**1572 * Mint multiple NFT tokens with one owner1573 * @param signer keyring of signer1574 * @param collectionId ID of collection1575 * @param owner tokens owner1576 * @param tokens array of tokens with owner and properties1577 * @example1578 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1579 * properties: [{1580 * key: "gender",1581 * value: "female",1582 * },{1583 * key: "age",1584 * value: "33",1585 * }],1586 * }]);1587 * @returns array of newly created tokens1588 */1589 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1590 const rawTokens = [];1591 for (const token of tokens) {1592 const raw = {NFT: {properties: token.properties}};1593 rawTokens.push(raw);1594 }1595 const creationResult = await this.helper.executeExtrinsic(1596 signer,1597 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1598 true,1599 );1600 const collection = this.getCollectionObject(collectionId);1601 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1602 }16031604 /**1605 * Set, change, or remove approved address to transfer the ownership of the NFT.1606 *1607 * @param signer keyring of signer1608 * @param collectionId ID of collection1609 * @param tokenId ID of token1610 * @param toAddressObj address to approve1611 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1612 * @returns ```true``` if extrinsic success, otherwise ```false```1613 */1614 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId) {1615 return super.approveToken(signer, collectionId, tokenId, toAddressObj, 1n);1616 }1617}161816191620class RFTGroup extends NFTnRFT {1621 /**1622 * Get collection object1623 * @param collectionId ID of collection1624 * @example getCollectionObject(2);1625 * @returns instance of UniqueRFTCollection1626 */1627 getCollectionObject(collectionId: number): UniqueRFTCollection {1628 return new UniqueRFTCollection(collectionId, this.helper);1629 }16301631 /**1632 * Get token object1633 * @param collectionId ID of collection1634 * @param tokenId ID of token1635 * @example getTokenObject(10, 5);1636 * @returns instance of UniqueNFTToken1637 */1638 getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1639 return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1640 }16411642 /**1643 * Get top 10 token owners with the largest number of pieces1644 * @param collectionId ID of collection1645 * @param tokenId ID of token1646 * @example getTokenTop10Owners(10, 5);1647 * @returns array of top 10 owners1648 */1649 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1650 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1651 }16521653 /**1654 * Get number of pieces owned by address1655 * @param collectionId ID of collection1656 * @param tokenId ID of token1657 * @param addressObj address token owner1658 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1659 * @returns number of pieces ownerd by address1660 */1661 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1662 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1663 }16641665 /**1666 * Transfer pieces of token to another address1667 * @param signer keyring of signer1668 * @param collectionId ID of collection1669 * @param tokenId ID of token1670 * @param addressObj address of a new owner1671 * @param amount number of pieces to be transfered1672 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1673 * @returns ```true``` if extrinsic success, otherwise ```false```1674 */1675 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1676 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1677 }16781679 /**1680 * Change ownership of some pieces of RFT on behalf of the owner.1681 * @param signer keyring of signer1682 * @param collectionId ID of collection1683 * @param tokenId ID of token1684 * @param fromAddressObj address on behalf of which the token will be sent1685 * @param toAddressObj new token owner1686 * @param amount number of pieces to be transfered1687 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1688 * @returns ```true``` if extrinsic success, otherwise ```false```1689 */1690 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1691 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1692 }16931694 /**1695 * Mint new collection1696 * @param signer keyring of signer1697 * @param collectionOptions Collection options1698 * @example1699 * mintCollection(aliceKeyring, {1700 * name: 'New',1701 * description: 'New collection',1702 * tokenPrefix: 'NEW',1703 * })1704 * @returns object of the created collection1705 */1706 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1707 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1708 }17091710 /**1711 * Mint new token1712 * @param signer keyring of signer1713 * @param data token data1714 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1715 * @returns created token object1716 */1717 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1718 const creationResult = await this.helper.executeExtrinsic(1719 signer,1720 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1721 refungible: {1722 pieces: data.pieces,1723 properties: data.properties,1724 },1725 }],1726 true,1727 );1728 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1729 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1730 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1731 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1732 }17331734 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1735 throw Error('Not implemented');1736 const creationResult = await this.helper.executeExtrinsic(1737 signer,1738 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1739 true, // `Unable to mint RFT tokens for ${label}`,1740 );1741 const collection = this.getCollectionObject(collectionId);1742 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1743 }17441745 /**1746 * Mint multiple RFT tokens with one owner1747 * @param signer keyring of signer1748 * @param collectionId ID of collection1749 * @param owner tokens owner1750 * @param tokens array of tokens with properties and pieces1751 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1752 * @returns array of newly created RFT tokens1753 */1754 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1755 const rawTokens = [];1756 for (const token of tokens) {1757 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1758 rawTokens.push(raw);1759 }1760 const creationResult = await this.helper.executeExtrinsic(1761 signer,1762 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1763 true,1764 );1765 const collection = this.getCollectionObject(collectionId);1766 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1767 }17681769 /**1770 * Destroys a concrete instance of RFT.1771 * @param signer keyring of signer1772 * @param collectionId ID of collection1773 * @param tokenId ID of token1774 * @param amount number of pieces to be burnt1775 * @example burnToken(aliceKeyring, 10, 5);1776 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1777 */1778 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1779 return await super.burnToken(signer, collectionId, tokenId, amount);1780 }17811782 /**1783 * Destroys a concrete instance of RFT on behalf of the owner.1784 * @param signer keyring of signer1785 * @param collectionId ID of collection1786 * @param tokenId ID of token1787 * @param fromAddressObj address on behalf of which the token will be burnt1788 * @param amount number of pieces to be burnt1789 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1790 * @returns ```true``` if extrinsic success, otherwise ```false```1791 */1792 async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1793 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1794 }17951796 /**1797 * Set, change, or remove approved address to transfer the ownership of the RFT.1798 *1799 * @param signer keyring of signer1800 * @param collectionId ID of collection1801 * @param tokenId ID of token1802 * @param toAddressObj address to approve1803 * @param amount number of pieces to be approved1804 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1805 * @returns true if the token success, otherwise false1806 */1807 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1808 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1809 }18101811 /**1812 * Get total number of pieces1813 * @param collectionId ID of collection1814 * @param tokenId ID of token1815 * @example getTokenTotalPieces(10, 5);1816 * @returns number of pieces1817 */1818 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1819 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1820 }18211822 /**1823 * Change number of token pieces. Signer must be the owner of all token pieces.1824 * @param signer keyring of signer1825 * @param collectionId ID of collection1826 * @param tokenId ID of token1827 * @param amount new number of pieces1828 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1829 * @returns true if the repartion was success, otherwise false1830 */1831 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {1832 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1833 const repartitionResult = await this.helper.executeExtrinsic(1834 signer,1835 'api.tx.unique.repartition', [collectionId, tokenId, amount],1836 true,1837 );1838 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');1839 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');1840 }1841}184218431844class FTGroup extends CollectionGroup {1845 /**1846 * Get collection object1847 * @param collectionId ID of collection1848 * @example getCollectionObject(2);1849 * @returns instance of UniqueFTCollection1850 */1851 getCollectionObject(collectionId: number): UniqueFTCollection {1852 return new UniqueFTCollection(collectionId, this.helper);1853 }18541855 /**1856 * Mint new fungible collection1857 * @param signer keyring of signer1858 * @param collectionOptions Collection options1859 * @param decimalPoints number of token decimals1860 * @example1861 * mintCollection(aliceKeyring, {1862 * name: 'New',1863 * description: 'New collection',1864 * tokenPrefix: 'NEW',1865 * }, 18)1866 * @returns newly created fungible collection1867 */1868 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1869 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1870 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1871 collectionOptions.mode = {fungible: decimalPoints};1872 for (const key of ['name', 'description', 'tokenPrefix']) {1873 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);1874 }1875 const creationResult = await this.helper.executeExtrinsic(1876 signer,1877 'api.tx.unique.createCollectionEx', [collectionOptions],1878 true,1879 );1880 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1881 }18821883 /**1884 * Mint tokens1885 * @param signer keyring of signer1886 * @param collectionId ID of collection1887 * @param owner address owner of new tokens1888 * @param amount amount of tokens to be meanted1889 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1890 * @returns ```true``` if extrinsic success, otherwise ```false```1891 */1892 async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {1893 const creationResult = await this.helper.executeExtrinsic(1894 signer,1895 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1896 fungible: {1897 value: amount,1898 },1899 }],1900 true, // `Unable to mint fungible tokens for ${label}`,1901 );1902 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');1903 }19041905 /**1906 * Mint multiple Fungible tokens with one owner1907 * @param signer keyring of signer1908 * @param collectionId ID of collection1909 * @param owner tokens owner1910 * @param tokens array of tokens with properties and pieces1911 * @returns ```true``` if extrinsic success, otherwise ```false```1912 */1913 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: {value: bigint}[], owner: ICrossAccountId): Promise<boolean> {1914 const rawTokens = [];1915 for (const token of tokens) {1916 const raw = {Fungible: {Value: token.value}};1917 rawTokens.push(raw);1918 }1919 const creationResult = await this.helper.executeExtrinsic(1920 signer,1921 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1922 true,1923 );1924 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');1925 }19261927 /**1928 * Get the top 10 owners with the largest balance for the Fungible collection1929 * @param collectionId ID of collection1930 * @example getTop10Owners(10);1931 * @returns array of ```ICrossAccountId```1932 */1933 async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {1934 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1935 }19361937 /**1938 * Get account balance1939 * @param collectionId ID of collection1940 * @param addressObj address of owner1941 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})1942 * @returns amount of fungible tokens owned by address1943 */1944 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1945 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1946 }19471948 /**1949 * Transfer tokens to address1950 * @param signer keyring of signer1951 * @param collectionId ID of collection1952 * @param toAddressObj address recipient1953 * @param amount amount of tokens to be sent1954 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1955 * @returns ```true``` if extrinsic success, otherwise ```false```1956 */1957 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {1958 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1959 }19601961 /**1962 * Transfer some tokens on behalf of the owner.1963 * @param signer keyring of signer1964 * @param collectionId ID of collection1965 * @param fromAddressObj address on behalf of which tokens will be sent1966 * @param toAddressObj address where token to be sent1967 * @param amount number of tokens to be sent1968 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);1969 * @returns ```true``` if extrinsic success, otherwise ```false```1970 */1971 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {1972 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1973 }19741975 /**1976 * Destroy some amount of tokens1977 * @param signer keyring of signer1978 * @param collectionId ID of collection1979 * @param amount amount of tokens to be destroyed1980 * @example burnTokens(aliceKeyring, 10, 1000n);1981 * @returns ```true``` if extrinsic success, otherwise ```false```1982 */1983 async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise<boolean> {1984 return await super.burnToken(signer, collectionId, 0, amount);1985 }19861987 /**1988 * Burn some tokens on behalf of the owner.1989 * @param signer keyring of signer1990 * @param collectionId ID of collection1991 * @param fromAddressObj address on behalf of which tokens will be burnt1992 * @param amount amount of tokens to be burnt1993 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1994 * @returns ```true``` if extrinsic success, otherwise ```false```1995 */1996 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1997 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);1998 }19992000 /**2001 * Get total collection supply2002 * @param collectionId2003 * @returns2004 */2005 async getTotalPieces(collectionId: number): Promise<bigint> {2006 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2007 }20082009 /**2010 * Set, change, or remove approved address to transfer tokens.2011 *2012 * @param signer keyring of signer2013 * @param collectionId ID of collection2014 * @param toAddressObj address to be approved2015 * @param amount amount of tokens to be approved2016 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2017 * @returns ```true``` if extrinsic success, otherwise ```false```2018 */2019 async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2020 return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2021 }20222023 /**2024 * Get amount of fungible tokens approved to transfer2025 * @param collectionId ID of collection2026 * @param fromAddressObj owner of tokens2027 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2028 * @returns number of tokens approved for the transfer2029 */2030 async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2031 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2032 }2033}203420352036class ChainGroup extends HelperGroup<ChainHelperBase> {2037 /**2038 * Get system properties of a chain2039 * @example getChainProperties();2040 * @returns ss58Format, token decimals, and token symbol2041 */2042 getChainProperties(): IChainProperties {2043 const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2044 return {2045 ss58Format: properties.ss58Format.toJSON(),2046 tokenDecimals: properties.tokenDecimals.toJSON(),2047 tokenSymbol: properties.tokenSymbol.toJSON(),2048 };2049 }20502051 /**2052 * Get chain header2053 * @example getLatestBlockNumber();2054 * @returns the number of the last block2055 */2056 async getLatestBlockNumber(): Promise<number> {2057 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2058 }20592060 /**2061 * Get block hash by block number2062 * @param blockNumber number of block2063 * @example getBlockHashByNumber(12345);2064 * @returns hash of a block2065 */2066 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2067 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2068 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2069 return blockHash;2070 }20712072 // TODO add docs2073 async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2074 const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2075 if (!blockHash) return null;2076 return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2077 }20782079 /**2080 * Get account nonce2081 * @param address substrate address2082 * @example getNonce("5GrwvaEF5zXb26Fz...");2083 * @returns number, account's nonce2084 */2085 async getNonce(address: TSubstrateAccount): Promise<number> {2086 return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2087 }2088}20892090class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2091 /**2092 * Get substrate address balance2093 * @param address substrate address2094 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2095 * @returns amount of tokens on address2096 */2097 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2098 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2099 }21002101 /**2102 * Transfer tokens to substrate address2103 * @param signer keyring of signer2104 * @param address substrate address of a recipient2105 * @param amount amount of tokens to be transfered2106 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2107 * @returns ```true``` if extrinsic success, otherwise ```false```2108 */2109 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2110 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}`*/);21112112 let transfer = {from: null, to: null, amount: 0n} as any;2113 result.result.events.forEach(({event: {data, method, section}}) => {2114 if ((section === 'balances') && (method === 'Transfer')) {2115 transfer = {2116 from: this.helper.address.normalizeSubstrate(data[0]),2117 to: this.helper.address.normalizeSubstrate(data[1]),2118 amount: BigInt(data[2]),2119 };2120 }2121 });2122 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from 2123 && this.helper.address.normalizeSubstrate(address) === transfer.to 2124 && BigInt(amount) === transfer.amount;2125 return isSuccess;2126 }21272128 /**2129 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2130 * @param address substrate address2131 * @returns2132 */2133 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2134 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2135 return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2136 }2137}21382139class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2140 /**2141 * Get ethereum address balance2142 * @param address ethereum address2143 * @example getEthereum("0x9F0583DbB855d...")2144 * @returns amount of tokens on address2145 */2146 async getEthereum(address: TEthereumAccount): Promise<bigint> {2147 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2148 }21492150 /**2151 * Transfer tokens to address2152 * @param signer keyring of signer2153 * @param address Ethereum address of a recipient2154 * @param amount amount of tokens to be transfered2155 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2156 * @returns ```true``` if extrinsic success, otherwise ```false```2157 */2158 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2159 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);21602161 let transfer = {from: null, to: null, amount: 0n} as any;2162 result.result.events.forEach(({event: {data, method, section}}) => {2163 if ((section === 'balances') && (method === 'Transfer')) {2164 transfer = {2165 from: data[0].toString(),2166 to: data[1].toString(),2167 amount: BigInt(data[2]),2168 };2169 }2170 });2171 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from 2172 && address === transfer.to 2173 && BigInt(amount) === transfer.amount;2174 return isSuccess;2175 }2176}21772178class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2179 subBalanceGroup: SubstrateBalanceGroup<T>;2180 ethBalanceGroup: EthereumBalanceGroup<T>;21812182 constructor(helper: T) {2183 super(helper);2184 this.subBalanceGroup = new SubstrateBalanceGroup(helper);2185 this.ethBalanceGroup = new EthereumBalanceGroup(helper);2186 }21872188 getCollectionCreationPrice(): bigint {2189 return 2n * this.getOneTokenNominal();2190 }2191 /**2192 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2193 * @example getOneTokenNominal()2194 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2195 */2196 getOneTokenNominal(): bigint {2197 const chainProperties = this.helper.chain.getChainProperties();2198 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2199 }22002201 /**2202 * Get substrate address balance2203 * @param address substrate address2204 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2205 * @returns amount of tokens on address2206 */2207 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2208 return this.subBalanceGroup.getSubstrate(address);2209 }22102211 /**2212 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2213 * @param address substrate address2214 * @returns2215 */2216 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2217 return this.subBalanceGroup.getSubstrateFull(address);2218 }22192220 /**2221 * Get ethereum address balance2222 * @param address ethereum address2223 * @example getEthereum("0x9F0583DbB855d...")2224 * @returns amount of tokens on address2225 */2226 async getEthereum(address: TEthereumAccount): Promise<bigint> {2227 return this.ethBalanceGroup.getEthereum(address);2228 }22292230 /**2231 * Transfer tokens to substrate address2232 * @param signer keyring of signer2233 * @param address substrate address of a recipient2234 * @param amount amount of tokens to be transfered2235 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2236 * @returns ```true``` if extrinsic success, otherwise ```false```2237 */2238 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2239 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2240 }2241}22422243class AddressGroup extends HelperGroup<ChainHelperBase> {2244 /**2245 * Normalizes the address to the specified ss58 format, by default ```42```.2246 * @param address substrate address2247 * @param ss58Format format for address conversion, by default ```42```2248 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2249 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2250 */2251 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2252 return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2253 }22542255 /**2256 * Get address in the connected chain format2257 * @param address substrate address2258 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2259 * @returns address in chain format2260 */2261 normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2262 return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2263 }22642265 /**2266 * Get substrate mirror of an ethereum address2267 * @param ethAddress ethereum address2268 * @param toChainFormat false for normalized account2269 * @example ethToSubstrate('0x9F0583DbB855d...')2270 * @returns substrate mirror of a provided ethereum address2271 */2272 ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): TSubstrateAccount {2273 return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2274 }22752276 /**2277 * Get ethereum mirror of a substrate address2278 * @param subAddress substrate account2279 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2280 * @returns ethereum mirror of a provided substrate address2281 */2282 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2283 return CrossAccountId.translateSubToEth(subAddress);2284 }2285}22862287class StakingGroup extends HelperGroup<UniqueHelper> {2288 /**2289 * Stake tokens for App Promotion2290 * @param signer keyring of signer2291 * @param amountToStake amount of tokens to stake2292 * @param label extra label for log2293 * @returns2294 */2295 async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2296 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2297 const _stakeResult = await this.helper.executeExtrinsic(2298 signer, 'api.tx.appPromotion.stake',2299 [amountToStake], true,2300 );2301 // TODO extract info from stakeResult2302 return true;2303 }23042305 /**2306 * Unstake tokens for App Promotion2307 * @param signer keyring of signer2308 * @param amountToUnstake amount of tokens to unstake2309 * @param label extra label for log2310 * @returns block number where balances will be unlocked2311 */2312 async unstake(signer: TSigner, label?: string): Promise<number> {2313 if(typeof label === 'undefined') label = `${signer.address}`;2314 const _unstakeResult = await this.helper.executeExtrinsic(2315 signer, 'api.tx.appPromotion.unstake',2316 [], true,2317 );2318 // TODO extract block number fron events2319 return 1;2320 }23212322 /**2323 * Get total staked amount for address2324 * @param address substrate or ethereum address2325 * @returns total staked amount2326 */2327 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2328 if (address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2329 return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2330 }23312332 /**2333 * Get total staked per block2334 * @param address substrate or ethereum address2335 * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2336 */2337 async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2338 const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2339 return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => {2340 return { 2341 block: block.toBigInt(),2342 amount: amount.toBigInt(),2343 };2344 });2345 }23462347 /**2348 * Get total pending unstake amount for address2349 * @param address substrate or ethereum address2350 * @returns total pending unstake amount2351 */2352 async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2353 return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2354 }23552356 /**2357 * Get pending unstake amount per block for address2358 * @param address substrate or ethereum address2359 * @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 block2360 */2361 async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2362 const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2363 const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => {2364 return {2365 block: block.toBigInt(),2366 amount: amount.toBigInt(),2367 };2368 });2369 return result;2370 }2371}23722373class SchedulerGroup extends HelperGroup<UniqueHelper> {2374 constructor(helper: UniqueHelper) {2375 super(helper);2376 }23772378 async cancelScheduled(signer: TSigner, scheduledId: string) {2379 return this.helper.executeExtrinsic(2380 signer,2381 'api.tx.scheduler.cancelNamed',2382 [scheduledId],2383 true,2384 );2385 }23862387 async changePriority(signer: TSigner, scheduledId: string, priority: number) {2388 return this.helper.executeExtrinsic(2389 signer,2390 'api.tx.scheduler.changeNamedPriority',2391 [scheduledId, priority],2392 true,2393 );2394 }23952396 scheduleAt<T extends UniqueHelper>(2397 scheduledId: string,2398 executionBlockNumber: number,2399 options: ISchedulerOptions = {},2400 ) {2401 return this.schedule<T>('scheduleNamed', scheduledId, executionBlockNumber, options);2402 }24032404 scheduleAfter<T extends UniqueHelper>(2405 scheduledId: string,2406 blocksBeforeExecution: number,2407 options: ISchedulerOptions = {},2408 ) {2409 return this.schedule<T>('scheduleNamedAfter', scheduledId, blocksBeforeExecution, options);2410 }24112412 schedule<T extends UniqueHelper>(2413 scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter',2414 scheduledId: string,2415 blocksNum: number,2416 options: ISchedulerOptions = {},2417 ) {2418 // eslint-disable-next-line @typescript-eslint/naming-convention2419 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2420 return this.helper.clone(ScheduledHelperType, {2421 scheduleFn,2422 scheduledId,2423 blocksNum,2424 options,2425 }) as T;2426 }2427}24282429class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {2430 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2431 await this.helper.executeExtrinsic(2432 signer,2433 'api.tx.foreignAssets.registerForeignAsset',2434 [ownerAddress, location, metadata],2435 true,2436 );2437 }24382439 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {2440 await this.helper.executeExtrinsic(2441 signer,2442 'api.tx.foreignAssets.updateForeignAsset',2443 [foreignAssetId, location, metadata],2444 true,2445 );2446 }2447}24482449class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {2450 palletName: string;24512452 constructor(helper: T, palletName: string) {2453 super(helper);24542455 this.palletName = palletName;2456 }24572458 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {2459 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);2460 }2461}24622463class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2464 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {2465 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);2466 }24672468 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {2469 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);2470 }24712472 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {2473 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);2474 }2475}24762477class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2478 async accounts(address: string, currencyId: any) {2479 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;2480 return BigInt(free);2481 }2482}24832484class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {2485 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {2486 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);2487 }24882489 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {2490 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);2491 }24922493 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {2494 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);2495 }24962497 async account(assetId: string | number, address: string) {2498 const accountAsset = (2499 await this.helper.callRpc('api.query.assets.account', [assetId, address])2500 ).toJSON()! as any;25012502 if (accountAsset !== null) {2503 return BigInt(accountAsset['balance']);2504 } else {2505 return null;2506 }2507 }2508}25092510class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {2511 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {2512 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);2513 }2514}25152516class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {2517 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {2518 const apiPrefix = 'api.tx.assetManager.';25192520 const registerTx = this.helper.constructApiCall(2521 apiPrefix + 'registerForeignAsset',2522 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],2523 );25242525 const setUnitsTx = this.helper.constructApiCall(2526 apiPrefix + 'setAssetUnitsPerSecond',2527 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],2528 );25292530 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);2531 const encodedProposal = batchCall?.method.toHex() || '';2532 return encodedProposal;2533 }25342535 async assetTypeId(location: any) {2536 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);2537 }2538}25392540class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {2541 async notePreimage(signer: TSigner, encodedProposal: string) {2542 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);2543 }25442545 externalProposeMajority(proposalHash: string) {2546 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);2547 }25482549 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {2550 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);2551 }25522553 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {2554 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);2555 }2556}25572558class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {2559 collective: string;25602561 constructor(helper: MoonbeamHelper, collective: string) {2562 super(helper);25632564 this.collective = collective;2565 }25662567 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {2568 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);2569 }25702571 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {2572 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);2573 }25742575 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {2576 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);2577 }25782579 async proposalCount() {2580 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));2581 }2582}25832584export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;2585export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;25862587export class UniqueHelper extends ChainHelperBase {2588 balance: BalanceGroup<UniqueHelper>;2589 collection: CollectionGroup;2590 nft: NFTGroup;2591 rft: RFTGroup;2592 ft: FTGroup;2593 staking: StakingGroup;2594 scheduler: SchedulerGroup;2595 foreignAssets: ForeignAssetsGroup;2596 xcm: XcmGroup<UniqueHelper>;2597 xTokens: XTokensGroup<UniqueHelper>;2598 tokens: TokensGroup<UniqueHelper>;25992600 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2601 super(logger, options.helperBase ?? UniqueHelper);26022603 this.balance = new BalanceGroup(this);2604 this.address = new AddressGroup(this);2605 this.collection = new CollectionGroup(this);2606 this.nft = new NFTGroup(this);2607 this.rft = new RFTGroup(this);2608 this.ft = new FTGroup(this);2609 this.staking = new StakingGroup(this);2610 this.scheduler = new SchedulerGroup(this);2611 this.foreignAssets = new ForeignAssetsGroup(this);2612 this.xcm = new XcmGroup(this, 'polkadotXcm');2613 this.xTokens = new XTokensGroup(this);2614 this.tokens = new TokensGroup(this);2615 }26162617 getSudo<T extends UniqueHelper>() {2618 // eslint-disable-next-line @typescript-eslint/naming-convention2619 const SudoHelperType = SudoHelper(this.helperBase);2620 return this.clone(SudoHelperType) as T;2621 }2622}26232624export class XcmChainHelper extends ChainHelperBase {2625 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {2626 const wsProvider = new WsProvider(wsEndpoint);2627 this.api = new ApiPromise({2628 provider: wsProvider,2629 });2630 await this.api.isReadyOrError;2631 this.network = await UniqueHelper.detectNetwork(this.api);2632 }2633}26342635export class RelayHelper extends XcmChainHelper {2636 xcm: XcmGroup<RelayHelper>;26372638 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2639 super(logger, options.helperBase ?? RelayHelper);26402641 this.xcm = new XcmGroup(this, 'xcmPallet');2642 }2643}26442645export class WestmintHelper extends XcmChainHelper {2646 balance: SubstrateBalanceGroup<WestmintHelper>;2647 xcm: XcmGroup<WestmintHelper>;2648 assets: AssetsGroup<WestmintHelper>;2649 xTokens: XTokensGroup<WestmintHelper>;26502651 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2652 super(logger, options.helperBase ?? WestmintHelper);26532654 this.balance = new SubstrateBalanceGroup(this);2655 this.xcm = new XcmGroup(this, 'polkadotXcm');2656 this.assets = new AssetsGroup(this);2657 this.xTokens = new XTokensGroup(this);2658 }2659}26602661export class MoonbeamHelper extends XcmChainHelper {2662 balance: EthereumBalanceGroup<MoonbeamHelper>;2663 assetManager: MoonbeamAssetManagerGroup;2664 assets: AssetsGroup<MoonbeamHelper>;2665 xTokens: XTokensGroup<MoonbeamHelper>;2666 democracy: MoonbeamDemocracyGroup;2667 collective: {2668 council: MoonbeamCollectiveGroup,2669 techCommittee: MoonbeamCollectiveGroup,2670 };26712672 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2673 super(logger, options.helperBase ?? MoonbeamHelper);26742675 this.balance = new EthereumBalanceGroup(this);2676 this.assetManager = new MoonbeamAssetManagerGroup(this);2677 this.assets = new AssetsGroup(this);2678 this.xTokens = new XTokensGroup(this);2679 this.democracy = new MoonbeamDemocracyGroup(this);2680 this.collective = {2681 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),2682 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),2683 };2684 }2685}26862687export class AcalaHelper extends XcmChainHelper {2688 balance: SubstrateBalanceGroup<AcalaHelper>;2689 assetRegistry: AcalaAssetRegistryGroup;2690 xTokens: XTokensGroup<AcalaHelper>;2691 tokens: TokensGroup<AcalaHelper>;26922693 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2694 super(logger, options.helperBase ?? AcalaHelper);26952696 this.balance = new SubstrateBalanceGroup(this);2697 this.assetRegistry = new AcalaAssetRegistryGroup(this);2698 this.xTokens = new XTokensGroup(this);2699 this.tokens = new TokensGroup(this);2700 }27012702 getSudo<T extends AcalaHelper>() {2703 // eslint-disable-next-line @typescript-eslint/naming-convention2704 const SudoHelperType = SudoHelper(this.helperBase);2705 return this.clone(SudoHelperType) as T;2706 }2707}27082709// eslint-disable-next-line @typescript-eslint/naming-convention2710function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2711 return class extends Base {2712 scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter';2713 scheduledId: string;2714 blocksNum: number;2715 options: ISchedulerOptions;27162717 constructor(...args: any[]) {2718 const logger = args[0] as ILogger;2719 const options = args[1] as {2720 scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter',2721 scheduledId: string,2722 blocksNum: number,2723 options: ISchedulerOptions2724 };27252726 super(logger);27272728 this.scheduleFn = options.scheduleFn;2729 this.scheduledId = options.scheduledId;2730 this.blocksNum = options.blocksNum;2731 this.options = options.options;2732 }27332734 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {2735 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);2736 const extrinsic = 'api.tx.scheduler.' + this.scheduleFn;27372738 return super.executeExtrinsic(2739 sender,2740 extrinsic,2741 [2742 this.scheduledId,2743 this.blocksNum,2744 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,2745 this.options.priority ?? null,2746 {Value: scheduledTx},2747 ],2748 expectSuccess,2749 );2750 }2751 };2752}27532754// eslint-disable-next-line @typescript-eslint/naming-convention2755function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {2756 return class extends Base {2757 constructor(...args: any[]) {2758 super(...args);2759 }27602761 executeExtrinsic (2762 sender: IKeyringPair,2763 extrinsic: string,2764 params: any[],2765 expectSuccess?: boolean,2766 ): Promise<ITransactionResult> {2767 const call = this.constructApiCall(extrinsic, params);27682769 return super.executeExtrinsic(2770 sender,2771 'api.tx.sudo.sudo',2772 [call],2773 expectSuccess,2774 );2775 }2776 };2777}27782779export class UniqueBaseCollection {2780 helper: UniqueHelper;2781 collectionId: number;27822783 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2784 this.collectionId = collectionId;2785 this.helper = uniqueHelper;2786 }27872788 async getData() {2789 return await this.helper.collection.getData(this.collectionId);2790 }27912792 async getLastTokenId() {2793 return await this.helper.collection.getLastTokenId(this.collectionId);2794 }27952796 async doesTokenExist(tokenId: number) {2797 return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);2798 }27992800 async getAdmins() {2801 return await this.helper.collection.getAdmins(this.collectionId);2802 }28032804 async getAllowList() {2805 return await this.helper.collection.getAllowList(this.collectionId);2806 }28072808 async getEffectiveLimits() {2809 return await this.helper.collection.getEffectiveLimits(this.collectionId);2810 }28112812 async getProperties(propertyKeys?: string[] | null) {2813 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);2814 }28152816 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2817 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2818 }28192820 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {2821 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);2822 }28232824 async confirmSponsorship(signer: TSigner) {2825 return await this.helper.collection.confirmSponsorship(signer, this.collectionId);2826 }28272828 async removeSponsor(signer: TSigner) {2829 return await this.helper.collection.removeSponsor(signer, this.collectionId);2830 }28312832 async setLimits(signer: TSigner, limits: ICollectionLimits) {2833 return await this.helper.collection.setLimits(signer, this.collectionId, limits);2834 }28352836 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {2837 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);2838 }28392840 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {2841 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);2842 }28432844 async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {2845 return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);2846 }28472848 async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {2849 return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);2850 }28512852 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {2853 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);2854 }28552856 async setProperties(signer: TSigner, properties: IProperty[]) {2857 return await this.helper.collection.setProperties(signer, this.collectionId, properties);2858 }28592860 async deleteProperties(signer: TSigner, propertyKeys: string[]) {2861 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);2862 }28632864 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {2865 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);2866 }28672868 async enableNesting(signer: TSigner, permissions: INestingPermissions) {2869 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);2870 }28712872 async disableNesting(signer: TSigner) {2873 return await this.helper.collection.disableNesting(signer, this.collectionId);2874 }28752876 async burn(signer: TSigner) {2877 return await this.helper.collection.burn(signer, this.collectionId);2878 }28792880 scheduleAt<T extends UniqueHelper>(2881 scheduledId: string,2882 executionBlockNumber: number,2883 options: ISchedulerOptions = {},2884 ) {2885 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(scheduledId, executionBlockNumber, options);2886 return new UniqueBaseCollection(this.collectionId, scheduledHelper);2887 }28882889 scheduleAfter<T extends UniqueHelper>(2890 scheduledId: string,2891 blocksBeforeExecution: number,2892 options: ISchedulerOptions = {},2893 ) {2894 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);2895 return new UniqueBaseCollection(this.collectionId, scheduledHelper);2896 }28972898 getSudo<T extends UniqueHelper>() {2899 return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());2900 }2901}290229032904export class UniqueNFTCollection extends UniqueBaseCollection {2905 getTokenObject(tokenId: number) {2906 return new UniqueNFToken(tokenId, this);2907 }29082909 async getTokensByAddress(addressObj: ICrossAccountId) {2910 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);2911 }29122913 async getToken(tokenId: number, blockHashAt?: string) {2914 return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);2915 }29162917 async getTokenOwner(tokenId: number, blockHashAt?: string) {2918 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);2919 }29202921 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {2922 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);2923 }29242925 async getTokenChildren(tokenId: number, blockHashAt?: string) {2926 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2927 }29282929 async getPropertyPermissions(propertyKeys: string[] | null = null) {2930 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);2931 }29322933 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {2934 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);2935 }29362937 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2938 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2939 }29402941 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2942 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);2943 }29442945 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {2946 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);2947 }29482949 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {2950 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);2951 }29522953 async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {2954 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});2955 }29562957 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) {2958 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);2959 }29602961 async burnToken(signer: TSigner, tokenId: number) {2962 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);2963 }29642965 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {2966 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);2967 }29682969 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2970 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);2971 }29722973 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {2974 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);2975 }29762977 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {2978 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);2979 }29802981 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {2982 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);2983 }29842985 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {2986 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);2987 }29882989 scheduleAt<T extends UniqueHelper>(2990 scheduledId: string,2991 executionBlockNumber: number,2992 options: ISchedulerOptions = {},2993 ) {2994 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(scheduledId, executionBlockNumber, options);2995 return new UniqueNFTCollection(this.collectionId, scheduledHelper);2996 }29972998 scheduleAfter<T extends UniqueHelper>(2999 scheduledId: string,3000 blocksBeforeExecution: number,3001 options: ISchedulerOptions = {},3002 ) {3003 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3004 return new UniqueNFTCollection(this.collectionId, scheduledHelper);3005 }30063007 getSudo<T extends UniqueHelper>() {3008 return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());3009 }3010}301130123013export class UniqueRFTCollection extends UniqueBaseCollection {3014 getTokenObject(tokenId: number) {3015 return new UniqueRFToken(tokenId, this);3016 }30173018 async getToken(tokenId: number, blockHashAt?: string) {3019 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3020 }30213022 async getTokensByAddress(addressObj: ICrossAccountId) {3023 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3024 }30253026 async getTop10TokenOwners(tokenId: number) {3027 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3028 }30293030 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3031 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3032 }30333034 async getTokenTotalPieces(tokenId: number) {3035 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3036 }30373038 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3039 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3040 }30413042 async getPropertyPermissions(propertyKeys: string[] | null = null) {3043 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3044 }30453046 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3047 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3048 }30493050 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {3051 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3052 }30533054 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3055 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3056 }30573058 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {3059 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3060 }30613062 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3063 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3064 }30653066 async mintToken(signer: TSigner, pieces=1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3067 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3068 }30693070 async mintMultipleTokens(signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) {3071 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3072 }30733074 async burnToken(signer: TSigner, tokenId: number, amount=1n) {3075 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3076 }30773078 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n) {3079 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3080 }30813082 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3083 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3084 }30853086 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3087 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3088 }30893090 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3091 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3092 }30933094 scheduleAt<T extends UniqueHelper>(3095 scheduledId: string,3096 executionBlockNumber: number,3097 options: ISchedulerOptions = {},3098 ) {3099 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(scheduledId, executionBlockNumber, options);3100 return new UniqueRFTCollection(this.collectionId, scheduledHelper);3101 }31023103 scheduleAfter<T extends UniqueHelper>(3104 scheduledId: string,3105 blocksBeforeExecution: number,3106 options: ISchedulerOptions = {},3107 ) {3108 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3109 return new UniqueRFTCollection(this.collectionId, scheduledHelper);3110 }31113112 getSudo<T extends UniqueHelper>() {3113 return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());3114 }3115}311631173118export class UniqueFTCollection extends UniqueBaseCollection {3119 async getBalance(addressObj: ICrossAccountId) {3120 return await this.helper.ft.getBalance(this.collectionId, addressObj);3121 }31223123 async getTotalPieces() {3124 return await this.helper.ft.getTotalPieces(this.collectionId);3125 }31263127 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3128 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3129 }31303131 async getTop10Owners() {3132 return await this.helper.ft.getTop10Owners(this.collectionId);3133 }31343135 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {3136 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3137 }31383139 async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {3140 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3141 }31423143 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3144 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3145 }31463147 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3148 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3149 }31503151 async burnTokens(signer: TSigner, amount=1n) {3152 return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3153 }31543155 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3156 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3157 }31583159 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3160 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3161 }31623163 scheduleAt<T extends UniqueHelper>(3164 scheduledId: string,3165 executionBlockNumber: number,3166 options: ISchedulerOptions = {},3167 ) {3168 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(scheduledId, executionBlockNumber, options);3169 return new UniqueFTCollection(this.collectionId, scheduledHelper);3170 }31713172 scheduleAfter<T extends UniqueHelper>(3173 scheduledId: string,3174 blocksBeforeExecution: number,3175 options: ISchedulerOptions = {},3176 ) {3177 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3178 return new UniqueFTCollection(this.collectionId, scheduledHelper);3179 }31803181 getSudo<T extends UniqueHelper>() {3182 return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());3183 }3184}318531863187export class UniqueBaseToken {3188 collection: UniqueNFTCollection | UniqueRFTCollection;3189 collectionId: number;3190 tokenId: number;31913192 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3193 this.collection = collection;3194 this.collectionId = collection.collectionId;3195 this.tokenId = tokenId;3196 }31973198 async getNextSponsored(addressObj: ICrossAccountId) {3199 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3200 }32013202 async getProperties(propertyKeys?: string[] | null) {3203 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3204 }32053206 async setProperties(signer: TSigner, properties: IProperty[]) {3207 return await this.collection.setTokenProperties(signer, this.tokenId, properties);3208 }32093210 async deleteProperties(signer: TSigner, propertyKeys: string[]) {3211 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3212 }32133214 async doesExist() {3215 return await this.collection.doesTokenExist(this.tokenId);3216 }32173218 nestingAccount() {3219 return this.collection.helper.util.getTokenAccount(this);3220 }32213222 scheduleAt<T extends UniqueHelper>(3223 scheduledId: string,3224 executionBlockNumber: number,3225 options: ISchedulerOptions = {},3226 ) {3227 const scheduledCollection = this.collection.scheduleAt<T>(scheduledId, executionBlockNumber, options);3228 return new UniqueBaseToken(this.tokenId, scheduledCollection);3229 }32303231 scheduleAfter<T extends UniqueHelper>(3232 scheduledId: string,3233 blocksBeforeExecution: number,3234 options: ISchedulerOptions = {},3235 ) {3236 const scheduledCollection = this.collection.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3237 return new UniqueBaseToken(this.tokenId, scheduledCollection);3238 }32393240 getSudo<T extends UniqueHelper>() {3241 return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());3242 }3243}324432453246export class UniqueNFToken extends UniqueBaseToken {3247 collection: UniqueNFTCollection;32483249 constructor(tokenId: number, collection: UniqueNFTCollection) {3250 super(tokenId, collection);3251 this.collection = collection;3252 }32533254 async getData(blockHashAt?: string) {3255 return await this.collection.getToken(this.tokenId, blockHashAt);3256 }32573258 async getOwner(blockHashAt?: string) {3259 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3260 }32613262 async getTopmostOwner(blockHashAt?: string) {3263 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3264 }32653266 async getChildren(blockHashAt?: string) {3267 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3268 }32693270 async nest(signer: TSigner, toTokenObj: IToken) {3271 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3272 }32733274 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3275 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3276 }32773278 async transfer(signer: TSigner, addressObj: ICrossAccountId) {3279 return await this.collection.transferToken(signer, this.tokenId, addressObj);3280 }32813282 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3283 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3284 }32853286 async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3287 return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3288 }32893290 async isApproved(toAddressObj: ICrossAccountId) {3291 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3292 }32933294 async burn(signer: TSigner) {3295 return await this.collection.burnToken(signer, this.tokenId);3296 }32973298 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3299 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3300 }33013302 scheduleAt<T extends UniqueHelper>(3303 scheduledId: string,3304 executionBlockNumber: number,3305 options: ISchedulerOptions = {},3306 ) {3307 const scheduledCollection = this.collection.scheduleAt<T>(scheduledId, executionBlockNumber, options);3308 return new UniqueNFToken(this.tokenId, scheduledCollection);3309 }33103311 scheduleAfter<T extends UniqueHelper>(3312 scheduledId: string,3313 blocksBeforeExecution: number,3314 options: ISchedulerOptions = {},3315 ) {3316 const scheduledCollection = this.collection.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3317 return new UniqueNFToken(this.tokenId, scheduledCollection);3318 }33193320 getSudo<T extends UniqueHelper>() {3321 return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());3322 }3323}33243325export class UniqueRFToken extends UniqueBaseToken {3326 collection: UniqueRFTCollection;33273328 constructor(tokenId: number, collection: UniqueRFTCollection) {3329 super(tokenId, collection);3330 this.collection = collection;3331 }33323333 async getData(blockHashAt?: string) {3334 return await this.collection.getToken(this.tokenId, blockHashAt);3335 }33363337 async getTop10Owners() {3338 return await this.collection.getTop10TokenOwners(this.tokenId);3339 }33403341 async getBalance(addressObj: ICrossAccountId) {3342 return await this.collection.getTokenBalance(this.tokenId, addressObj);3343 }33443345 async getTotalPieces() {3346 return await this.collection.getTokenTotalPieces(this.tokenId);3347 }33483349 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3350 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3351 }33523353 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {3354 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3355 }33563357 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3358 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3359 }33603361 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3362 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3363 }33643365 async repartition(signer: TSigner, amount: bigint) {3366 return await this.collection.repartitionToken(signer, this.tokenId, amount);3367 }33683369 async burn(signer: TSigner, amount=1n) {3370 return await this.collection.burnToken(signer, this.tokenId, amount);3371 }33723373 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3374 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3375 }33763377 scheduleAt<T extends UniqueHelper>(3378 scheduledId: string,3379 executionBlockNumber: number,3380 options: ISchedulerOptions = {},3381 ) {3382 const scheduledCollection = this.collection.scheduleAt<T>(scheduledId, executionBlockNumber, options);3383 return new UniqueRFToken(this.tokenId, scheduledCollection);3384 }33853386 scheduleAfter<T extends UniqueHelper>(3387 scheduledId: string,3388 blocksBeforeExecution: number,3389 options: ISchedulerOptions = {},3390 ) {3391 const scheduledCollection = this.collection.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3392 return new UniqueRFToken(this.tokenId, scheduledCollection);3393 }33943395 getSudo<T extends UniqueHelper>() {3396 return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());3397 }3398}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034/* eslint-disable @typescript-eslint/no-var-requires */5/* eslint-disable function-call-argument-newline */6/* eslint-disable no-prototype-builtins */78import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';11import {IKeyringPair} from '@polkadot/types/types';12import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata} from './types';1314export class CrossAccountId implements ICrossAccountId {15 Substrate?: TSubstrateAccount;16 Ethereum?: TEthereumAccount;1718 constructor(account: ICrossAccountId) {19 if (account.Substrate) this.Substrate = account.Substrate;20 if (account.Ethereum) this.Ethereum = account.Ethereum;21 }2223 static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {24 switch (domain) {25 case 'Substrate': return new CrossAccountId({Substrate: account.address});26 case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();27 }28 }2930 static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {31 return new CrossAccountId({Substrate: address.substrate, Ethereum: address.ethereum});32 }3334 static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {35 return encodeAddress(decodeAddress(address), ss58Format);36 }3738 static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {39 return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});40 }41 42 withNormalizedSubstrate(ss58Format = 42): CrossAccountId {43 if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);44 return this;45 }4647 static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {48 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));49 }5051 toEthereum(): CrossAccountId {52 if (this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});53 return this;54 }5556 static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {57 return evmToAddress(address, ss58Format);58 }5960 toSubstrate(ss58Format?: number): CrossAccountId {61 if (this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});62 return this;63 }64 65 toLowerCase(): CrossAccountId {66 if (this.Substrate) this.Substrate = this.Substrate.toLowerCase();67 if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();68 return this;69 }70}7172const nesting = {73 toChecksumAddress(address: string): string {74 if (typeof address === 'undefined') return '';7576 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);7778 address = address.toLowerCase().replace(/^0x/i,'');79 const addressHash = keccakAsHex(address).replace(/^0x/i,'');80 const checksumAddress = ['0x'];8182 for (let i = 0; i < address.length; i++) {83 // If ith character is 8 to f then make it uppercase84 if (parseInt(addressHash[i], 16) > 7) {85 checksumAddress.push(address[i].toUpperCase());86 } else {87 checksumAddress.push(address[i]);88 }89 }90 return checksumAddress.join('');91 },92 tokenIdToAddress(collectionId: number, tokenId: number) {93 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);94 },95};9697class UniqueUtil {98 static transactionStatus = {99 NOT_READY: 'NotReady',100 FAIL: 'Fail',101 SUCCESS: 'Success',102 };103104 static chainLogType = {105 EXTRINSIC: 'extrinsic',106 RPC: 'rpc',107 };108109 static getTokenAccount(token: IToken): CrossAccountId {110 return new CrossAccountId({Ethereum: this.getTokenAddress(token)});111 }112113 static getTokenAddress(token: IToken): string {114 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);115 }116117 static getDefaultLogger(): ILogger {118 return {119 log(msg: any, level = 'INFO') {120 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));121 },122 level: {123 ERROR: 'ERROR',124 WARNING: 'WARNING',125 INFO: 'INFO',126 },127 };128 }129130 static vec2str(arr: string[] | number[]) {131 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');132 }133134 static str2vec(string: string) {135 if (typeof string !== 'string') return string;136 return Array.from(string).map(x => x.charCodeAt(0));137 }138139 static fromSeed(seed: string, ss58Format = 42) {140 const keyring = new Keyring({type: 'sr25519', ss58Format});141 return keyring.addFromUri(seed);142 }143144 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {145 if (creationResult.status !== this.transactionStatus.SUCCESS) {146 throw Error('Unable to create collection!');147 }148149 let collectionId = null;150 creationResult.result.events.forEach(({event: {data, method, section}}) => {151 if ((section === 'common') && (method === 'CollectionCreated')) {152 collectionId = parseInt(data[0].toString(), 10);153 }154 });155156 if (collectionId === null) {157 throw Error('No CollectionCreated event was found!');158 }159160 return collectionId;161 }162163 static extractTokensFromCreationResult(creationResult: ITransactionResult): {164 success: boolean, 165 tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],166 } {167 if (creationResult.status !== this.transactionStatus.SUCCESS) {168 throw Error('Unable to create tokens!');169 }170 let success = false;171 const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];172 creationResult.result.events.forEach(({event: {data, method, section}}) => {173 if (method === 'ExtrinsicSuccess') {174 success = true;175 } else if ((section === 'common') && (method === 'ItemCreated')) {176 tokens.push({177 collectionId: parseInt(data[0].toString(), 10),178 tokenId: parseInt(data[1].toString(), 10),179 owner: data[2].toHuman(),180 amount: data[3].toBigInt(),181 });182 }183 });184 return {success, tokens};185 }186187 static extractTokensFromBurnResult(burnResult: ITransactionResult): {188 success: boolean, 189 tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],190 } {191 if (burnResult.status !== this.transactionStatus.SUCCESS) {192 throw Error('Unable to burn tokens!');193 }194 let success = false;195 const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];196 burnResult.result.events.forEach(({event: {data, method, section}}) => {197 if (method === 'ExtrinsicSuccess') {198 success = true;199 } else if ((section === 'common') && (method === 'ItemDestroyed')) {200 tokens.push({201 collectionId: parseInt(data[0].toString(), 10),202 tokenId: parseInt(data[1].toString(), 10),203 owner: data[2].toHuman(),204 amount: data[3].toBigInt(),205 });206 }207 });208 return {success, tokens};209 }210211 static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {212 let eventId = null;213 events.forEach(({event: {data, method, section}}) => {214 if ((section === expectedSection) && (method === expectedMethod)) {215 eventId = parseInt(data[0].toString(), 10);216 }217 });218219 if (eventId === null) {220 throw Error(`No ${expectedMethod} event was found!`);221 }222 return eventId === collectionId;223 }224225 static isTokenTransferSuccess(events: {event: IEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {226 const normalizeAddress = (address: string | ICrossAccountId) => {227 if(typeof address === 'string') return address;228 const obj = {} as any;229 Object.keys(address).forEach(k => {230 obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];231 });232 if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);233 if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();234 return address;235 };236 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;237 events.forEach(({event: {data, method, section}}) => {238 if ((section === 'common') && (method === 'Transfer')) {239 const hData = (data as any).toJSON();240 transfer = {241 collectionId: hData[0],242 tokenId: hData[1],243 from: normalizeAddress(hData[2]),244 to: normalizeAddress(hData[3]),245 amount: BigInt(hData[4]),246 };247 }248 });249 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;250 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);251 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);252 isSuccess = isSuccess && amount === transfer.amount;253 return isSuccess;254 }255256 static bigIntToDecimals(number: bigint, decimals = 18) {257 const numberStr = number.toString();258 const dotPos = numberStr.length - decimals;259 260 if (dotPos <= 0) {261 return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;262 } else {263 const intPart = numberStr.substring(0, dotPos);264 const fractPart = numberStr.substring(dotPos);265 return intPart + '.' + fractPart;266 }267 }268}269270class UniqueEventHelper {271 private static extractIndex(index: any): [number, number] | string {272 if(index.toRawType() === '[u8;2]') return [index[0], index[1]];273 return index.toJSON();274 }275276 private static extractSub(data: any, subTypes: any): {[key: string]: any} {277 let obj: any = {};278 let index = 0;279280 if (data.entries) {281 for(const [key, value] of data.entries()) {282 obj[key] = this.extractData(value, subTypes[index]);283 index++;284 }285 } else obj = data.toJSON();286287 return obj;288 }289 290 private static extractData(data: any, type: any): any {291 if(!type) return data.toHuman();292 if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();293 if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();294 if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);295 return data.toHuman();296 }297298 public static extractEvents(records: ITransactionResult): IEvent[] {299 const parsedEvents: IEvent[] = [];300301 records.result.events.forEach((record) => {302 const {event, phase} = record;303 const types = (event as any).typeDef;304305 const eventData: IEvent = {306 section: event.section.toString(),307 method: event.method.toString(),308 index: this.extractIndex(event.index),309 data: [],310 phase: phase.toJSON(),311 };312313 event.data.forEach((val: any, index: number) => {314 eventData.data.push(this.extractData(val, types[index]));315 });316317 parsedEvents.push(eventData);318 });319320 return parsedEvents;321 }322}323324export class ChainHelperBase {325 helperBase: any;326327 transactionStatus = UniqueUtil.transactionStatus;328 chainLogType = UniqueUtil.chainLogType;329 util: typeof UniqueUtil;330 eventHelper: typeof UniqueEventHelper;331 logger: ILogger;332 api: ApiPromise | null;333 forcedNetwork: TNetworks | null;334 network: TNetworks | null;335 chainLog: IUniqueHelperLog[];336 children: ChainHelperBase[];337 address: AddressGroup;338 chain: ChainGroup;339340 constructor(logger?: ILogger, helperBase?: any) {341 this.helperBase = helperBase;342343 this.util = UniqueUtil;344 this.eventHelper = UniqueEventHelper;345 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();346 this.logger = logger;347 this.api = null;348 this.forcedNetwork = null;349 this.network = null;350 this.chainLog = [];351 this.children = [];352 this.address = new AddressGroup(this);353 this.chain = new ChainGroup(this);354 }355356 clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {357 Object.setPrototypeOf(helperCls.prototype, this);358 const newHelper = new helperCls(this.logger, options);359360 newHelper.api = this.api;361 newHelper.network = this.network;362 newHelper.forceNetwork = this.forceNetwork;363364 this.children.push(newHelper);365366 return newHelper;367 }368369 getApi(): ApiPromise {370 if(this.api === null) throw Error('API not initialized');371 return this.api;372 }373374 clearChainLog(): void {375 this.chainLog = [];376 }377378 forceNetwork(value: TNetworks): void {379 this.forcedNetwork = value;380 }381382 async connect(wsEndpoint: string, listeners?: IApiListeners) {383 if (this.api !== null) throw Error('Already connected');384 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);385 this.api = api;386 this.network = network;387 }388389 async disconnect() {390 for (const child of this.children) {391 child.clearApi();392 }393394 if (this.api === null) return;395 await this.api.disconnect();396 this.clearApi();397 }398399 clearApi() {400 this.api = null;401 this.network = null;402 }403404 static async detectNetwork(api: ApiPromise): Promise<TNetworks> {405 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;406 const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];407408 if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;409410 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;411 return 'opal';412 }413414 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {415 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});416 await api.isReady;417418 const network = await this.detectNetwork(api);419420 await api.disconnect();421422 return network;423 }424425 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{426 api: ApiPromise;427 network: TNetworks;428 }> {429 console.log('createConnection network = ', network);430 if(typeof network === 'undefined' || network === null) network = 'opal';431 const supportedRPC = {432 opal: {433 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,434 },435 quartz: {436 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,437 },438 unique: {439 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,440 },441 rococo: {},442 westend: {},443 moonbeam: {},444 moonriver: {},445 acala: {},446 karura: {},447 westmint: {},448 };449 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);450 const rpc = supportedRPC[network];451452 // TODO: investigate how to replace rpc in runtime453 // api._rpcCore.addUserInterfaces(rpc);454455 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});456457 await api.isReadyOrError;458459 if (typeof listeners === 'undefined') listeners = {};460 for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {461 if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;462 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);463 }464465 return {api, network};466 }467468 getTransactionStatus(data: {events: {event: IEvent}[], status: any}) {469 const {events, status} = data;470 if (status.isReady) {471 return this.transactionStatus.NOT_READY;472 }473 if (status.isBroadcast) {474 return this.transactionStatus.NOT_READY;475 }476 if (status.isInBlock || status.isFinalized) {477 const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');478 if (errors.length > 0) {479 return this.transactionStatus.FAIL;480 }481 if (events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {482 return this.transactionStatus.SUCCESS;483 }484 }485486 return this.transactionStatus.FAIL;487 }488489 signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {490 const sign = (callback: any) => {491 if(options !== null) return transaction.signAndSend(sender, options, callback);492 return transaction.signAndSend(sender, callback);493 };494 // eslint-disable-next-line no-async-promise-executor495 return new Promise(async (resolve, reject) => {496 try {497 const unsub = await sign((result: any) => {498 const status = this.getTransactionStatus(result);499500 if (status === this.transactionStatus.SUCCESS) {501 this.logger.log(`${label} successful`);502 unsub();503 resolve({result, status});504 } else if (status === this.transactionStatus.FAIL) {505 let moduleError = null;506507 if (result.hasOwnProperty('dispatchError')) {508 const dispatchError = result['dispatchError'];509510 if (dispatchError) {511 if (dispatchError.isModule) {512 const modErr = dispatchError.asModule;513 const errorMeta = dispatchError.registry.findMetaError(modErr);514515 moduleError = `${errorMeta.section}.${errorMeta.name}`;516 } else {517 moduleError = dispatchError.toHuman();518 }519 } else {520 this.logger.log(result, this.logger.level.ERROR);521 }522 }523524 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);525 unsub();526 reject({status, moduleError, result});527 }528 });529 } catch (e) {530 this.logger.log(e, this.logger.level.ERROR);531 reject(e);532 }533 });534 }535536 constructApiCall(apiCall: string, params: any[]) {537 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);538 let call = this.getApi() as any;539 for(const part of apiCall.slice(4).split('.')) {540 call = call[part];541 }542 return call(...params);543 }544545 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {546 if(this.api === null) throw Error('API not initialized');547 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);548549 const startTime = (new Date()).getTime();550 let result: ITransactionResult;551 let events: IEvent[] = [];552 try {553 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;554 events = this.eventHelper.extractEvents(result);555 }556 catch(e) {557 if(!(e as object).hasOwnProperty('status')) throw e;558 result = e as ITransactionResult;559 }560561 const endTime = (new Date()).getTime();562563 const log = {564 executedAt: endTime,565 executionTime: endTime - startTime,566 type: this.chainLogType.EXTRINSIC,567 status: result.status,568 call: extrinsic,569 signer: this.getSignerAddress(sender),570 params,571 } as IUniqueHelperLog;572573 if(result.status !== this.transactionStatus.SUCCESS) {574 if (result.moduleError) log.moduleError = result.moduleError;575 else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;576 }577 if(events.length > 0) log.events = events;578579 this.chainLog.push(log);580581 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {582 if (result.moduleError) throw Error(`${result.moduleError}`);583 else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));584 }585 return result;586 }587588 async callRpc(rpc: string, params?: any[]) {589 if(typeof params === 'undefined') params = [];590 if(this.api === null) throw Error('API not initialized');591 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);592593 const startTime = (new Date()).getTime();594 let result;595 let error = null;596 const log = {597 type: this.chainLogType.RPC,598 call: rpc,599 params,600 } as IUniqueHelperLog;601602 try {603 result = await this.constructApiCall(rpc, params);604 }605 catch(e) {606 error = e;607 }608609 const endTime = (new Date()).getTime();610611 log.executedAt = endTime;612 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';613 log.executionTime = endTime - startTime;614615 this.chainLog.push(log);616617 if(error !== null) throw error;618619 return result;620 }621622 getSignerAddress(signer: IKeyringPair | string): string {623 if(typeof signer === 'string') return signer;624 return signer.address;625 }626627 fetchAllPalletNames(): string[] {628 if(this.api === null) throw Error('API not initialized');629 return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());630 }631632 fetchMissingPalletNames(requiredPallets: string[]): string[] {633 const palletNames = this.fetchAllPalletNames();634 return requiredPallets.filter(p => !palletNames.includes(p));635 }636}637638639class HelperGroup<T extends ChainHelperBase> {640 helper: T;641642 constructor(uniqueHelper: T) {643 this.helper = uniqueHelper;644 }645}646647648class CollectionGroup extends HelperGroup<UniqueHelper> {649 /**650 * Get number of blocks when sponsored transaction is available.651 *652 * @param collectionId ID of collection653 * @param tokenId ID of token654 * @param addressObj address for which the sponsorship is checked655 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});656 * @returns number of blocks or null if sponsorship hasn't been set657 */658 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {659 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();660 }661662 /**663 * Get the number of created collections.664 *665 * @returns number of created collections666 */667 async getTotalCount(): Promise<number> {668 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();669 }670671 /**672 * Get information about the collection with additional data,673 * including the number of tokens it contains, its administrators,674 * the normalized address of the collection's owner, and decoded name and description.675 *676 * @param collectionId ID of collection677 * @example await getData(2)678 * @returns collection information object679 */680 async getData(collectionId: number): Promise<{681 id: number;682 name: string;683 description: string;684 tokensCount: number;685 admins: CrossAccountId[];686 normalizedOwner: TSubstrateAccount;687 raw: any688 } | null> {689 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);690 const humanCollection = collection.toHuman(), collectionData = {691 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],692 raw: humanCollection,693 } as any, jsonCollection = collection.toJSON();694 if (humanCollection === null) return null;695 collectionData.raw.limits = jsonCollection.limits;696 collectionData.raw.permissions = jsonCollection.permissions;697 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);698 for (const key of ['name', 'description']) {699 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);700 }701702 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))703 ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)704 : 0;705 collectionData.admins = await this.getAdmins(collectionId);706707 return collectionData;708 }709710 /**711 * Get the addresses of the collection's administrators, optionally normalized.712 *713 * @param collectionId ID of collection714 * @param normalize whether to normalize the addresses to the default ss58 format715 * @example await getAdmins(1)716 * @returns array of administrators717 */718 async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {719 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();720721 return normalize722 ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())723 : admins;724 }725726 /**727 * Get the addresses added to the collection allow-list, optionally normalized.728 * @param collectionId ID of collection729 * @param normalize whether to normalize the addresses to the default ss58 format730 * @example await getAllowList(1)731 * @returns array of allow-listed addresses732 */733 async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {734 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();735 return normalize736 ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())737 : allowListed;738 }739740 /**741 * Get the effective limits of the collection instead of null for default values742 *743 * @param collectionId ID of collection744 * @example await getEffectiveLimits(2)745 * @returns object of collection limits746 */747 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {748 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();749 }750751 /**752 * Burns the collection if the signer has sufficient permissions and collection is empty.753 *754 * @param signer keyring of signer755 * @param collectionId ID of collection756 * @example await helper.collection.burn(aliceKeyring, 3);757 * @returns ```true``` if extrinsic success, otherwise ```false```758 */759 async burn(signer: TSigner, collectionId: number): Promise<boolean> {760 const result = await this.helper.executeExtrinsic(761 signer,762 'api.tx.unique.destroyCollection', [collectionId],763 true,764 );765766 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');767 }768769 /**770 * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.771 *772 * @param signer keyring of signer773 * @param collectionId ID of collection774 * @param sponsorAddress Sponsor substrate address775 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")776 * @returns ```true``` if extrinsic success, otherwise ```false```777 */778 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {779 const result = await this.helper.executeExtrinsic(780 signer,781 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],782 true,783 );784785 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet');786 }787788 /**789 * Confirms consent to sponsor the collection on behalf of the signer.790 *791 * @param signer keyring of signer792 * @param collectionId ID of collection793 * @example confirmSponsorship(aliceKeyring, 10)794 * @returns ```true``` if extrinsic success, otherwise ```false```795 */796 async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {797 const result = await this.helper.executeExtrinsic(798 signer,799 'api.tx.unique.confirmSponsorship', [collectionId],800 true,801 );802803 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed');804 }805806 /**807 * Removes the sponsor of a collection, regardless if it consented or not.808 *809 * @param signer keyring of signer810 * @param collectionId ID of collection811 * @example removeSponsor(aliceKeyring, 10)812 * @returns ```true``` if extrinsic success, otherwise ```false```813 */814 async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {815 const result = await this.helper.executeExtrinsic(816 signer,817 'api.tx.unique.removeCollectionSponsor', [collectionId],818 true,819 );820821 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorRemoved');822 }823824 /**825 * Sets the limits of the collection. At least one limit must be specified for a correct call.826 *827 * @param signer keyring of signer828 * @param collectionId ID of collection829 * @param limits collection limits object830 * @example831 * await setLimits(832 * aliceKeyring,833 * 10,834 * {835 * sponsorTransferTimeout: 0,836 * ownerCanDestroy: false837 * }838 * )839 * @returns ```true``` if extrinsic success, otherwise ```false```840 */841 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {842 const result = await this.helper.executeExtrinsic(843 signer,844 'api.tx.unique.setCollectionLimits', [collectionId, limits],845 true,846 );847848 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet');849 }850851 /**852 * Changes the owner of the collection to the new Substrate address.853 *854 * @param signer keyring of signer855 * @param collectionId ID of collection856 * @param ownerAddress substrate address of new owner857 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")858 * @returns ```true``` if extrinsic success, otherwise ```false```859 */860 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {861 const result = await this.helper.executeExtrinsic(862 signer,863 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],864 true,865 );866867 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged');868 }869870 /**871 * Adds a collection administrator.872 *873 * @param signer keyring of signer874 * @param collectionId ID of collection875 * @param adminAddressObj Administrator address (substrate or ethereum)876 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})877 * @returns ```true``` if extrinsic success, otherwise ```false```878 */879 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {880 const result = await this.helper.executeExtrinsic(881 signer,882 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],883 true,884 );885886 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded');887 }888889 /**890 * Removes a collection administrator.891 *892 * @param signer keyring of signer893 * @param collectionId ID of collection894 * @param adminAddressObj Administrator address (substrate or ethereum)895 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})896 * @returns ```true``` if extrinsic success, otherwise ```false```897 */898 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {899 const result = await this.helper.executeExtrinsic(900 signer,901 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],902 true,903 );904905 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved');906 }907908 /**909 * Check if user is in allow list.910 * 911 * @param collectionId ID of collection912 * @param user Account to check913 * @example await getAdmins(1)914 * @returns is user in allow list915 */916 async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {917 return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();918 }919920 /**921 * Adds an address to allow list922 * @param signer keyring of signer923 * @param collectionId ID of collection924 * @param addressObj address to add to the allow list925 * @returns ```true``` if extrinsic success, otherwise ```false```926 */927 async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {928 const result = await this.helper.executeExtrinsic(929 signer,930 'api.tx.unique.addToAllowList', [collectionId, addressObj],931 true,932 );933934 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');935 }936937 /**938 * Removes an address from allow list939 *940 * @param signer keyring of signer941 * @param collectionId ID of collection942 * @param addressObj address to remove from the allow list943 * @returns ```true``` if extrinsic success, otherwise ```false```944 */945 async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {946 const result = await this.helper.executeExtrinsic(947 signer,948 'api.tx.unique.removeFromAllowList', [collectionId, addressObj],949 true,950 );951952 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved');953 }954955 /**956 * Sets onchain permissions for selected collection.957 *958 * @param signer keyring of signer959 * @param collectionId ID of collection960 * @param permissions collection permissions object961 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});962 * @returns ```true``` if extrinsic success, otherwise ```false```963 */964 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {965 const result = await this.helper.executeExtrinsic(966 signer,967 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],968 true,969 );970971 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet');972 }973974 /**975 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.976 *977 * @param signer keyring of signer978 * @param collectionId ID of collection979 * @param permissions nesting permissions object980 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});981 * @returns ```true``` if extrinsic success, otherwise ```false```982 */983 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {984 return await this.setPermissions(signer, collectionId, {nesting: permissions});985 }986987 /**988 * Disables nesting for selected collection.989 *990 * @param signer keyring of signer991 * @param collectionId ID of collection992 * @example disableNesting(aliceKeyring, 10);993 * @returns ```true``` if extrinsic success, otherwise ```false```994 */995 async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {996 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});997 }998999 /**1000 * Sets onchain properties to the collection.1001 *1002 * @param signer keyring of signer1003 * @param collectionId ID of collection1004 * @param properties array of property objects1005 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1006 * @returns ```true``` if extrinsic success, otherwise ```false```1007 */1008 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1009 const result = await this.helper.executeExtrinsic(1010 signer,1011 'api.tx.unique.setCollectionProperties', [collectionId, properties],1012 true,1013 );10141015 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1016 }10171018 /**1019 * Get collection properties.1020 * 1021 * @param collectionId ID of collection1022 * @param propertyKeys optionally filter the returned properties to only these keys1023 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1024 * @returns array of key-value pairs1025 */1026 async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1027 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1028 }10291030 /**1031 * Deletes onchain properties from the collection.1032 *1033 * @param signer keyring of signer1034 * @param collectionId ID of collection1035 * @param propertyKeys array of property keys to delete1036 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1037 * @returns ```true``` if extrinsic success, otherwise ```false```1038 */1039 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1040 const result = await this.helper.executeExtrinsic(1041 signer,1042 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1043 true,1044 );10451046 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1047 }10481049 /**1050 * Changes the owner of the token.1051 *1052 * @param signer keyring of signer1053 * @param collectionId ID of collection1054 * @param tokenId ID of token1055 * @param addressObj address of a new owner1056 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1057 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1058 * @returns true if the token success, otherwise false1059 */1060 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1061 const result = await this.helper.executeExtrinsic(1062 signer,1063 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1064 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1065 );10661067 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1068 }10691070 /**1071 *1072 * Change ownership of a token(s) on behalf of the owner.1073 *1074 * @param signer keyring of signer1075 * @param collectionId ID of collection1076 * @param tokenId ID of token1077 * @param fromAddressObj address on behalf of which the token will be sent1078 * @param toAddressObj new token owner1079 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1080 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1081 * @returns true if the token success, otherwise false1082 */1083 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1084 const result = await this.helper.executeExtrinsic(1085 signer,1086 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1087 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1088 );1089 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1090 }10911092 /**1093 *1094 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1095 *1096 * @param signer keyring of signer1097 * @param collectionId ID of collection1098 * @param tokenId ID of token1099 * @param amount amount of tokens to be burned. For NFT must be set to 1n1100 * @example burnToken(aliceKeyring, 10, 5);1101 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1102 */1103 async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1104 const burnResult = await this.helper.executeExtrinsic(1105 signer,1106 'api.tx.unique.burnItem', [collectionId, tokenId, amount],1107 true, // `Unable to burn token for ${label}`,1108 );1109 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1110 if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1111 return burnedTokens.success;1112 }11131114 /**1115 * Destroys a concrete instance of NFT on behalf of the owner1116 *1117 * @param signer keyring of signer1118 * @param collectionId ID of collection1119 * @param tokenId ID of token1120 * @param fromAddressObj address on behalf of which the token will be burnt1121 * @param amount amount of tokens to be burned. For NFT must be set to 1n1122 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1123 * @returns ```true``` if extrinsic success, otherwise ```false```1124 */1125 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1126 const burnResult = await this.helper.executeExtrinsic(1127 signer,1128 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1129 true, // `Unable to burn token from for ${label}`,1130 );1131 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1132 return burnedTokens.success && burnedTokens.tokens.length > 0;1133 }11341135 /**1136 * Set, change, or remove approved address to transfer the ownership of the NFT.1137 *1138 * @param signer keyring of signer1139 * @param collectionId ID of collection1140 * @param tokenId ID of token1141 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1142 * @param amount amount of token to be approved. For NFT must be set to 1n1143 * @returns ```true``` if extrinsic success, otherwise ```false```1144 */1145 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1146 const approveResult = await this.helper.executeExtrinsic(1147 signer,1148 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1149 true, // `Unable to approve token for ${label}`,1150 );11511152 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1153 }11541155 /**1156 * Get the amount of token pieces approved to transfer or burn. Normally 0.1157 *1158 * @param collectionId ID of collection1159 * @param tokenId ID of token1160 * @param toAccountObj address which is approved to use token pieces1161 * @param fromAccountObj address which may have allowed the use of its owned tokens1162 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1163 * @returns number of approved to transfer pieces1164 */1165 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1166 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1167 }11681169 /**1170 * Get the last created token ID in a collection1171 *1172 * @param collectionId ID of collection1173 * @example getLastTokenId(10);1174 * @returns id of the last created token1175 */1176 async getLastTokenId(collectionId: number): Promise<number> {1177 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1178 }11791180 /**1181 * Check if token exists1182 *1183 * @param collectionId ID of collection1184 * @param tokenId ID of token1185 * @example doesTokenExist(10, 20);1186 * @returns true if the token exists, otherwise false1187 */1188 async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1189 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1190 }1191}11921193class NFTnRFT extends CollectionGroup {1194 /**1195 * Get tokens owned by account1196 *1197 * @param collectionId ID of collection1198 * @param addressObj tokens owner1199 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1200 * @returns array of token ids owned by account1201 */1202 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1203 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1204 }12051206 /**1207 * Get token data1208 *1209 * @param collectionId ID of collection1210 * @param tokenId ID of token1211 * @param propertyKeys optionally filter the token properties to only these keys1212 * @param blockHashAt optionally query the data at some block with this hash1213 * @example getToken(10, 5);1214 * @returns human readable token data1215 */1216 async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1217 properties: IProperty[];1218 owner: CrossAccountId;1219 normalizedOwner: CrossAccountId;1220 }| null> {1221 let tokenData;1222 if(typeof blockHashAt === 'undefined') {1223 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1224 }1225 else {1226 if(propertyKeys.length == 0) {1227 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1228 if(!collection) return null;1229 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1230 }1231 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1232 }1233 tokenData = tokenData.toHuman();1234 if (tokenData === null || tokenData.owner === null) return null;1235 const owner = {} as any;1236 for (const key of Object.keys(tokenData.owner)) {1237 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate' 1238 ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key]) 1239 : tokenData.owner[key];1240 }1241 tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1242 return tokenData;1243 }12441245 /**1246 * Set permissions to change token properties1247 *1248 * @param signer keyring of signer1249 * @param collectionId ID of collection1250 * @param permissions permissions to change a property by the collection admin or token owner1251 * @example setTokenPropertyPermissions(1252 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1253 * )1254 * @returns true if extrinsic success otherwise false1255 */1256 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1257 const result = await this.helper.executeExtrinsic(1258 signer,1259 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1260 true,1261 );12621263 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1264 }12651266 /**1267 * Get token property permissions.1268 * 1269 * @param collectionId ID of collection1270 * @param propertyKeys optionally filter the returned property permissions to only these keys1271 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1272 * @returns array of key-permission pairs1273 */1274 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1275 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1276 }12771278 /**1279 * Set token properties1280 *1281 * @param signer keyring of signer1282 * @param collectionId ID of collection1283 * @param tokenId ID of token1284 * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1285 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1286 * @returns ```true``` if extrinsic success, otherwise ```false```1287 */1288 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1289 const result = await this.helper.executeExtrinsic(1290 signer,1291 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1292 true,1293 );12941295 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1296 }12971298 /**1299 * Get properties, metadata assigned to a token.1300 * 1301 * @param collectionId ID of collection1302 * @param tokenId ID of token1303 * @param propertyKeys optionally filter the returned properties to only these keys1304 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1305 * @returns array of key-value pairs1306 */1307 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1308 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1309 }13101311 /**1312 * Delete the provided properties of a token1313 * @param signer keyring of signer1314 * @param collectionId ID of collection1315 * @param tokenId ID of token1316 * @param propertyKeys property keys to be deleted1317 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1318 * @returns ```true``` if extrinsic success, otherwise ```false```1319 */1320 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1321 const result = await this.helper.executeExtrinsic(1322 signer,1323 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1324 true,1325 );13261327 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1328 }13291330 /**1331 * Mint new collection1332 *1333 * @param signer keyring of signer1334 * @param collectionOptions basic collection options and properties1335 * @param mode NFT or RFT type of a collection1336 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1337 * @returns object of the created collection1338 */1339 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1340 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1341 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1342 for (const key of ['name', 'description', 'tokenPrefix']) {1343 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);1344 }1345 const creationResult = await this.helper.executeExtrinsic(1346 signer,1347 'api.tx.unique.createCollectionEx', [collectionOptions],1348 true, // errorLabel,1349 );1350 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1351 }13521353 getCollectionObject(_collectionId: number): any {1354 return null;1355 }13561357 getTokenObject(_collectionId: number, _tokenId: number): any {1358 return null;1359 }1360}136113621363class NFTGroup extends NFTnRFT {1364 /**1365 * Get collection object1366 * @param collectionId ID of collection1367 * @example getCollectionObject(2);1368 * @returns instance of UniqueNFTCollection1369 */1370 getCollectionObject(collectionId: number): UniqueNFTCollection {1371 return new UniqueNFTCollection(collectionId, this.helper);1372 }13731374 /**1375 * Get token object1376 * @param collectionId ID of collection1377 * @param tokenId ID of token1378 * @example getTokenObject(10, 5);1379 * @returns instance of UniqueNFTToken1380 */1381 getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1382 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1383 }13841385 /**1386 * Get token's owner1387 * @param collectionId ID of collection1388 * @param tokenId ID of token1389 * @param blockHashAt optionally query the data at the block with this hash1390 * @example getTokenOwner(10, 5);1391 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1392 */1393 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1394 let owner;1395 if (typeof blockHashAt === 'undefined') {1396 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1397 } else {1398 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1399 }1400 return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1401 }14021403 /**1404 * Is token approved to transfer1405 * @param collectionId ID of collection1406 * @param tokenId ID of token1407 * @param toAccountObj address to be approved1408 * @returns ```true``` if extrinsic success, otherwise ```false```1409 */1410 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1411 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1412 }14131414 /**1415 * Changes the owner of the token.1416 *1417 * @param signer keyring of signer1418 * @param collectionId ID of collection1419 * @param tokenId ID of token1420 * @param addressObj address of a new owner1421 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1422 * @returns ```true``` if extrinsic success, otherwise ```false```1423 */1424 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1425 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1426 }14271428 /**1429 *1430 * Change ownership of a NFT on behalf of the owner.1431 *1432 * @param signer keyring of signer1433 * @param collectionId ID of collection1434 * @param tokenId ID of token1435 * @param fromAddressObj address on behalf of which the token will be sent1436 * @param toAddressObj new token owner1437 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1438 * @returns ```true``` if extrinsic success, otherwise ```false```1439 */1440 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1441 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1442 }14431444 /**1445 * Recursively find the address that owns the token1446 * @param collectionId ID of collection1447 * @param tokenId ID of token1448 * @param blockHashAt1449 * @example getTokenTopmostOwner(10, 5);1450 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1451 */1452 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1453 let owner;1454 if (typeof blockHashAt === 'undefined') {1455 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1456 } else {1457 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1458 }14591460 if (owner === null) return null;14611462 return owner.toHuman();1463 }14641465 /**1466 * Get tokens nested in the provided token1467 * @param collectionId ID of collection1468 * @param tokenId ID of token1469 * @param blockHashAt optionally query the data at the block with this hash1470 * @example getTokenChildren(10, 5);1471 * @returns tokens whose depth of nesting is <= 51472 */1473 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1474 let children;1475 if(typeof blockHashAt === 'undefined') {1476 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1477 } else {1478 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1479 }14801481 return children.toJSON().map((x: any) => {1482 return {collectionId: x.collection, tokenId: x.token};1483 });1484 }14851486 /**1487 * Nest one token into another1488 * @param signer keyring of signer1489 * @param tokenObj token to be nested1490 * @param rootTokenObj token to be parent1491 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1492 * @returns ```true``` if extrinsic success, otherwise ```false```1493 */1494 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1495 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1496 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1497 if(!result) {1498 throw Error('Unable to nest token!');1499 }1500 return result;1501 }15021503 /**1504 * Remove token from nested state1505 * @param signer keyring of signer1506 * @param tokenObj token to unnest1507 * @param rootTokenObj parent of a token1508 * @param toAddressObj address of a new token owner1509 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1510 * @returns ```true``` if extrinsic success, otherwise ```false```1511 */1512 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1513 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1514 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1515 if(!result) {1516 throw Error('Unable to unnest token!');1517 }1518 return result;1519 }15201521 /**1522 * Mint new collection1523 * @param signer keyring of signer1524 * @param collectionOptions Collection options1525 * @example1526 * mintCollection(aliceKeyring, {1527 * name: 'New',1528 * description: 'New collection',1529 * tokenPrefix: 'NEW',1530 * })1531 * @returns object of the created collection1532 */1533 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1534 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1535 }15361537 /**1538 * Mint new token1539 * @param signer keyring of signer1540 * @param data token data1541 * @returns created token object1542 */1543 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1544 const creationResult = await this.helper.executeExtrinsic(1545 signer,1546 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1547 nft: {1548 properties: data.properties,1549 },1550 }],1551 true,1552 );1553 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1554 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1555 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1556 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1557 }15581559 /**1560 * Mint multiple NFT tokens1561 * @param signer keyring of signer1562 * @param collectionId ID of collection1563 * @param tokens array of tokens with owner and properties1564 * @example1565 * mintMultipleTokens(aliceKeyring, 10, [{1566 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1567 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1568 * },{1569 * owner: {Ethereum: "0x9F0583DbB855d..."},1570 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1571 * }]);1572 * @returns ```true``` if extrinsic success, otherwise ```false```1573 */1574 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1575 const creationResult = await this.helper.executeExtrinsic(1576 signer,1577 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1578 true,1579 );1580 const collection = this.getCollectionObject(collectionId);1581 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1582 }15831584 /**1585 * Mint multiple NFT tokens with one owner1586 * @param signer keyring of signer1587 * @param collectionId ID of collection1588 * @param owner tokens owner1589 * @param tokens array of tokens with owner and properties1590 * @example1591 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1592 * properties: [{1593 * key: "gender",1594 * value: "female",1595 * },{1596 * key: "age",1597 * value: "33",1598 * }],1599 * }]);1600 * @returns array of newly created tokens1601 */1602 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1603 const rawTokens = [];1604 for (const token of tokens) {1605 const raw = {NFT: {properties: token.properties}};1606 rawTokens.push(raw);1607 }1608 const creationResult = await this.helper.executeExtrinsic(1609 signer,1610 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1611 true,1612 );1613 const collection = this.getCollectionObject(collectionId);1614 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1615 }16161617 /**1618 * Set, change, or remove approved address to transfer the ownership of the NFT.1619 *1620 * @param signer keyring of signer1621 * @param collectionId ID of collection1622 * @param tokenId ID of token1623 * @param toAddressObj address to approve1624 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1625 * @returns ```true``` if extrinsic success, otherwise ```false```1626 */1627 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId) {1628 return super.approveToken(signer, collectionId, tokenId, toAddressObj, 1n);1629 }1630}163116321633class RFTGroup extends NFTnRFT {1634 /**1635 * Get collection object1636 * @param collectionId ID of collection1637 * @example getCollectionObject(2);1638 * @returns instance of UniqueRFTCollection1639 */1640 getCollectionObject(collectionId: number): UniqueRFTCollection {1641 return new UniqueRFTCollection(collectionId, this.helper);1642 }16431644 /**1645 * Get token object1646 * @param collectionId ID of collection1647 * @param tokenId ID of token1648 * @example getTokenObject(10, 5);1649 * @returns instance of UniqueNFTToken1650 */1651 getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1652 return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1653 }16541655 /**1656 * Get top 10 token owners with the largest number of pieces1657 * @param collectionId ID of collection1658 * @param tokenId ID of token1659 * @example getTokenTop10Owners(10, 5);1660 * @returns array of top 10 owners1661 */1662 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1663 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1664 }16651666 /**1667 * Get number of pieces owned by address1668 * @param collectionId ID of collection1669 * @param tokenId ID of token1670 * @param addressObj address token owner1671 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1672 * @returns number of pieces ownerd by address1673 */1674 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1675 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1676 }16771678 /**1679 * Transfer pieces of token to another address1680 * @param signer keyring of signer1681 * @param collectionId ID of collection1682 * @param tokenId ID of token1683 * @param addressObj address of a new owner1684 * @param amount number of pieces to be transfered1685 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1686 * @returns ```true``` if extrinsic success, otherwise ```false```1687 */1688 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1689 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1690 }16911692 /**1693 * Change ownership of some pieces of RFT on behalf of the owner.1694 * @param signer keyring of signer1695 * @param collectionId ID of collection1696 * @param tokenId ID of token1697 * @param fromAddressObj address on behalf of which the token will be sent1698 * @param toAddressObj new token owner1699 * @param amount number of pieces to be transfered1700 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1701 * @returns ```true``` if extrinsic success, otherwise ```false```1702 */1703 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1704 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1705 }17061707 /**1708 * Mint new collection1709 * @param signer keyring of signer1710 * @param collectionOptions Collection options1711 * @example1712 * mintCollection(aliceKeyring, {1713 * name: 'New',1714 * description: 'New collection',1715 * tokenPrefix: 'NEW',1716 * })1717 * @returns object of the created collection1718 */1719 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1720 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1721 }17221723 /**1724 * Mint new token1725 * @param signer keyring of signer1726 * @param data token data1727 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1728 * @returns created token object1729 */1730 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1731 const creationResult = await this.helper.executeExtrinsic(1732 signer,1733 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1734 refungible: {1735 pieces: data.pieces,1736 properties: data.properties,1737 },1738 }],1739 true,1740 );1741 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1742 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1743 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1744 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1745 }17461747 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1748 throw Error('Not implemented');1749 const creationResult = await this.helper.executeExtrinsic(1750 signer,1751 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1752 true, // `Unable to mint RFT tokens for ${label}`,1753 );1754 const collection = this.getCollectionObject(collectionId);1755 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1756 }17571758 /**1759 * Mint multiple RFT tokens with one owner1760 * @param signer keyring of signer1761 * @param collectionId ID of collection1762 * @param owner tokens owner1763 * @param tokens array of tokens with properties and pieces1764 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1765 * @returns array of newly created RFT tokens1766 */1767 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1768 const rawTokens = [];1769 for (const token of tokens) {1770 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1771 rawTokens.push(raw);1772 }1773 const creationResult = await this.helper.executeExtrinsic(1774 signer,1775 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1776 true,1777 );1778 const collection = this.getCollectionObject(collectionId);1779 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1780 }17811782 /**1783 * Destroys a concrete instance of RFT.1784 * @param signer keyring of signer1785 * @param collectionId ID of collection1786 * @param tokenId ID of token1787 * @param amount number of pieces to be burnt1788 * @example burnToken(aliceKeyring, 10, 5);1789 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1790 */1791 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1792 return await super.burnToken(signer, collectionId, tokenId, amount);1793 }17941795 /**1796 * Destroys a concrete instance of RFT on behalf of the owner.1797 * @param signer keyring of signer1798 * @param collectionId ID of collection1799 * @param tokenId ID of token1800 * @param fromAddressObj address on behalf of which the token will be burnt1801 * @param amount number of pieces to be burnt1802 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1803 * @returns ```true``` if extrinsic success, otherwise ```false```1804 */1805 async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1806 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1807 }18081809 /**1810 * Set, change, or remove approved address to transfer the ownership of the RFT.1811 *1812 * @param signer keyring of signer1813 * @param collectionId ID of collection1814 * @param tokenId ID of token1815 * @param toAddressObj address to approve1816 * @param amount number of pieces to be approved1817 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1818 * @returns true if the token success, otherwise false1819 */1820 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1821 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1822 }18231824 /**1825 * Get total number of pieces1826 * @param collectionId ID of collection1827 * @param tokenId ID of token1828 * @example getTokenTotalPieces(10, 5);1829 * @returns number of pieces1830 */1831 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1832 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1833 }18341835 /**1836 * Change number of token pieces. Signer must be the owner of all token pieces.1837 * @param signer keyring of signer1838 * @param collectionId ID of collection1839 * @param tokenId ID of token1840 * @param amount new number of pieces1841 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1842 * @returns true if the repartion was success, otherwise false1843 */1844 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {1845 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1846 const repartitionResult = await this.helper.executeExtrinsic(1847 signer,1848 'api.tx.unique.repartition', [collectionId, tokenId, amount],1849 true,1850 );1851 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');1852 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');1853 }1854}185518561857class FTGroup extends CollectionGroup {1858 /**1859 * Get collection object1860 * @param collectionId ID of collection1861 * @example getCollectionObject(2);1862 * @returns instance of UniqueFTCollection1863 */1864 getCollectionObject(collectionId: number): UniqueFTCollection {1865 return new UniqueFTCollection(collectionId, this.helper);1866 }18671868 /**1869 * Mint new fungible collection1870 * @param signer keyring of signer1871 * @param collectionOptions Collection options1872 * @param decimalPoints number of token decimals1873 * @example1874 * mintCollection(aliceKeyring, {1875 * name: 'New',1876 * description: 'New collection',1877 * tokenPrefix: 'NEW',1878 * }, 18)1879 * @returns newly created fungible collection1880 */1881 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1882 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1883 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1884 collectionOptions.mode = {fungible: decimalPoints};1885 for (const key of ['name', 'description', 'tokenPrefix']) {1886 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);1887 }1888 const creationResult = await this.helper.executeExtrinsic(1889 signer,1890 'api.tx.unique.createCollectionEx', [collectionOptions],1891 true,1892 );1893 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1894 }18951896 /**1897 * Mint tokens1898 * @param signer keyring of signer1899 * @param collectionId ID of collection1900 * @param owner address owner of new tokens1901 * @param amount amount of tokens to be meanted1902 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1903 * @returns ```true``` if extrinsic success, otherwise ```false```1904 */1905 async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {1906 const creationResult = await this.helper.executeExtrinsic(1907 signer,1908 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1909 fungible: {1910 value: amount,1911 },1912 }],1913 true, // `Unable to mint fungible tokens for ${label}`,1914 );1915 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');1916 }19171918 /**1919 * Mint multiple Fungible tokens with one owner1920 * @param signer keyring of signer1921 * @param collectionId ID of collection1922 * @param owner tokens owner1923 * @param tokens array of tokens with properties and pieces1924 * @returns ```true``` if extrinsic success, otherwise ```false```1925 */1926 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: {value: bigint}[], owner: ICrossAccountId): Promise<boolean> {1927 const rawTokens = [];1928 for (const token of tokens) {1929 const raw = {Fungible: {Value: token.value}};1930 rawTokens.push(raw);1931 }1932 const creationResult = await this.helper.executeExtrinsic(1933 signer,1934 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1935 true,1936 );1937 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');1938 }19391940 /**1941 * Get the top 10 owners with the largest balance for the Fungible collection1942 * @param collectionId ID of collection1943 * @example getTop10Owners(10);1944 * @returns array of ```ICrossAccountId```1945 */1946 async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {1947 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1948 }19491950 /**1951 * Get account balance1952 * @param collectionId ID of collection1953 * @param addressObj address of owner1954 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})1955 * @returns amount of fungible tokens owned by address1956 */1957 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1958 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1959 }19601961 /**1962 * Transfer tokens to address1963 * @param signer keyring of signer1964 * @param collectionId ID of collection1965 * @param toAddressObj address recipient1966 * @param amount amount of tokens to be sent1967 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1968 * @returns ```true``` if extrinsic success, otherwise ```false```1969 */1970 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {1971 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1972 }19731974 /**1975 * Transfer some tokens on behalf of the owner.1976 * @param signer keyring of signer1977 * @param collectionId ID of collection1978 * @param fromAddressObj address on behalf of which tokens will be sent1979 * @param toAddressObj address where token to be sent1980 * @param amount number of tokens to be sent1981 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);1982 * @returns ```true``` if extrinsic success, otherwise ```false```1983 */1984 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {1985 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1986 }19871988 /**1989 * Destroy some amount of tokens1990 * @param signer keyring of signer1991 * @param collectionId ID of collection1992 * @param amount amount of tokens to be destroyed1993 * @example burnTokens(aliceKeyring, 10, 1000n);1994 * @returns ```true``` if extrinsic success, otherwise ```false```1995 */1996 async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise<boolean> {1997 return await super.burnToken(signer, collectionId, 0, amount);1998 }19992000 /**2001 * Burn some tokens on behalf of the owner.2002 * @param signer keyring of signer2003 * @param collectionId ID of collection2004 * @param fromAddressObj address on behalf of which tokens will be burnt2005 * @param amount amount of tokens to be burnt2006 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2007 * @returns ```true``` if extrinsic success, otherwise ```false```2008 */2009 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {2010 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2011 }20122013 /**2014 * Get total collection supply2015 * @param collectionId2016 * @returns2017 */2018 async getTotalPieces(collectionId: number): Promise<bigint> {2019 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2020 }20212022 /**2023 * Set, change, or remove approved address to transfer tokens.2024 *2025 * @param signer keyring of signer2026 * @param collectionId ID of collection2027 * @param toAddressObj address to be approved2028 * @param amount amount of tokens to be approved2029 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2030 * @returns ```true``` if extrinsic success, otherwise ```false```2031 */2032 async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2033 return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2034 }20352036 /**2037 * Get amount of fungible tokens approved to transfer2038 * @param collectionId ID of collection2039 * @param fromAddressObj owner of tokens2040 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2041 * @returns number of tokens approved for the transfer2042 */2043 async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2044 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2045 }2046}204720482049class ChainGroup extends HelperGroup<ChainHelperBase> {2050 /**2051 * Get system properties of a chain2052 * @example getChainProperties();2053 * @returns ss58Format, token decimals, and token symbol2054 */2055 getChainProperties(): IChainProperties {2056 const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2057 return {2058 ss58Format: properties.ss58Format.toJSON(),2059 tokenDecimals: properties.tokenDecimals.toJSON(),2060 tokenSymbol: properties.tokenSymbol.toJSON(),2061 };2062 }20632064 /**2065 * Get chain header2066 * @example getLatestBlockNumber();2067 * @returns the number of the last block2068 */2069 async getLatestBlockNumber(): Promise<number> {2070 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2071 }20722073 /**2074 * Get block hash by block number2075 * @param blockNumber number of block2076 * @example getBlockHashByNumber(12345);2077 * @returns hash of a block2078 */2079 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2080 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2081 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2082 return blockHash;2083 }20842085 // TODO add docs2086 async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2087 const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2088 if (!blockHash) return null;2089 return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2090 }20912092 /**2093 * Get account nonce2094 * @param address substrate address2095 * @example getNonce("5GrwvaEF5zXb26Fz...");2096 * @returns number, account's nonce2097 */2098 async getNonce(address: TSubstrateAccount): Promise<number> {2099 return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2100 }2101}21022103class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2104 /**2105 * Get substrate address balance2106 * @param address substrate address2107 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2108 * @returns amount of tokens on address2109 */2110 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2111 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2112 }21132114 /**2115 * Transfer tokens to substrate address2116 * @param signer keyring of signer2117 * @param address substrate address of a recipient2118 * @param amount amount of tokens to be transfered2119 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2120 * @returns ```true``` if extrinsic success, otherwise ```false```2121 */2122 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2123 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}`*/);21242125 let transfer = {from: null, to: null, amount: 0n} as any;2126 result.result.events.forEach(({event: {data, method, section}}) => {2127 if ((section === 'balances') && (method === 'Transfer')) {2128 transfer = {2129 from: this.helper.address.normalizeSubstrate(data[0]),2130 to: this.helper.address.normalizeSubstrate(data[1]),2131 amount: BigInt(data[2]),2132 };2133 }2134 });2135 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from 2136 && this.helper.address.normalizeSubstrate(address) === transfer.to 2137 && BigInt(amount) === transfer.amount;2138 return isSuccess;2139 }21402141 /**2142 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2143 * @param address substrate address2144 * @returns2145 */2146 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2147 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2148 return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2149 }2150}21512152class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2153 /**2154 * Get ethereum address balance2155 * @param address ethereum address2156 * @example getEthereum("0x9F0583DbB855d...")2157 * @returns amount of tokens on address2158 */2159 async getEthereum(address: TEthereumAccount): Promise<bigint> {2160 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2161 }21622163 /**2164 * Transfer tokens to address2165 * @param signer keyring of signer2166 * @param address Ethereum address of a recipient2167 * @param amount amount of tokens to be transfered2168 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2169 * @returns ```true``` if extrinsic success, otherwise ```false```2170 */2171 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2172 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);21732174 let transfer = {from: null, to: null, amount: 0n} as any;2175 result.result.events.forEach(({event: {data, method, section}}) => {2176 if ((section === 'balances') && (method === 'Transfer')) {2177 transfer = {2178 from: data[0].toString(),2179 to: data[1].toString(),2180 amount: BigInt(data[2]),2181 };2182 }2183 });2184 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from 2185 && address === transfer.to 2186 && BigInt(amount) === transfer.amount;2187 return isSuccess;2188 }2189}21902191class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2192 subBalanceGroup: SubstrateBalanceGroup<T>;2193 ethBalanceGroup: EthereumBalanceGroup<T>;21942195 constructor(helper: T) {2196 super(helper);2197 this.subBalanceGroup = new SubstrateBalanceGroup(helper);2198 this.ethBalanceGroup = new EthereumBalanceGroup(helper);2199 }22002201 getCollectionCreationPrice(): bigint {2202 return 2n * this.getOneTokenNominal();2203 }2204 /**2205 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2206 * @example getOneTokenNominal()2207 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2208 */2209 getOneTokenNominal(): bigint {2210 const chainProperties = this.helper.chain.getChainProperties();2211 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2212 }22132214 /**2215 * Get substrate address balance2216 * @param address substrate address2217 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2218 * @returns amount of tokens on address2219 */2220 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2221 return this.subBalanceGroup.getSubstrate(address);2222 }22232224 /**2225 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2226 * @param address substrate address2227 * @returns2228 */2229 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2230 return this.subBalanceGroup.getSubstrateFull(address);2231 }22322233 /**2234 * Get ethereum address balance2235 * @param address ethereum address2236 * @example getEthereum("0x9F0583DbB855d...")2237 * @returns amount of tokens on address2238 */2239 async getEthereum(address: TEthereumAccount): Promise<bigint> {2240 return this.ethBalanceGroup.getEthereum(address);2241 }22422243 /**2244 * Transfer tokens to substrate address2245 * @param signer keyring of signer2246 * @param address substrate address of a recipient2247 * @param amount amount of tokens to be transfered2248 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2249 * @returns ```true``` if extrinsic success, otherwise ```false```2250 */2251 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2252 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2253 }2254}22552256class AddressGroup extends HelperGroup<ChainHelperBase> {2257 /**2258 * Normalizes the address to the specified ss58 format, by default ```42```.2259 * @param address substrate address2260 * @param ss58Format format for address conversion, by default ```42```2261 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2262 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2263 */2264 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2265 return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2266 }22672268 /**2269 * Get address in the connected chain format2270 * @param address substrate address2271 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2272 * @returns address in chain format2273 */2274 normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2275 return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2276 }22772278 /**2279 * Get substrate mirror of an ethereum address2280 * @param ethAddress ethereum address2281 * @param toChainFormat false for normalized account2282 * @example ethToSubstrate('0x9F0583DbB855d...')2283 * @returns substrate mirror of a provided ethereum address2284 */2285 ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): TSubstrateAccount {2286 return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2287 }22882289 /**2290 * Get ethereum mirror of a substrate address2291 * @param subAddress substrate account2292 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2293 * @returns ethereum mirror of a provided substrate address2294 */2295 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2296 return CrossAccountId.translateSubToEth(subAddress);2297 }22982299 paraSiblingSovereignAccount(paraid: number) {2300 // We are getting a *sibling* parachain sovereign account,2301 // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2302 const siblingPrefix = '0x7369626c';23032304 const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2305 const suffix = '000000000000000000000000000000000000000000000000';23062307 return siblingPrefix + encodedParaId + suffix;2308 }2309}23102311class StakingGroup extends HelperGroup<UniqueHelper> {2312 /**2313 * Stake tokens for App Promotion2314 * @param signer keyring of signer2315 * @param amountToStake amount of tokens to stake2316 * @param label extra label for log2317 * @returns2318 */2319 async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2320 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2321 const _stakeResult = await this.helper.executeExtrinsic(2322 signer, 'api.tx.appPromotion.stake',2323 [amountToStake], true,2324 );2325 // TODO extract info from stakeResult2326 return true;2327 }23282329 /**2330 * Unstake tokens for App Promotion2331 * @param signer keyring of signer2332 * @param amountToUnstake amount of tokens to unstake2333 * @param label extra label for log2334 * @returns block number where balances will be unlocked2335 */2336 async unstake(signer: TSigner, label?: string): Promise<number> {2337 if(typeof label === 'undefined') label = `${signer.address}`;2338 const _unstakeResult = await this.helper.executeExtrinsic(2339 signer, 'api.tx.appPromotion.unstake',2340 [], true,2341 );2342 // TODO extract block number fron events2343 return 1;2344 }23452346 /**2347 * Get total staked amount for address2348 * @param address substrate or ethereum address2349 * @returns total staked amount2350 */2351 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2352 if (address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2353 return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2354 }23552356 /**2357 * Get total staked per block2358 * @param address substrate or ethereum address2359 * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2360 */2361 async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2362 const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2363 return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => {2364 return { 2365 block: block.toBigInt(),2366 amount: amount.toBigInt(),2367 };2368 });2369 }23702371 /**2372 * Get total pending unstake amount for address2373 * @param address substrate or ethereum address2374 * @returns total pending unstake amount2375 */2376 async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2377 return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2378 }23792380 /**2381 * Get pending unstake amount per block for address2382 * @param address substrate or ethereum address2383 * @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 block2384 */2385 async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2386 const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2387 const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => {2388 return {2389 block: block.toBigInt(),2390 amount: amount.toBigInt(),2391 };2392 });2393 return result;2394 }2395}23962397class SchedulerGroup extends HelperGroup<UniqueHelper> {2398 constructor(helper: UniqueHelper) {2399 super(helper);2400 }24012402 async cancelScheduled(signer: TSigner, scheduledId: string) {2403 return this.helper.executeExtrinsic(2404 signer,2405 'api.tx.scheduler.cancelNamed',2406 [scheduledId],2407 true,2408 );2409 }24102411 async changePriority(signer: TSigner, scheduledId: string, priority: number) {2412 return this.helper.executeExtrinsic(2413 signer,2414 'api.tx.scheduler.changeNamedPriority',2415 [scheduledId, priority],2416 true,2417 );2418 }24192420 scheduleAt<T extends UniqueHelper>(2421 scheduledId: string,2422 executionBlockNumber: number,2423 options: ISchedulerOptions = {},2424 ) {2425 return this.schedule<T>('scheduleNamed', scheduledId, executionBlockNumber, options);2426 }24272428 scheduleAfter<T extends UniqueHelper>(2429 scheduledId: string,2430 blocksBeforeExecution: number,2431 options: ISchedulerOptions = {},2432 ) {2433 return this.schedule<T>('scheduleNamedAfter', scheduledId, blocksBeforeExecution, options);2434 }24352436 schedule<T extends UniqueHelper>(2437 scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter',2438 scheduledId: string,2439 blocksNum: number,2440 options: ISchedulerOptions = {},2441 ) {2442 // eslint-disable-next-line @typescript-eslint/naming-convention2443 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2444 return this.helper.clone(ScheduledHelperType, {2445 scheduleFn,2446 scheduledId,2447 blocksNum,2448 options,2449 }) as T;2450 }2451}24522453class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {2454 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2455 await this.helper.executeExtrinsic(2456 signer,2457 'api.tx.foreignAssets.registerForeignAsset',2458 [ownerAddress, location, metadata],2459 true,2460 );2461 }24622463 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {2464 await this.helper.executeExtrinsic(2465 signer,2466 'api.tx.foreignAssets.updateForeignAsset',2467 [foreignAssetId, location, metadata],2468 true,2469 );2470 }2471}24722473class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {2474 palletName: string;24752476 constructor(helper: T, palletName: string) {2477 super(helper);24782479 this.palletName = palletName;2480 }24812482 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {2483 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);2484 }2485}24862487class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2488 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {2489 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);2490 }24912492 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {2493 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);2494 }24952496 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {2497 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);2498 }2499}25002501class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2502 async accounts(address: string, currencyId: any) {2503 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;2504 return BigInt(free);2505 }2506}25072508class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {2509 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {2510 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);2511 }25122513 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {2514 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);2515 }25162517 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {2518 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);2519 }25202521 async account(assetId: string | number, address: string) {2522 const accountAsset = (2523 await this.helper.callRpc('api.query.assets.account', [assetId, address])2524 ).toJSON()! as any;25252526 if (accountAsset !== null) {2527 return BigInt(accountAsset['balance']);2528 } else {2529 return null;2530 }2531 }2532}25332534class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {2535 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {2536 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);2537 }2538}25392540class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {2541 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {2542 const apiPrefix = 'api.tx.assetManager.';25432544 const registerTx = this.helper.constructApiCall(2545 apiPrefix + 'registerForeignAsset',2546 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],2547 );25482549 const setUnitsTx = this.helper.constructApiCall(2550 apiPrefix + 'setAssetUnitsPerSecond',2551 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],2552 );25532554 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);2555 const encodedProposal = batchCall?.method.toHex() || '';2556 return encodedProposal;2557 }25582559 async assetTypeId(location: any) {2560 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);2561 }2562}25632564class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {2565 async notePreimage(signer: TSigner, encodedProposal: string) {2566 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);2567 }25682569 externalProposeMajority(proposalHash: string) {2570 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);2571 }25722573 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {2574 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);2575 }25762577 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {2578 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);2579 }2580}25812582class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {2583 collective: string;25842585 constructor(helper: MoonbeamHelper, collective: string) {2586 super(helper);25872588 this.collective = collective;2589 }25902591 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {2592 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);2593 }25942595 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {2596 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);2597 }25982599 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {2600 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);2601 }26022603 async proposalCount() {2604 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));2605 }2606}26072608export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;2609export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;26102611export class UniqueHelper extends ChainHelperBase {2612 balance: BalanceGroup<UniqueHelper>;2613 collection: CollectionGroup;2614 nft: NFTGroup;2615 rft: RFTGroup;2616 ft: FTGroup;2617 staking: StakingGroup;2618 scheduler: SchedulerGroup;2619 foreignAssets: ForeignAssetsGroup;2620 xcm: XcmGroup<UniqueHelper>;2621 xTokens: XTokensGroup<UniqueHelper>;2622 tokens: TokensGroup<UniqueHelper>;26232624 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2625 super(logger, options.helperBase ?? UniqueHelper);26262627 this.balance = new BalanceGroup(this);2628 this.collection = new CollectionGroup(this);2629 this.nft = new NFTGroup(this);2630 this.rft = new RFTGroup(this);2631 this.ft = new FTGroup(this);2632 this.staking = new StakingGroup(this);2633 this.scheduler = new SchedulerGroup(this);2634 this.foreignAssets = new ForeignAssetsGroup(this);2635 this.xcm = new XcmGroup(this, 'polkadotXcm');2636 this.xTokens = new XTokensGroup(this);2637 this.tokens = new TokensGroup(this);2638 }26392640 getSudo<T extends UniqueHelper>() {2641 // eslint-disable-next-line @typescript-eslint/naming-convention2642 const SudoHelperType = SudoHelper(this.helperBase);2643 return this.clone(SudoHelperType) as T;2644 }2645}26462647export class XcmChainHelper extends ChainHelperBase {2648 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {2649 const wsProvider = new WsProvider(wsEndpoint);2650 this.api = new ApiPromise({2651 provider: wsProvider,2652 });2653 await this.api.isReadyOrError;2654 this.network = await UniqueHelper.detectNetwork(this.api);2655 }2656}26572658export class RelayHelper extends XcmChainHelper {2659 xcm: XcmGroup<RelayHelper>;26602661 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2662 super(logger, options.helperBase ?? RelayHelper);26632664 this.xcm = new XcmGroup(this, 'xcmPallet');2665 }2666}26672668export class WestmintHelper extends XcmChainHelper {2669 balance: SubstrateBalanceGroup<WestmintHelper>;2670 xcm: XcmGroup<WestmintHelper>;2671 assets: AssetsGroup<WestmintHelper>;2672 xTokens: XTokensGroup<WestmintHelper>;26732674 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2675 super(logger, options.helperBase ?? WestmintHelper);26762677 this.balance = new SubstrateBalanceGroup(this);2678 this.xcm = new XcmGroup(this, 'polkadotXcm');2679 this.assets = new AssetsGroup(this);2680 this.xTokens = new XTokensGroup(this);2681 }2682}26832684export class MoonbeamHelper extends XcmChainHelper {2685 balance: EthereumBalanceGroup<MoonbeamHelper>;2686 assetManager: MoonbeamAssetManagerGroup;2687 assets: AssetsGroup<MoonbeamHelper>;2688 xTokens: XTokensGroup<MoonbeamHelper>;2689 democracy: MoonbeamDemocracyGroup;2690 collective: {2691 council: MoonbeamCollectiveGroup,2692 techCommittee: MoonbeamCollectiveGroup,2693 };26942695 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2696 super(logger, options.helperBase ?? MoonbeamHelper);26972698 this.balance = new EthereumBalanceGroup(this);2699 this.assetManager = new MoonbeamAssetManagerGroup(this);2700 this.assets = new AssetsGroup(this);2701 this.xTokens = new XTokensGroup(this);2702 this.democracy = new MoonbeamDemocracyGroup(this);2703 this.collective = {2704 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),2705 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),2706 };2707 }2708}27092710export class AcalaHelper extends XcmChainHelper {2711 balance: SubstrateBalanceGroup<AcalaHelper>;2712 assetRegistry: AcalaAssetRegistryGroup;2713 xTokens: XTokensGroup<AcalaHelper>;2714 tokens: TokensGroup<AcalaHelper>;27152716 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2717 super(logger, options.helperBase ?? AcalaHelper);27182719 this.balance = new SubstrateBalanceGroup(this);2720 this.assetRegistry = new AcalaAssetRegistryGroup(this);2721 this.xTokens = new XTokensGroup(this);2722 this.tokens = new TokensGroup(this);2723 }27242725 getSudo<T extends AcalaHelper>() {2726 // eslint-disable-next-line @typescript-eslint/naming-convention2727 const SudoHelperType = SudoHelper(this.helperBase);2728 return this.clone(SudoHelperType) as T;2729 }2730}27312732// eslint-disable-next-line @typescript-eslint/naming-convention2733function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2734 return class extends Base {2735 scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter';2736 scheduledId: string;2737 blocksNum: number;2738 options: ISchedulerOptions;27392740 constructor(...args: any[]) {2741 const logger = args[0] as ILogger;2742 const options = args[1] as {2743 scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter',2744 scheduledId: string,2745 blocksNum: number,2746 options: ISchedulerOptions2747 };27482749 super(logger);27502751 this.scheduleFn = options.scheduleFn;2752 this.scheduledId = options.scheduledId;2753 this.blocksNum = options.blocksNum;2754 this.options = options.options;2755 }27562757 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {2758 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);2759 const extrinsic = 'api.tx.scheduler.' + this.scheduleFn;27602761 return super.executeExtrinsic(2762 sender,2763 extrinsic,2764 [2765 this.scheduledId,2766 this.blocksNum,2767 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,2768 this.options.priority ?? null,2769 {Value: scheduledTx},2770 ],2771 expectSuccess,2772 );2773 }2774 };2775}27762777// eslint-disable-next-line @typescript-eslint/naming-convention2778function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {2779 return class extends Base {2780 constructor(...args: any[]) {2781 super(...args);2782 }27832784 executeExtrinsic (2785 sender: IKeyringPair,2786 extrinsic: string,2787 params: any[],2788 expectSuccess?: boolean,2789 ): Promise<ITransactionResult> {2790 const call = this.constructApiCall(extrinsic, params);27912792 return super.executeExtrinsic(2793 sender,2794 'api.tx.sudo.sudo',2795 [call],2796 expectSuccess,2797 );2798 }2799 };2800}28012802export class UniqueBaseCollection {2803 helper: UniqueHelper;2804 collectionId: number;28052806 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2807 this.collectionId = collectionId;2808 this.helper = uniqueHelper;2809 }28102811 async getData() {2812 return await this.helper.collection.getData(this.collectionId);2813 }28142815 async getLastTokenId() {2816 return await this.helper.collection.getLastTokenId(this.collectionId);2817 }28182819 async doesTokenExist(tokenId: number) {2820 return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);2821 }28222823 async getAdmins() {2824 return await this.helper.collection.getAdmins(this.collectionId);2825 }28262827 async getAllowList() {2828 return await this.helper.collection.getAllowList(this.collectionId);2829 }28302831 async getEffectiveLimits() {2832 return await this.helper.collection.getEffectiveLimits(this.collectionId);2833 }28342835 async getProperties(propertyKeys?: string[] | null) {2836 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);2837 }28382839 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2840 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2841 }28422843 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {2844 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);2845 }28462847 async confirmSponsorship(signer: TSigner) {2848 return await this.helper.collection.confirmSponsorship(signer, this.collectionId);2849 }28502851 async removeSponsor(signer: TSigner) {2852 return await this.helper.collection.removeSponsor(signer, this.collectionId);2853 }28542855 async setLimits(signer: TSigner, limits: ICollectionLimits) {2856 return await this.helper.collection.setLimits(signer, this.collectionId, limits);2857 }28582859 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {2860 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);2861 }28622863 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {2864 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);2865 }28662867 async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {2868 return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);2869 }28702871 async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {2872 return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);2873 }28742875 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {2876 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);2877 }28782879 async setProperties(signer: TSigner, properties: IProperty[]) {2880 return await this.helper.collection.setProperties(signer, this.collectionId, properties);2881 }28822883 async deleteProperties(signer: TSigner, propertyKeys: string[]) {2884 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);2885 }28862887 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {2888 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);2889 }28902891 async enableNesting(signer: TSigner, permissions: INestingPermissions) {2892 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);2893 }28942895 async disableNesting(signer: TSigner) {2896 return await this.helper.collection.disableNesting(signer, this.collectionId);2897 }28982899 async burn(signer: TSigner) {2900 return await this.helper.collection.burn(signer, this.collectionId);2901 }29022903 scheduleAt<T extends UniqueHelper>(2904 scheduledId: string,2905 executionBlockNumber: number,2906 options: ISchedulerOptions = {},2907 ) {2908 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(scheduledId, executionBlockNumber, options);2909 return new UniqueBaseCollection(this.collectionId, scheduledHelper);2910 }29112912 scheduleAfter<T extends UniqueHelper>(2913 scheduledId: string,2914 blocksBeforeExecution: number,2915 options: ISchedulerOptions = {},2916 ) {2917 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);2918 return new UniqueBaseCollection(this.collectionId, scheduledHelper);2919 }29202921 getSudo<T extends UniqueHelper>() {2922 return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());2923 }2924}292529262927export class UniqueNFTCollection extends UniqueBaseCollection {2928 getTokenObject(tokenId: number) {2929 return new UniqueNFToken(tokenId, this);2930 }29312932 async getTokensByAddress(addressObj: ICrossAccountId) {2933 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);2934 }29352936 async getToken(tokenId: number, blockHashAt?: string) {2937 return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);2938 }29392940 async getTokenOwner(tokenId: number, blockHashAt?: string) {2941 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);2942 }29432944 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {2945 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);2946 }29472948 async getTokenChildren(tokenId: number, blockHashAt?: string) {2949 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2950 }29512952 async getPropertyPermissions(propertyKeys: string[] | null = null) {2953 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);2954 }29552956 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {2957 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);2958 }29592960 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2961 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2962 }29632964 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2965 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);2966 }29672968 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {2969 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);2970 }29712972 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {2973 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);2974 }29752976 async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {2977 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});2978 }29792980 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) {2981 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);2982 }29832984 async burnToken(signer: TSigner, tokenId: number) {2985 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);2986 }29872988 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {2989 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);2990 }29912992 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {2993 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);2994 }29952996 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {2997 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);2998 }29993000 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3001 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3002 }30033004 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3005 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3006 }30073008 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3009 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3010 }30113012 scheduleAt<T extends UniqueHelper>(3013 scheduledId: string,3014 executionBlockNumber: number,3015 options: ISchedulerOptions = {},3016 ) {3017 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(scheduledId, executionBlockNumber, options);3018 return new UniqueNFTCollection(this.collectionId, scheduledHelper);3019 }30203021 scheduleAfter<T extends UniqueHelper>(3022 scheduledId: string,3023 blocksBeforeExecution: number,3024 options: ISchedulerOptions = {},3025 ) {3026 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3027 return new UniqueNFTCollection(this.collectionId, scheduledHelper);3028 }30293030 getSudo<T extends UniqueHelper>() {3031 return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());3032 }3033}303430353036export class UniqueRFTCollection extends UniqueBaseCollection {3037 getTokenObject(tokenId: number) {3038 return new UniqueRFToken(tokenId, this);3039 }30403041 async getToken(tokenId: number, blockHashAt?: string) {3042 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3043 }30443045 async getTokensByAddress(addressObj: ICrossAccountId) {3046 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3047 }30483049 async getTop10TokenOwners(tokenId: number) {3050 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3051 }30523053 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3054 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3055 }30563057 async getTokenTotalPieces(tokenId: number) {3058 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3059 }30603061 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3062 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3063 }30643065 async getPropertyPermissions(propertyKeys: string[] | null = null) {3066 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3067 }30683069 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3070 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3071 }30723073 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {3074 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3075 }30763077 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3078 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3079 }30803081 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {3082 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3083 }30843085 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3086 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3087 }30883089 async mintToken(signer: TSigner, pieces=1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3090 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3091 }30923093 async mintMultipleTokens(signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) {3094 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3095 }30963097 async burnToken(signer: TSigner, tokenId: number, amount=1n) {3098 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3099 }31003101 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n) {3102 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3103 }31043105 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3106 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3107 }31083109 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3110 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3111 }31123113 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3114 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3115 }31163117 scheduleAt<T extends UniqueHelper>(3118 scheduledId: string,3119 executionBlockNumber: number,3120 options: ISchedulerOptions = {},3121 ) {3122 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(scheduledId, executionBlockNumber, options);3123 return new UniqueRFTCollection(this.collectionId, scheduledHelper);3124 }31253126 scheduleAfter<T extends UniqueHelper>(3127 scheduledId: string,3128 blocksBeforeExecution: number,3129 options: ISchedulerOptions = {},3130 ) {3131 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3132 return new UniqueRFTCollection(this.collectionId, scheduledHelper);3133 }31343135 getSudo<T extends UniqueHelper>() {3136 return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());3137 }3138}313931403141export class UniqueFTCollection extends UniqueBaseCollection {3142 async getBalance(addressObj: ICrossAccountId) {3143 return await this.helper.ft.getBalance(this.collectionId, addressObj);3144 }31453146 async getTotalPieces() {3147 return await this.helper.ft.getTotalPieces(this.collectionId);3148 }31493150 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3151 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3152 }31533154 async getTop10Owners() {3155 return await this.helper.ft.getTop10Owners(this.collectionId);3156 }31573158 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {3159 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3160 }31613162 async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {3163 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3164 }31653166 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3167 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3168 }31693170 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3171 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3172 }31733174 async burnTokens(signer: TSigner, amount=1n) {3175 return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3176 }31773178 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3179 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3180 }31813182 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3183 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3184 }31853186 scheduleAt<T extends UniqueHelper>(3187 scheduledId: string,3188 executionBlockNumber: number,3189 options: ISchedulerOptions = {},3190 ) {3191 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(scheduledId, executionBlockNumber, options);3192 return new UniqueFTCollection(this.collectionId, scheduledHelper);3193 }31943195 scheduleAfter<T extends UniqueHelper>(3196 scheduledId: string,3197 blocksBeforeExecution: number,3198 options: ISchedulerOptions = {},3199 ) {3200 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3201 return new UniqueFTCollection(this.collectionId, scheduledHelper);3202 }32033204 getSudo<T extends UniqueHelper>() {3205 return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());3206 }3207}320832093210export class UniqueBaseToken {3211 collection: UniqueNFTCollection | UniqueRFTCollection;3212 collectionId: number;3213 tokenId: number;32143215 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3216 this.collection = collection;3217 this.collectionId = collection.collectionId;3218 this.tokenId = tokenId;3219 }32203221 async getNextSponsored(addressObj: ICrossAccountId) {3222 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3223 }32243225 async getProperties(propertyKeys?: string[] | null) {3226 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3227 }32283229 async setProperties(signer: TSigner, properties: IProperty[]) {3230 return await this.collection.setTokenProperties(signer, this.tokenId, properties);3231 }32323233 async deleteProperties(signer: TSigner, propertyKeys: string[]) {3234 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3235 }32363237 async doesExist() {3238 return await this.collection.doesTokenExist(this.tokenId);3239 }32403241 nestingAccount() {3242 return this.collection.helper.util.getTokenAccount(this);3243 }32443245 scheduleAt<T extends UniqueHelper>(3246 scheduledId: string,3247 executionBlockNumber: number,3248 options: ISchedulerOptions = {},3249 ) {3250 const scheduledCollection = this.collection.scheduleAt<T>(scheduledId, executionBlockNumber, options);3251 return new UniqueBaseToken(this.tokenId, scheduledCollection);3252 }32533254 scheduleAfter<T extends UniqueHelper>(3255 scheduledId: string,3256 blocksBeforeExecution: number,3257 options: ISchedulerOptions = {},3258 ) {3259 const scheduledCollection = this.collection.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3260 return new UniqueBaseToken(this.tokenId, scheduledCollection);3261 }32623263 getSudo<T extends UniqueHelper>() {3264 return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());3265 }3266}326732683269export class UniqueNFToken extends UniqueBaseToken {3270 collection: UniqueNFTCollection;32713272 constructor(tokenId: number, collection: UniqueNFTCollection) {3273 super(tokenId, collection);3274 this.collection = collection;3275 }32763277 async getData(blockHashAt?: string) {3278 return await this.collection.getToken(this.tokenId, blockHashAt);3279 }32803281 async getOwner(blockHashAt?: string) {3282 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3283 }32843285 async getTopmostOwner(blockHashAt?: string) {3286 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3287 }32883289 async getChildren(blockHashAt?: string) {3290 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3291 }32923293 async nest(signer: TSigner, toTokenObj: IToken) {3294 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3295 }32963297 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3298 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3299 }33003301 async transfer(signer: TSigner, addressObj: ICrossAccountId) {3302 return await this.collection.transferToken(signer, this.tokenId, addressObj);3303 }33043305 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3306 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3307 }33083309 async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3310 return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3311 }33123313 async isApproved(toAddressObj: ICrossAccountId) {3314 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3315 }33163317 async burn(signer: TSigner) {3318 return await this.collection.burnToken(signer, this.tokenId);3319 }33203321 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3322 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3323 }33243325 scheduleAt<T extends UniqueHelper>(3326 scheduledId: string,3327 executionBlockNumber: number,3328 options: ISchedulerOptions = {},3329 ) {3330 const scheduledCollection = this.collection.scheduleAt<T>(scheduledId, executionBlockNumber, options);3331 return new UniqueNFToken(this.tokenId, scheduledCollection);3332 }33333334 scheduleAfter<T extends UniqueHelper>(3335 scheduledId: string,3336 blocksBeforeExecution: number,3337 options: ISchedulerOptions = {},3338 ) {3339 const scheduledCollection = this.collection.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3340 return new UniqueNFToken(this.tokenId, scheduledCollection);3341 }33423343 getSudo<T extends UniqueHelper>() {3344 return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());3345 }3346}33473348export class UniqueRFToken extends UniqueBaseToken {3349 collection: UniqueRFTCollection;33503351 constructor(tokenId: number, collection: UniqueRFTCollection) {3352 super(tokenId, collection);3353 this.collection = collection;3354 }33553356 async getData(blockHashAt?: string) {3357 return await this.collection.getToken(this.tokenId, blockHashAt);3358 }33593360 async getTop10Owners() {3361 return await this.collection.getTop10TokenOwners(this.tokenId);3362 }33633364 async getBalance(addressObj: ICrossAccountId) {3365 return await this.collection.getTokenBalance(this.tokenId, addressObj);3366 }33673368 async getTotalPieces() {3369 return await this.collection.getTokenTotalPieces(this.tokenId);3370 }33713372 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3373 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3374 }33753376 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {3377 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3378 }33793380 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3381 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3382 }33833384 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3385 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3386 }33873388 async repartition(signer: TSigner, amount: bigint) {3389 return await this.collection.repartitionToken(signer, this.tokenId, amount);3390 }33913392 async burn(signer: TSigner, amount=1n) {3393 return await this.collection.burnToken(signer, this.tokenId, amount);3394 }33953396 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3397 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3398 }33993400 scheduleAt<T extends UniqueHelper>(3401 scheduledId: string,3402 executionBlockNumber: number,3403 options: ISchedulerOptions = {},3404 ) {3405 const scheduledCollection = this.collection.scheduleAt<T>(scheduledId, executionBlockNumber, options);3406 return new UniqueRFToken(this.tokenId, scheduledCollection);3407 }34083409 scheduleAfter<T extends UniqueHelper>(3410 scheduledId: string,3411 blocksBeforeExecution: number,3412 options: ISchedulerOptions = {},3413 ) {3414 const scheduledCollection = this.collection.scheduleAfter<T>(scheduledId, blocksBeforeExecution, options);3415 return new UniqueRFToken(this.tokenId, scheduledCollection);3416 }34173418 getSudo<T extends UniqueHelper>() {3419 return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());3420 }3421}