difftreelog
Fix playgrounds extracData method
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, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';11import {IKeyringPair} from '@polkadot/types/types';12import {hexToU8a} from '@polkadot/util/hex';13import {u8aConcat} from '@polkadot/util/u8a';14import {15 IApiListeners,16 IBlock,17 IEvent,18 IChainProperties,19 ICollectionCreationOptions,20 ICollectionLimits,21 ICollectionPermissions,22 ICrossAccountId,23 ICrossAccountIdLower,24 ILogger,25 INestingPermissions,26 IProperty,27 IStakingInfo,28 ISchedulerOptions,29 ISubstrateBalance,30 IToken,31 ITokenPropertyPermission,32 ITransactionResult,33 IUniqueHelperLog,34 TApiAllowedListeners,35 TEthereumAccount,36 TSigner,37 TSubstrateAccount,38 TNetworks,39 IForeignAssetMetadata,40 AcalaAssetMetadata,41 MoonbeamAssetInfo,42 DemocracyStandardAccountVote,43 IEthCrossAccountId,44} from './types';45import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';4647export class CrossAccountId implements ICrossAccountId {48 Substrate?: TSubstrateAccount;49 Ethereum?: TEthereumAccount;5051 constructor(account: ICrossAccountId) {52 if (account.Substrate) this.Substrate = account.Substrate;53 if (account.Ethereum) this.Ethereum = account.Ethereum;54 }5556 static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {57 switch (domain) {58 case 'Substrate': return new CrossAccountId({Substrate: account.address});59 case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();60 }61 }6263 static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {64 return new CrossAccountId({Substrate: address.substrate, Ethereum: address.ethereum});65 }6667 static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {68 return encodeAddress(decodeAddress(address), ss58Format);69 }7071 static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {72 return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});73 }7475 withNormalizedSubstrate(ss58Format = 42): CrossAccountId {76 if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);77 return this;78 }7980 static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {81 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));82 }8384 toEthereum(): CrossAccountId {85 if (this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});86 return this;87 }8889 static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {90 return evmToAddress(address, ss58Format);91 }9293 toSubstrate(ss58Format?: number): CrossAccountId {94 if (this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});95 return this;96 }9798 toLowerCase(): CrossAccountId {99 if (this.Substrate) this.Substrate = this.Substrate.toLowerCase();100 if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();101 return this;102 }103}104105const nesting = {106 toChecksumAddress(address: string): string {107 if (typeof address === 'undefined') return '';108109 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);110111 address = address.toLowerCase().replace(/^0x/i,'');112 const addressHash = keccakAsHex(address).replace(/^0x/i,'');113 const checksumAddress = ['0x'];114115 for (let i = 0; i < address.length; i++) {116 // If ith character is 8 to f then make it uppercase117 if (parseInt(addressHash[i], 16) > 7) {118 checksumAddress.push(address[i].toUpperCase());119 } else {120 checksumAddress.push(address[i]);121 }122 }123 return checksumAddress.join('');124 },125 tokenIdToAddress(collectionId: number, tokenId: number) {126 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);127 },128};129130class UniqueUtil {131 static transactionStatus = {132 NOT_READY: 'NotReady',133 FAIL: 'Fail',134 SUCCESS: 'Success',135 };136137 static chainLogType = {138 EXTRINSIC: 'extrinsic',139 RPC: 'rpc',140 };141142 static getTokenAccount(token: IToken): CrossAccountId {143 return new CrossAccountId({Ethereum: this.getTokenAddress(token)});144 }145146 static getTokenAddress(token: IToken): string {147 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);148 }149150 static getDefaultLogger(): ILogger {151 return {152 log(msg: any, level = 'INFO') {153 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));154 },155 level: {156 ERROR: 'ERROR',157 WARNING: 'WARNING',158 INFO: 'INFO',159 },160 };161 }162163 static vec2str(arr: string[] | number[]) {164 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');165 }166167 static str2vec(string: string) {168 if (typeof string !== 'string') return string;169 return Array.from(string).map(x => x.charCodeAt(0));170 }171172 static fromSeed(seed: string, ss58Format = 42) {173 const keyring = new Keyring({type: 'sr25519', ss58Format});174 return keyring.addFromUri(seed);175 }176177 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {178 if (creationResult.status !== this.transactionStatus.SUCCESS) {179 throw Error('Unable to create collection!');180 }181182 let collectionId = null;183 creationResult.result.events.forEach(({event: {data, method, section}}) => {184 if ((section === 'common') && (method === 'CollectionCreated')) {185 collectionId = parseInt(data[0].toString(), 10);186 }187 });188189 if (collectionId === null) {190 throw Error('No CollectionCreated event was found!');191 }192193 return collectionId;194 }195196 static extractTokensFromCreationResult(creationResult: ITransactionResult): {197 success: boolean,198 tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],199 } {200 if (creationResult.status !== this.transactionStatus.SUCCESS) {201 throw Error('Unable to create tokens!');202 }203 let success = false;204 const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];205 creationResult.result.events.forEach(({event: {data, method, section}}) => {206 if (method === 'ExtrinsicSuccess') {207 success = true;208 } else if ((section === 'common') && (method === 'ItemCreated')) {209 tokens.push({210 collectionId: parseInt(data[0].toString(), 10),211 tokenId: parseInt(data[1].toString(), 10),212 owner: data[2].toHuman(),213 amount: data[3].toBigInt(),214 });215 }216 });217 return {success, tokens};218 }219220 static extractTokensFromBurnResult(burnResult: ITransactionResult): {221 success: boolean,222 tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],223 } {224 if (burnResult.status !== this.transactionStatus.SUCCESS) {225 throw Error('Unable to burn tokens!');226 }227 let success = false;228 const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];229 burnResult.result.events.forEach(({event: {data, method, section}}) => {230 if (method === 'ExtrinsicSuccess') {231 success = true;232 } else if ((section === 'common') && (method === 'ItemDestroyed')) {233 tokens.push({234 collectionId: parseInt(data[0].toString(), 10),235 tokenId: parseInt(data[1].toString(), 10),236 owner: data[2].toHuman(),237 amount: data[3].toBigInt(),238 });239 }240 });241 return {success, tokens};242 }243244 static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {245 let eventId = null;246 events.forEach(({event: {data, method, section}}) => {247 if ((section === expectedSection) && (method === expectedMethod)) {248 eventId = parseInt(data[0].toString(), 10);249 }250 });251252 if (eventId === null) {253 throw Error(`No ${expectedMethod} event was found!`);254 }255 return eventId === collectionId;256 }257258 static isTokenTransferSuccess(events: {event: IEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {259 const normalizeAddress = (address: string | ICrossAccountId) => {260 if(typeof address === 'string') return address;261 const obj = {} as any;262 Object.keys(address).forEach(k => {263 obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];264 });265 if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);266 if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();267 return address;268 };269 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;270 events.forEach(({event: {data, method, section}}) => {271 if ((section === 'common') && (method === 'Transfer')) {272 const hData = (data as any).toJSON();273 transfer = {274 collectionId: hData[0],275 tokenId: hData[1],276 from: normalizeAddress(hData[2]),277 to: normalizeAddress(hData[3]),278 amount: BigInt(hData[4]),279 };280 }281 });282 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;283 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);284 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);285 isSuccess = isSuccess && amount === transfer.amount;286 return isSuccess;287 }288289 static bigIntToDecimals(number: bigint, decimals = 18) {290 const numberStr = number.toString();291 const dotPos = numberStr.length - decimals;292293 if (dotPos <= 0) {294 return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;295 } else {296 const intPart = numberStr.substring(0, dotPos);297 const fractPart = numberStr.substring(dotPos);298 return intPart + '.' + fractPart;299 }300 }301}302303class UniqueEventHelper {304 private static extractIndex(index: any): [number, number] | string {305 if(index.toRawType() === '[u8;2]') return [index[0], index[1]];306 return index.toJSON();307 }308309 private static extractSub(data: any, subTypes: any): {[key: string]: any} {310 let obj: any = {};311 let index = 0;312313 if (data.entries) {314 for(const [key, value] of data.entries()) {315 obj[key] = this.extractData(value, subTypes[index]);316 index++;317 }318 } else obj = data.toJSON();319320 return obj;321 }322323 private static extractData(data: any, type: any): any {324 if(!type) return data.toHuman();325 if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();326 if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();327 if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);328 return data.toHuman();329 }330331 public static extractEvents(events: {event: any, phase: any}[]): IEvent[] {332 const parsedEvents: IEvent[] = [];333334 events.forEach((record) => {335 const {event, phase} = record;336 const types = event.typeDef;337338 const eventData: IEvent = {339 section: event.section.toString(),340 method: event.method.toString(),341 index: this.extractIndex(event.index),342 data: [],343 phase: phase.toJSON(),344 };345346 event.data.forEach((val: any, index: number) => {347 eventData.data.push(this.extractData(val, types[index]));348 });349350 parsedEvents.push(eventData);351 });352353 return parsedEvents;354 }355}356357export class ChainHelperBase {358 helperBase: any;359360 transactionStatus = UniqueUtil.transactionStatus;361 chainLogType = UniqueUtil.chainLogType;362 util: typeof UniqueUtil;363 eventHelper: typeof UniqueEventHelper;364 logger: ILogger;365 api: ApiPromise | null;366 forcedNetwork: TNetworks | null;367 network: TNetworks | null;368 chainLog: IUniqueHelperLog[];369 children: ChainHelperBase[];370 address: AddressGroup;371 chain: ChainGroup;372373 constructor(logger?: ILogger, helperBase?: any) {374 this.helperBase = helperBase;375376 this.util = UniqueUtil;377 this.eventHelper = UniqueEventHelper;378 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();379 this.logger = logger;380 this.api = null;381 this.forcedNetwork = null;382 this.network = null;383 this.chainLog = [];384 this.children = [];385 this.address = new AddressGroup(this);386 this.chain = new ChainGroup(this);387 }388389 clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {390 Object.setPrototypeOf(helperCls.prototype, this);391 const newHelper = new helperCls(this.logger, options);392393 newHelper.api = this.api;394 newHelper.network = this.network;395 newHelper.forceNetwork = this.forceNetwork;396397 this.children.push(newHelper);398399 return newHelper;400 }401402 getApi(): ApiPromise {403 if(this.api === null) throw Error('API not initialized');404 return this.api;405 }406407 clearChainLog(): void {408 this.chainLog = [];409 }410411 forceNetwork(value: TNetworks): void {412 this.forcedNetwork = value;413 }414415 async connect(wsEndpoint: string, listeners?: IApiListeners) {416 if (this.api !== null) throw Error('Already connected');417 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);418 this.api = api;419 this.network = network;420 }421422 async disconnect() {423 for (const child of this.children) {424 child.clearApi();425 }426427 if (this.api === null) return;428 await this.api.disconnect();429 this.clearApi();430 }431432 clearApi() {433 this.api = null;434 this.network = null;435 }436437 static async detectNetwork(api: ApiPromise): Promise<TNetworks> {438 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;439 const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];440441 if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;442443 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;444 return 'opal';445 }446447 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {448 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});449 await api.isReady;450451 const network = await this.detectNetwork(api);452453 await api.disconnect();454455 return network;456 }457458 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{459 api: ApiPromise;460 network: TNetworks;461 }> {462 if(typeof network === 'undefined' || network === null) network = 'opal';463 const supportedRPC = {464 opal: {465 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,466 },467 quartz: {468 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,469 },470 unique: {471 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,472 },473 rococo: {},474 westend: {},475 moonbeam: {},476 moonriver: {},477 acala: {},478 karura: {},479 westmint: {},480 };481 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);482 const rpc = supportedRPC[network];483484 // TODO: investigate how to replace rpc in runtime485 // api._rpcCore.addUserInterfaces(rpc);486487 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});488489 await api.isReadyOrError;490491 if (typeof listeners === 'undefined') listeners = {};492 for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {493 if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;494 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);495 }496497 return {api, network};498 }499500 getTransactionStatus(data: {events: {event: IEvent}[], status: any}) {501 const {events, status} = data;502 if (status.isReady) {503 return this.transactionStatus.NOT_READY;504 }505 if (status.isBroadcast) {506 return this.transactionStatus.NOT_READY;507 }508 if (status.isInBlock || status.isFinalized) {509 const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');510 if (errors.length > 0) {511 return this.transactionStatus.FAIL;512 }513 if (events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {514 return this.transactionStatus.SUCCESS;515 }516 }517518 return this.transactionStatus.FAIL;519 }520521 signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {522 const sign = (callback: any) => {523 if(options !== null) return transaction.signAndSend(sender, options, callback);524 return transaction.signAndSend(sender, callback);525 };526 // eslint-disable-next-line no-async-promise-executor527 return new Promise(async (resolve, reject) => {528 try {529 const unsub = await sign((result: any) => {530 const status = this.getTransactionStatus(result);531532 if (status === this.transactionStatus.SUCCESS) {533 this.logger.log(`${label} successful`);534 unsub();535 resolve({result, status});536 } else if (status === this.transactionStatus.FAIL) {537 let moduleError = null;538539 if (result.hasOwnProperty('dispatchError')) {540 const dispatchError = result['dispatchError'];541542 if (dispatchError) {543 if (dispatchError.isModule) {544 const modErr = dispatchError.asModule;545 const errorMeta = dispatchError.registry.findMetaError(modErr);546547 moduleError = `${errorMeta.section}.${errorMeta.name}`;548 } else {549 moduleError = dispatchError.toHuman();550 }551 } else {552 this.logger.log(result, this.logger.level.ERROR);553 }554 }555556 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);557 unsub();558 reject({status, moduleError, result});559 }560 });561 } catch (e) {562 this.logger.log(e, this.logger.level.ERROR);563 reject(e);564 }565 });566 }567568 async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {569 const api = this.getApi();570 const signingInfo = await api.derive.tx.signingInfo(signer.address);571572 // We need to sign the tx because573 // unsigned transactions does not have an inclusion fee574 tx.sign(signer, {575 blockHash: api.genesisHash,576 genesisHash: api.genesisHash,577 runtimeVersion: api.runtimeVersion,578 nonce: signingInfo.nonce,579 });580581 if (len === null) {582 return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;583 } else {584 return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;585 }586 }587588 constructApiCall(apiCall: string, params: any[]) {589 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);590 let call = this.getApi() as any;591 for(const part of apiCall.slice(4).split('.')) {592 call = call[part];593 }594 return call(...params);595 }596597 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {598 if(this.api === null) throw Error('API not initialized');599 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);600601 const startTime = (new Date()).getTime();602 let result: ITransactionResult;603 let events: IEvent[] = [];604 try {605 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;606 events = this.eventHelper.extractEvents(result.result.events);607 }608 catch(e) {609 if(!(e as object).hasOwnProperty('status')) throw e;610 result = e as ITransactionResult;611 }612613 const endTime = (new Date()).getTime();614615 const log = {616 executedAt: endTime,617 executionTime: endTime - startTime,618 type: this.chainLogType.EXTRINSIC,619 status: result.status,620 call: extrinsic,621 signer: this.getSignerAddress(sender),622 params,623 } as IUniqueHelperLog;624625 if(result.status !== this.transactionStatus.SUCCESS) {626 if (result.moduleError) log.moduleError = result.moduleError;627 else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;628 }629 if(events.length > 0) log.events = events;630631 this.chainLog.push(log);632633 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {634 if (result.moduleError) throw Error(`${result.moduleError}`);635 else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));636 }637 return result;638 }639640 async callRpc(rpc: string, params?: any[]) {641 if(typeof params === 'undefined') params = [];642 if(this.api === null) throw Error('API not initialized');643 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);644645 const startTime = (new Date()).getTime();646 let result;647 let error = null;648 const log = {649 type: this.chainLogType.RPC,650 call: rpc,651 params,652 } as IUniqueHelperLog;653654 try {655 result = await this.constructApiCall(rpc, params);656 }657 catch(e) {658 error = e;659 }660661 const endTime = (new Date()).getTime();662663 log.executedAt = endTime;664 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';665 log.executionTime = endTime - startTime;666667 this.chainLog.push(log);668669 if(error !== null) throw error;670671 return result;672 }673674 getSignerAddress(signer: IKeyringPair | string): string {675 if(typeof signer === 'string') return signer;676 return signer.address;677 }678679 fetchAllPalletNames(): string[] {680 if(this.api === null) throw Error('API not initialized');681 return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());682 }683684 fetchMissingPalletNames(requiredPallets: string[]): string[] {685 const palletNames = this.fetchAllPalletNames();686 return requiredPallets.filter(p => !palletNames.includes(p));687 }688}689690691class HelperGroup<T extends ChainHelperBase> {692 helper: T;693694 constructor(uniqueHelper: T) {695 this.helper = uniqueHelper;696 }697}698699700class CollectionGroup extends HelperGroup<UniqueHelper> {701 /**702 * Get number of blocks when sponsored transaction is available.703 *704 * @param collectionId ID of collection705 * @param tokenId ID of token706 * @param addressObj address for which the sponsorship is checked707 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});708 * @returns number of blocks or null if sponsorship hasn't been set709 */710 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {711 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();712 }713714 /**715 * Get the number of created collections.716 *717 * @returns number of created collections718 */719 async getTotalCount(): Promise<number> {720 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();721 }722723 /**724 * Get information about the collection with additional data,725 * including the number of tokens it contains, its administrators,726 * the normalized address of the collection's owner, and decoded name and description.727 *728 * @param collectionId ID of collection729 * @example await getData(2)730 * @returns collection information object731 */732 async getData(collectionId: number): Promise<{733 id: number;734 name: string;735 description: string;736 tokensCount: number;737 admins: CrossAccountId[];738 normalizedOwner: TSubstrateAccount;739 raw: any740 } | null> {741 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);742 const humanCollection = collection.toHuman(), collectionData = {743 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],744 raw: humanCollection,745 } as any, jsonCollection = collection.toJSON();746 if (humanCollection === null) return null;747 collectionData.raw.limits = jsonCollection.limits;748 collectionData.raw.permissions = jsonCollection.permissions;749 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);750 for (const key of ['name', 'description']) {751 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);752 }753754 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))755 ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)756 : 0;757 collectionData.admins = await this.getAdmins(collectionId);758759 return collectionData;760 }761762 /**763 * Get the addresses of the collection's administrators, optionally normalized.764 *765 * @param collectionId ID of collection766 * @param normalize whether to normalize the addresses to the default ss58 format767 * @example await getAdmins(1)768 * @returns array of administrators769 */770 async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {771 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();772773 return normalize774 ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())775 : admins;776 }777778 /**779 * Get the addresses added to the collection allow-list, optionally normalized.780 * @param collectionId ID of collection781 * @param normalize whether to normalize the addresses to the default ss58 format782 * @example await getAllowList(1)783 * @returns array of allow-listed addresses784 */785 async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {786 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();787 return normalize788 ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())789 : allowListed;790 }791792 /**793 * Get the effective limits of the collection instead of null for default values794 *795 * @param collectionId ID of collection796 * @example await getEffectiveLimits(2)797 * @returns object of collection limits798 */799 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {800 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();801 }802803 /**804 * Burns the collection if the signer has sufficient permissions and collection is empty.805 *806 * @param signer keyring of signer807 * @param collectionId ID of collection808 * @example await helper.collection.burn(aliceKeyring, 3);809 * @returns ```true``` if extrinsic success, otherwise ```false```810 */811 async burn(signer: TSigner, collectionId: number): Promise<boolean> {812 const result = await this.helper.executeExtrinsic(813 signer,814 'api.tx.unique.destroyCollection', [collectionId],815 true,816 );817818 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');819 }820821 /**822 * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.823 *824 * @param signer keyring of signer825 * @param collectionId ID of collection826 * @param sponsorAddress Sponsor substrate address827 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")828 * @returns ```true``` if extrinsic success, otherwise ```false```829 */830 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {831 const result = await this.helper.executeExtrinsic(832 signer,833 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],834 true,835 );836837 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet');838 }839840 /**841 * Confirms consent to sponsor the collection on behalf of the signer.842 *843 * @param signer keyring of signer844 * @param collectionId ID of collection845 * @example confirmSponsorship(aliceKeyring, 10)846 * @returns ```true``` if extrinsic success, otherwise ```false```847 */848 async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {849 const result = await this.helper.executeExtrinsic(850 signer,851 'api.tx.unique.confirmSponsorship', [collectionId],852 true,853 );854855 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed');856 }857858 /**859 * Removes the sponsor of a collection, regardless if it consented or not.860 *861 * @param signer keyring of signer862 * @param collectionId ID of collection863 * @example removeSponsor(aliceKeyring, 10)864 * @returns ```true``` if extrinsic success, otherwise ```false```865 */866 async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {867 const result = await this.helper.executeExtrinsic(868 signer,869 'api.tx.unique.removeCollectionSponsor', [collectionId],870 true,871 );872873 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorRemoved');874 }875876 /**877 * Sets the limits of the collection. At least one limit must be specified for a correct call.878 *879 * @param signer keyring of signer880 * @param collectionId ID of collection881 * @param limits collection limits object882 * @example883 * await setLimits(884 * aliceKeyring,885 * 10,886 * {887 * sponsorTransferTimeout: 0,888 * ownerCanDestroy: false889 * }890 * )891 * @returns ```true``` if extrinsic success, otherwise ```false```892 */893 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {894 const result = await this.helper.executeExtrinsic(895 signer,896 'api.tx.unique.setCollectionLimits', [collectionId, limits],897 true,898 );899900 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet');901 }902903 /**904 * Changes the owner of the collection to the new Substrate address.905 *906 * @param signer keyring of signer907 * @param collectionId ID of collection908 * @param ownerAddress substrate address of new owner909 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")910 * @returns ```true``` if extrinsic success, otherwise ```false```911 */912 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {913 const result = await this.helper.executeExtrinsic(914 signer,915 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],916 true,917 );918919 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged');920 }921922 /**923 * Adds a collection administrator.924 *925 * @param signer keyring of signer926 * @param collectionId ID of collection927 * @param adminAddressObj Administrator address (substrate or ethereum)928 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})929 * @returns ```true``` if extrinsic success, otherwise ```false```930 */931 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {932 const result = await this.helper.executeExtrinsic(933 signer,934 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],935 true,936 );937938 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded');939 }940941 /**942 * Removes a collection administrator.943 *944 * @param signer keyring of signer945 * @param collectionId ID of collection946 * @param adminAddressObj Administrator address (substrate or ethereum)947 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})948 * @returns ```true``` if extrinsic success, otherwise ```false```949 */950 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {951 const result = await this.helper.executeExtrinsic(952 signer,953 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],954 true,955 );956957 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved');958 }959960 /**961 * Check if user is in allow list.962 *963 * @param collectionId ID of collection964 * @param user Account to check965 * @example await getAdmins(1)966 * @returns is user in allow list967 */968 async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {969 return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();970 }971972 /**973 * Adds an address to allow list974 * @param signer keyring of signer975 * @param collectionId ID of collection976 * @param addressObj address to add to the allow list977 * @returns ```true``` if extrinsic success, otherwise ```false```978 */979 async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {980 const result = await this.helper.executeExtrinsic(981 signer,982 'api.tx.unique.addToAllowList', [collectionId, addressObj],983 true,984 );985986 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');987 }988989 /**990 * Removes an address from allow list991 *992 * @param signer keyring of signer993 * @param collectionId ID of collection994 * @param addressObj address to remove from the allow list995 * @returns ```true``` if extrinsic success, otherwise ```false```996 */997 async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {998 const result = await this.helper.executeExtrinsic(999 signer,1000 'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1001 true,1002 );10031004 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved');1005 }10061007 /**1008 * Sets onchain permissions for selected collection.1009 *1010 * @param signer keyring of signer1011 * @param collectionId ID of collection1012 * @param permissions collection permissions object1013 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1014 * @returns ```true``` if extrinsic success, otherwise ```false```1015 */1016 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1017 const result = await this.helper.executeExtrinsic(1018 signer,1019 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1020 true,1021 );10221023 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet');1024 }10251026 /**1027 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1028 *1029 * @param signer keyring of signer1030 * @param collectionId ID of collection1031 * @param permissions nesting permissions object1032 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1033 * @returns ```true``` if extrinsic success, otherwise ```false```1034 */1035 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1036 return await this.setPermissions(signer, collectionId, {nesting: permissions});1037 }10381039 /**1040 * Disables nesting for selected collection.1041 *1042 * @param signer keyring of signer1043 * @param collectionId ID of collection1044 * @example disableNesting(aliceKeyring, 10);1045 * @returns ```true``` if extrinsic success, otherwise ```false```1046 */1047 async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1048 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1049 }10501051 /**1052 * Sets onchain properties to the collection.1053 *1054 * @param signer keyring of signer1055 * @param collectionId ID of collection1056 * @param properties array of property objects1057 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1058 * @returns ```true``` if extrinsic success, otherwise ```false```1059 */1060 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1061 const result = await this.helper.executeExtrinsic(1062 signer,1063 'api.tx.unique.setCollectionProperties', [collectionId, properties],1064 true,1065 );10661067 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1068 }10691070 /**1071 * Get collection properties.1072 *1073 * @param collectionId ID of collection1074 * @param propertyKeys optionally filter the returned properties to only these keys1075 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1076 * @returns array of key-value pairs1077 */1078 async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1079 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1080 }10811082 async getCollectionOptions(collectionId: number) {1083 return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1084 }10851086 /**1087 * Deletes onchain properties from the collection.1088 *1089 * @param signer keyring of signer1090 * @param collectionId ID of collection1091 * @param propertyKeys array of property keys to delete1092 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1093 * @returns ```true``` if extrinsic success, otherwise ```false```1094 */1095 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1096 const result = await this.helper.executeExtrinsic(1097 signer,1098 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1099 true,1100 );11011102 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1103 }11041105 /**1106 * Changes the owner of the token.1107 *1108 * @param signer keyring of signer1109 * @param collectionId ID of collection1110 * @param tokenId ID of token1111 * @param addressObj address of a new owner1112 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1113 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1114 * @returns true if the token success, otherwise false1115 */1116 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1117 const result = await this.helper.executeExtrinsic(1118 signer,1119 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1120 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1121 );11221123 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1124 }11251126 /**1127 *1128 * Change ownership of a token(s) on behalf of the owner.1129 *1130 * @param signer keyring of signer1131 * @param collectionId ID of collection1132 * @param tokenId ID of token1133 * @param fromAddressObj address on behalf of which the token will be sent1134 * @param toAddressObj new token owner1135 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1136 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1137 * @returns true if the token success, otherwise false1138 */1139 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1140 const result = await this.helper.executeExtrinsic(1141 signer,1142 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1143 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1144 );1145 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1146 }11471148 /**1149 *1150 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1151 *1152 * @param signer keyring of signer1153 * @param collectionId ID of collection1154 * @param tokenId ID of token1155 * @param amount amount of tokens to be burned. For NFT must be set to 1n1156 * @example burnToken(aliceKeyring, 10, 5);1157 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1158 */1159 async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1160 const burnResult = await this.helper.executeExtrinsic(1161 signer,1162 'api.tx.unique.burnItem', [collectionId, tokenId, amount],1163 true, // `Unable to burn token for ${label}`,1164 );1165 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1166 if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1167 return burnedTokens.success;1168 }11691170 /**1171 * Destroys a concrete instance of NFT on behalf of the owner1172 *1173 * @param signer keyring of signer1174 * @param collectionId ID of collection1175 * @param tokenId ID of token1176 * @param fromAddressObj address on behalf of which the token will be burnt1177 * @param amount amount of tokens to be burned. For NFT must be set to 1n1178 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1179 * @returns ```true``` if extrinsic success, otherwise ```false```1180 */1181 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1182 const burnResult = await this.helper.executeExtrinsic(1183 signer,1184 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1185 true, // `Unable to burn token from for ${label}`,1186 );1187 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1188 return burnedTokens.success && burnedTokens.tokens.length > 0;1189 }11901191 /**1192 * Set, change, or remove approved address to transfer the ownership of the NFT.1193 *1194 * @param signer keyring of signer1195 * @param collectionId ID of collection1196 * @param tokenId ID of token1197 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1198 * @param amount amount of token to be approved. For NFT must be set to 1n1199 * @returns ```true``` if extrinsic success, otherwise ```false```1200 */1201 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1202 const approveResult = await this.helper.executeExtrinsic(1203 signer,1204 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1205 true, // `Unable to approve token for ${label}`,1206 );12071208 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1209 }12101211 /**1212 * Get the amount of token pieces approved to transfer or burn. Normally 0.1213 *1214 * @param collectionId ID of collection1215 * @param tokenId ID of token1216 * @param toAccountObj address which is approved to use token pieces1217 * @param fromAccountObj address which may have allowed the use of its owned tokens1218 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1219 * @returns number of approved to transfer pieces1220 */1221 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1222 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1223 }12241225 /**1226 * Get the last created token ID in a collection1227 *1228 * @param collectionId ID of collection1229 * @example getLastTokenId(10);1230 * @returns id of the last created token1231 */1232 async getLastTokenId(collectionId: number): Promise<number> {1233 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1234 }12351236 /**1237 * Check if token exists1238 *1239 * @param collectionId ID of collection1240 * @param tokenId ID of token1241 * @example doesTokenExist(10, 20);1242 * @returns true if the token exists, otherwise false1243 */1244 async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1245 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1246 }1247}12481249class NFTnRFT extends CollectionGroup {1250 /**1251 * Get tokens owned by account1252 *1253 * @param collectionId ID of collection1254 * @param addressObj tokens owner1255 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1256 * @returns array of token ids owned by account1257 */1258 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1259 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1260 }12611262 /**1263 * Get token data1264 *1265 * @param collectionId ID of collection1266 * @param tokenId ID of token1267 * @param propertyKeys optionally filter the token properties to only these keys1268 * @param blockHashAt optionally query the data at some block with this hash1269 * @example getToken(10, 5);1270 * @returns human readable token data1271 */1272 async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1273 properties: IProperty[];1274 owner: CrossAccountId;1275 normalizedOwner: CrossAccountId;1276 }| null> {1277 let tokenData;1278 if(typeof blockHashAt === 'undefined') {1279 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1280 }1281 else {1282 if(propertyKeys.length == 0) {1283 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1284 if(!collection) return null;1285 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1286 }1287 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1288 }1289 tokenData = tokenData.toHuman();1290 if (tokenData === null || tokenData.owner === null) return null;1291 const owner = {} as any;1292 for (const key of Object.keys(tokenData.owner)) {1293 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1294 ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1295 : tokenData.owner[key];1296 }1297 tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1298 return tokenData;1299 }13001301 /**1302 * Set permissions to change token properties1303 *1304 * @param signer keyring of signer1305 * @param collectionId ID of collection1306 * @param permissions permissions to change a property by the collection admin or token owner1307 * @example setTokenPropertyPermissions(1308 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1309 * )1310 * @returns true if extrinsic success otherwise false1311 */1312 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1313 const result = await this.helper.executeExtrinsic(1314 signer,1315 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1316 true,1317 );13181319 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1320 }13211322 /**1323 * Get token property permissions.1324 *1325 * @param collectionId ID of collection1326 * @param propertyKeys optionally filter the returned property permissions to only these keys1327 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1328 * @returns array of key-permission pairs1329 */1330 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1331 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1332 }13331334 /**1335 * Set token properties1336 *1337 * @param signer keyring of signer1338 * @param collectionId ID of collection1339 * @param tokenId ID of token1340 * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1341 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1342 * @returns ```true``` if extrinsic success, otherwise ```false```1343 */1344 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1345 const result = await this.helper.executeExtrinsic(1346 signer,1347 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1348 true,1349 );13501351 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1352 }13531354 /**1355 * Get properties, metadata assigned to a token.1356 *1357 * @param collectionId ID of collection1358 * @param tokenId ID of token1359 * @param propertyKeys optionally filter the returned properties to only these keys1360 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1361 * @returns array of key-value pairs1362 */1363 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1364 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1365 }13661367 /**1368 * Delete the provided properties of a token1369 * @param signer keyring of signer1370 * @param collectionId ID of collection1371 * @param tokenId ID of token1372 * @param propertyKeys property keys to be deleted1373 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1374 * @returns ```true``` if extrinsic success, otherwise ```false```1375 */1376 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1377 const result = await this.helper.executeExtrinsic(1378 signer,1379 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1380 true,1381 );13821383 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1384 }13851386 /**1387 * Mint new collection1388 *1389 * @param signer keyring of signer1390 * @param collectionOptions basic collection options and properties1391 * @param mode NFT or RFT type of a collection1392 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1393 * @returns object of the created collection1394 */1395 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1396 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1397 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1398 for (const key of ['name', 'description', 'tokenPrefix']) {1399 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);1400 }1401 const creationResult = await this.helper.executeExtrinsic(1402 signer,1403 'api.tx.unique.createCollectionEx', [collectionOptions],1404 true, // errorLabel,1405 );1406 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1407 }14081409 getCollectionObject(_collectionId: number): any {1410 return null;1411 }14121413 getTokenObject(_collectionId: number, _tokenId: number): any {1414 return null;1415 }14161417 /**1418 * Tells whether the given `owner` approves the `operator`.1419 * @param collectionId ID of collection1420 * @param owner owner address1421 * @param operator operator addrees1422 * @returns true if operator is enabled1423 */1424 async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1425 return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1426 }14271428 /** Sets or unsets the approval of a given operator.1429 * The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1430 * @param operator Operator1431 * @param approved Should operator status be granted or revoked?1432 * @returns ```true``` if extrinsic success, otherwise ```false```1433 */1434 async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1435 const result = await this.helper.executeExtrinsic(1436 signer,1437 'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1438 true,1439 );1440 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1441 }1442}144314441445class NFTGroup extends NFTnRFT {1446 /**1447 * Get collection object1448 * @param collectionId ID of collection1449 * @example getCollectionObject(2);1450 * @returns instance of UniqueNFTCollection1451 */1452 getCollectionObject(collectionId: number): UniqueNFTCollection {1453 return new UniqueNFTCollection(collectionId, this.helper);1454 }14551456 /**1457 * Get token object1458 * @param collectionId ID of collection1459 * @param tokenId ID of token1460 * @example getTokenObject(10, 5);1461 * @returns instance of UniqueNFTToken1462 */1463 getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1464 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1465 }14661467 /**1468 * Get token's owner1469 * @param collectionId ID of collection1470 * @param tokenId ID of token1471 * @param blockHashAt optionally query the data at the block with this hash1472 * @example getTokenOwner(10, 5);1473 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1474 */1475 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1476 let owner;1477 if (typeof blockHashAt === 'undefined') {1478 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1479 } else {1480 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1481 }1482 return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1483 }14841485 /**1486 * Is token approved to transfer1487 * @param collectionId ID of collection1488 * @param tokenId ID of token1489 * @param toAccountObj address to be approved1490 * @returns ```true``` if extrinsic success, otherwise ```false```1491 */1492 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1493 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1494 }14951496 /**1497 * Changes the owner of the token.1498 *1499 * @param signer keyring of signer1500 * @param collectionId ID of collection1501 * @param tokenId ID of token1502 * @param addressObj address of a new owner1503 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1504 * @returns ```true``` if extrinsic success, otherwise ```false```1505 */1506 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1507 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1508 }15091510 /**1511 *1512 * Change ownership of a NFT on behalf of the owner.1513 *1514 * @param signer keyring of signer1515 * @param collectionId ID of collection1516 * @param tokenId ID of token1517 * @param fromAddressObj address on behalf of which the token will be sent1518 * @param toAddressObj new token owner1519 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1520 * @returns ```true``` if extrinsic success, otherwise ```false```1521 */1522 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1523 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1524 }15251526 /**1527 * Recursively find the address that owns the token1528 * @param collectionId ID of collection1529 * @param tokenId ID of token1530 * @param blockHashAt1531 * @example getTokenTopmostOwner(10, 5);1532 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1533 */1534 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1535 let owner;1536 if (typeof blockHashAt === 'undefined') {1537 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1538 } else {1539 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1540 }15411542 if (owner === null) return null;15431544 return owner.toHuman();1545 }15461547 /**1548 * Get tokens nested in the provided token1549 * @param collectionId ID of collection1550 * @param tokenId ID of token1551 * @param blockHashAt optionally query the data at the block with this hash1552 * @example getTokenChildren(10, 5);1553 * @returns tokens whose depth of nesting is <= 51554 */1555 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1556 let children;1557 if(typeof blockHashAt === 'undefined') {1558 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1559 } else {1560 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1561 }15621563 return children.toJSON().map((x: any) => {1564 return {collectionId: x.collection, tokenId: x.token};1565 });1566 }15671568 /**1569 * Nest one token into another1570 * @param signer keyring of signer1571 * @param tokenObj token to be nested1572 * @param rootTokenObj token to be parent1573 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1574 * @returns ```true``` if extrinsic success, otherwise ```false```1575 */1576 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1577 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1578 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1579 if(!result) {1580 throw Error('Unable to nest token!');1581 }1582 return result;1583 }15841585 /**1586 * Remove token from nested state1587 * @param signer keyring of signer1588 * @param tokenObj token to unnest1589 * @param rootTokenObj parent of a token1590 * @param toAddressObj address of a new token owner1591 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1592 * @returns ```true``` if extrinsic success, otherwise ```false```1593 */1594 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1595 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1596 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1597 if(!result) {1598 throw Error('Unable to unnest token!');1599 }1600 return result;1601 }16021603 /**1604 * Mint new collection1605 * @param signer keyring of signer1606 * @param collectionOptions Collection options1607 * @example1608 * mintCollection(aliceKeyring, {1609 * name: 'New',1610 * description: 'New collection',1611 * tokenPrefix: 'NEW',1612 * })1613 * @returns object of the created collection1614 */1615 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1616 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1617 }16181619 /**1620 * Mint new token1621 * @param signer keyring of signer1622 * @param data token data1623 * @returns created token object1624 */1625 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1626 const creationResult = await this.helper.executeExtrinsic(1627 signer,1628 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1629 nft: {1630 properties: data.properties,1631 },1632 }],1633 true,1634 );1635 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1636 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1637 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1638 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1639 }16401641 /**1642 * Mint multiple NFT tokens1643 * @param signer keyring of signer1644 * @param collectionId ID of collection1645 * @param tokens array of tokens with owner and properties1646 * @example1647 * mintMultipleTokens(aliceKeyring, 10, [{1648 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1649 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1650 * },{1651 * owner: {Ethereum: "0x9F0583DbB855d..."},1652 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1653 * }]);1654 * @returns ```true``` if extrinsic success, otherwise ```false```1655 */1656 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1657 const creationResult = await this.helper.executeExtrinsic(1658 signer,1659 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1660 true,1661 );1662 const collection = this.getCollectionObject(collectionId);1663 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1664 }16651666 /**1667 * Mint multiple NFT tokens with one owner1668 * @param signer keyring of signer1669 * @param collectionId ID of collection1670 * @param owner tokens owner1671 * @param tokens array of tokens with owner and properties1672 * @example1673 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1674 * properties: [{1675 * key: "gender",1676 * value: "female",1677 * },{1678 * key: "age",1679 * value: "33",1680 * }],1681 * }]);1682 * @returns array of newly created tokens1683 */1684 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1685 const rawTokens = [];1686 for (const token of tokens) {1687 const raw = {NFT: {properties: token.properties}};1688 rawTokens.push(raw);1689 }1690 const creationResult = await this.helper.executeExtrinsic(1691 signer,1692 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1693 true,1694 );1695 const collection = this.getCollectionObject(collectionId);1696 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1697 }16981699 /**1700 * Set, change, or remove approved address to transfer the ownership of the NFT.1701 *1702 * @param signer keyring of signer1703 * @param collectionId ID of collection1704 * @param tokenId ID of token1705 * @param toAddressObj address to approve1706 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1707 * @returns ```true``` if extrinsic success, otherwise ```false```1708 */1709 approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId) {1710 return super.approveToken(signer, collectionId, tokenId, toAddressObj, 1n);1711 }1712}171317141715class RFTGroup extends NFTnRFT {1716 /**1717 * Get collection object1718 * @param collectionId ID of collection1719 * @example getCollectionObject(2);1720 * @returns instance of UniqueRFTCollection1721 */1722 getCollectionObject(collectionId: number): UniqueRFTCollection {1723 return new UniqueRFTCollection(collectionId, this.helper);1724 }17251726 /**1727 * Get token object1728 * @param collectionId ID of collection1729 * @param tokenId ID of token1730 * @example getTokenObject(10, 5);1731 * @returns instance of UniqueNFTToken1732 */1733 getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1734 return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1735 }17361737 /**1738 * Get top 10 token owners with the largest number of pieces1739 * @param collectionId ID of collection1740 * @param tokenId ID of token1741 * @example getTokenTop10Owners(10, 5);1742 * @returns array of top 10 owners1743 */1744 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1745 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1746 }17471748 /**1749 * Get number of pieces owned by address1750 * @param collectionId ID of collection1751 * @param tokenId ID of token1752 * @param addressObj address token owner1753 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1754 * @returns number of pieces ownerd by address1755 */1756 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1757 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1758 }17591760 /**1761 * Transfer pieces of token to another address1762 * @param signer keyring of signer1763 * @param collectionId ID of collection1764 * @param tokenId ID of token1765 * @param addressObj address of a new owner1766 * @param amount number of pieces to be transfered1767 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1768 * @returns ```true``` if extrinsic success, otherwise ```false```1769 */1770 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1771 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1772 }17731774 /**1775 * Change ownership of some pieces of RFT on behalf of the owner.1776 * @param signer keyring of signer1777 * @param collectionId ID of collection1778 * @param tokenId ID of token1779 * @param fromAddressObj address on behalf of which the token will be sent1780 * @param toAddressObj new token owner1781 * @param amount number of pieces to be transfered1782 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1783 * @returns ```true``` if extrinsic success, otherwise ```false```1784 */1785 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1786 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1787 }17881789 /**1790 * Mint new collection1791 * @param signer keyring of signer1792 * @param collectionOptions Collection options1793 * @example1794 * mintCollection(aliceKeyring, {1795 * name: 'New',1796 * description: 'New collection',1797 * tokenPrefix: 'NEW',1798 * })1799 * @returns object of the created collection1800 */1801 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1802 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1803 }18041805 /**1806 * Mint new token1807 * @param signer keyring of signer1808 * @param data token data1809 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1810 * @returns created token object1811 */1812 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1813 const creationResult = await this.helper.executeExtrinsic(1814 signer,1815 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1816 refungible: {1817 pieces: data.pieces,1818 properties: data.properties,1819 },1820 }],1821 true,1822 );1823 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1824 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1825 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1826 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1827 }18281829 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1830 throw Error('Not implemented');1831 const creationResult = await this.helper.executeExtrinsic(1832 signer,1833 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1834 true, // `Unable to mint RFT tokens for ${label}`,1835 );1836 const collection = this.getCollectionObject(collectionId);1837 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1838 }18391840 /**1841 * Mint multiple RFT tokens with one owner1842 * @param signer keyring of signer1843 * @param collectionId ID of collection1844 * @param owner tokens owner1845 * @param tokens array of tokens with properties and pieces1846 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1847 * @returns array of newly created RFT tokens1848 */1849 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1850 const rawTokens = [];1851 for (const token of tokens) {1852 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1853 rawTokens.push(raw);1854 }1855 const creationResult = await this.helper.executeExtrinsic(1856 signer,1857 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1858 true,1859 );1860 const collection = this.getCollectionObject(collectionId);1861 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1862 }18631864 /**1865 * Destroys a concrete instance of RFT.1866 * @param signer keyring of signer1867 * @param collectionId ID of collection1868 * @param tokenId ID of token1869 * @param amount number of pieces to be burnt1870 * @example burnToken(aliceKeyring, 10, 5);1871 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1872 */1873 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1874 return await super.burnToken(signer, collectionId, tokenId, amount);1875 }18761877 /**1878 * Destroys a concrete instance of RFT on behalf of the owner.1879 * @param signer keyring of signer1880 * @param collectionId ID of collection1881 * @param tokenId ID of token1882 * @param fromAddressObj address on behalf of which the token will be burnt1883 * @param amount number of pieces to be burnt1884 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1885 * @returns ```true``` if extrinsic success, otherwise ```false```1886 */1887 async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1888 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1889 }18901891 /**1892 * Set, change, or remove approved address to transfer the ownership of the RFT.1893 *1894 * @param signer keyring of signer1895 * @param collectionId ID of collection1896 * @param tokenId ID of token1897 * @param toAddressObj address to approve1898 * @param amount number of pieces to be approved1899 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1900 * @returns true if the token success, otherwise false1901 */1902 approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1903 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1904 }19051906 /**1907 * Get total number of pieces1908 * @param collectionId ID of collection1909 * @param tokenId ID of token1910 * @example getTokenTotalPieces(10, 5);1911 * @returns number of pieces1912 */1913 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1914 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1915 }19161917 /**1918 * Change number of token pieces. Signer must be the owner of all token pieces.1919 * @param signer keyring of signer1920 * @param collectionId ID of collection1921 * @param tokenId ID of token1922 * @param amount new number of pieces1923 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1924 * @returns true if the repartion was success, otherwise false1925 */1926 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {1927 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1928 const repartitionResult = await this.helper.executeExtrinsic(1929 signer,1930 'api.tx.unique.repartition', [collectionId, tokenId, amount],1931 true,1932 );1933 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');1934 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');1935 }1936}193719381939class FTGroup extends CollectionGroup {1940 /**1941 * Get collection object1942 * @param collectionId ID of collection1943 * @example getCollectionObject(2);1944 * @returns instance of UniqueFTCollection1945 */1946 getCollectionObject(collectionId: number): UniqueFTCollection {1947 return new UniqueFTCollection(collectionId, this.helper);1948 }19491950 /**1951 * Mint new fungible collection1952 * @param signer keyring of signer1953 * @param collectionOptions Collection options1954 * @param decimalPoints number of token decimals1955 * @example1956 * mintCollection(aliceKeyring, {1957 * name: 'New',1958 * description: 'New collection',1959 * tokenPrefix: 'NEW',1960 * }, 18)1961 * @returns newly created fungible collection1962 */1963 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1964 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1965 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1966 collectionOptions.mode = {fungible: decimalPoints};1967 for (const key of ['name', 'description', 'tokenPrefix']) {1968 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);1969 }1970 const creationResult = await this.helper.executeExtrinsic(1971 signer,1972 'api.tx.unique.createCollectionEx', [collectionOptions],1973 true,1974 );1975 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1976 }19771978 /**1979 * Mint tokens1980 * @param signer keyring of signer1981 * @param collectionId ID of collection1982 * @param owner address owner of new tokens1983 * @param amount amount of tokens to be meanted1984 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1985 * @returns ```true``` if extrinsic success, otherwise ```false```1986 */1987 async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {1988 const creationResult = await this.helper.executeExtrinsic(1989 signer,1990 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1991 fungible: {1992 value: amount,1993 },1994 }],1995 true, // `Unable to mint fungible tokens for ${label}`,1996 );1997 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');1998 }19992000 /**2001 * Mint multiple Fungible tokens with one owner2002 * @param signer keyring of signer2003 * @param collectionId ID of collection2004 * @param owner tokens owner2005 * @param tokens array of tokens with properties and pieces2006 * @returns ```true``` if extrinsic success, otherwise ```false```2007 */2008 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: {value: bigint}[], owner: ICrossAccountId): Promise<boolean> {2009 const rawTokens = [];2010 for (const token of tokens) {2011 const raw = {Fungible: {Value: token.value}};2012 rawTokens.push(raw);2013 }2014 const creationResult = await this.helper.executeExtrinsic(2015 signer,2016 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2017 true,2018 );2019 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2020 }20212022 /**2023 * Get the top 10 owners with the largest balance for the Fungible collection2024 * @param collectionId ID of collection2025 * @example getTop10Owners(10);2026 * @returns array of ```ICrossAccountId```2027 */2028 async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2029 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2030 }20312032 /**2033 * Get account balance2034 * @param collectionId ID of collection2035 * @param addressObj address of owner2036 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2037 * @returns amount of fungible tokens owned by address2038 */2039 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2040 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2041 }20422043 /**2044 * Transfer tokens to address2045 * @param signer keyring of signer2046 * @param collectionId ID of collection2047 * @param toAddressObj address recipient2048 * @param amount amount of tokens to be sent2049 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2050 * @returns ```true``` if extrinsic success, otherwise ```false```2051 */2052 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2053 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2054 }20552056 /**2057 * Transfer some tokens on behalf of the owner.2058 * @param signer keyring of signer2059 * @param collectionId ID of collection2060 * @param fromAddressObj address on behalf of which tokens will be sent2061 * @param toAddressObj address where token to be sent2062 * @param amount number of tokens to be sent2063 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2064 * @returns ```true``` if extrinsic success, otherwise ```false```2065 */2066 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {2067 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2068 }20692070 /**2071 * Destroy some amount of tokens2072 * @param signer keyring of signer2073 * @param collectionId ID of collection2074 * @param amount amount of tokens to be destroyed2075 * @example burnTokens(aliceKeyring, 10, 1000n);2076 * @returns ```true``` if extrinsic success, otherwise ```false```2077 */2078 async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise<boolean> {2079 return await super.burnToken(signer, collectionId, 0, amount);2080 }20812082 /**2083 * Burn some tokens on behalf of the owner.2084 * @param signer keyring of signer2085 * @param collectionId ID of collection2086 * @param fromAddressObj address on behalf of which tokens will be burnt2087 * @param amount amount of tokens to be burnt2088 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2089 * @returns ```true``` if extrinsic success, otherwise ```false```2090 */2091 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {2092 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2093 }20942095 /**2096 * Get total collection supply2097 * @param collectionId2098 * @returns2099 */2100 async getTotalPieces(collectionId: number): Promise<bigint> {2101 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2102 }21032104 /**2105 * Set, change, or remove approved address to transfer tokens.2106 *2107 * @param signer keyring of signer2108 * @param collectionId ID of collection2109 * @param toAddressObj address to be approved2110 * @param amount amount of tokens to be approved2111 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2112 * @returns ```true``` if extrinsic success, otherwise ```false```2113 */2114 approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2115 return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2116 }21172118 /**2119 * Get amount of fungible tokens approved to transfer2120 * @param collectionId ID of collection2121 * @param fromAddressObj owner of tokens2122 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2123 * @returns number of tokens approved for the transfer2124 */2125 getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2126 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2127 }2128}212921302131class ChainGroup extends HelperGroup<ChainHelperBase> {2132 /**2133 * Get system properties of a chain2134 * @example getChainProperties();2135 * @returns ss58Format, token decimals, and token symbol2136 */2137 getChainProperties(): IChainProperties {2138 const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2139 return {2140 ss58Format: properties.ss58Format.toJSON(),2141 tokenDecimals: properties.tokenDecimals.toJSON(),2142 tokenSymbol: properties.tokenSymbol.toJSON(),2143 };2144 }21452146 /**2147 * Get chain header2148 * @example getLatestBlockNumber();2149 * @returns the number of the last block2150 */2151 async getLatestBlockNumber(): Promise<number> {2152 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2153 }21542155 /**2156 * Get block hash by block number2157 * @param blockNumber number of block2158 * @example getBlockHashByNumber(12345);2159 * @returns hash of a block2160 */2161 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2162 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2163 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2164 return blockHash;2165 }21662167 // TODO add docs2168 async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2169 const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2170 if (!blockHash) return null;2171 return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2172 }21732174 /**2175 * Get account nonce2176 * @param address substrate address2177 * @example getNonce("5GrwvaEF5zXb26Fz...");2178 * @returns number, account's nonce2179 */2180 async getNonce(address: TSubstrateAccount): Promise<number> {2181 return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2182 }2183}21842185class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2186 /**2187 * Get substrate address balance2188 * @param address substrate address2189 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2190 * @returns amount of tokens on address2191 */2192 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2193 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2194 }21952196 /**2197 * Transfer tokens to substrate address2198 * @param signer keyring of signer2199 * @param address substrate address of a recipient2200 * @param amount amount of tokens to be transfered2201 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2202 * @returns ```true``` if extrinsic success, otherwise ```false```2203 */2204 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2205 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}`*/);22062207 let transfer = {from: null, to: null, amount: 0n} as any;2208 result.result.events.forEach(({event: {data, method, section}}) => {2209 if ((section === 'balances') && (method === 'Transfer')) {2210 transfer = {2211 from: this.helper.address.normalizeSubstrate(data[0]),2212 to: this.helper.address.normalizeSubstrate(data[1]),2213 amount: BigInt(data[2]),2214 };2215 }2216 });2217 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2218 && this.helper.address.normalizeSubstrate(address) === transfer.to2219 && BigInt(amount) === transfer.amount;2220 return isSuccess;2221 }22222223 /**2224 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2225 * @param address substrate address2226 * @returns2227 */2228 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2229 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2230 return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2231 }2232}22332234class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2235 /**2236 * Get ethereum address balance2237 * @param address ethereum address2238 * @example getEthereum("0x9F0583DbB855d...")2239 * @returns amount of tokens on address2240 */2241 async getEthereum(address: TEthereumAccount): Promise<bigint> {2242 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2243 }22442245 /**2246 * Transfer tokens to address2247 * @param signer keyring of signer2248 * @param address Ethereum address of a recipient2249 * @param amount amount of tokens to be transfered2250 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2251 * @returns ```true``` if extrinsic success, otherwise ```false```2252 */2253 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2254 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);22552256 let transfer = {from: null, to: null, amount: 0n} as any;2257 result.result.events.forEach(({event: {data, method, section}}) => {2258 if ((section === 'balances') && (method === 'Transfer')) {2259 transfer = {2260 from: data[0].toString(),2261 to: data[1].toString(),2262 amount: BigInt(data[2]),2263 };2264 }2265 });2266 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2267 && address === transfer.to2268 && BigInt(amount) === transfer.amount;2269 return isSuccess;2270 }2271}22722273class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2274 subBalanceGroup: SubstrateBalanceGroup<T>;2275 ethBalanceGroup: EthereumBalanceGroup<T>;22762277 constructor(helper: T) {2278 super(helper);2279 this.subBalanceGroup = new SubstrateBalanceGroup(helper);2280 this.ethBalanceGroup = new EthereumBalanceGroup(helper);2281 }22822283 getCollectionCreationPrice(): bigint {2284 return 2n * this.getOneTokenNominal();2285 }2286 /**2287 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2288 * @example getOneTokenNominal()2289 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2290 */2291 getOneTokenNominal(): bigint {2292 const chainProperties = this.helper.chain.getChainProperties();2293 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2294 }22952296 /**2297 * Get substrate address balance2298 * @param address substrate address2299 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2300 * @returns amount of tokens on address2301 */2302 getSubstrate(address: TSubstrateAccount): Promise<bigint> {2303 return this.subBalanceGroup.getSubstrate(address);2304 }23052306 /**2307 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2308 * @param address substrate address2309 * @returns2310 */2311 getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2312 return this.subBalanceGroup.getSubstrateFull(address);2313 }23142315 /**2316 * Get ethereum address balance2317 * @param address ethereum address2318 * @example getEthereum("0x9F0583DbB855d...")2319 * @returns amount of tokens on address2320 */2321 getEthereum(address: TEthereumAccount): Promise<bigint> {2322 return this.ethBalanceGroup.getEthereum(address);2323 }23242325 /**2326 * Transfer tokens to substrate address2327 * @param signer keyring of signer2328 * @param address substrate address of a recipient2329 * @param amount amount of tokens to be transfered2330 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2331 * @returns ```true``` if extrinsic success, otherwise ```false```2332 */2333 transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2334 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2335 }23362337 async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2338 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);23392340 let transfer = {from: null, to: null, amount: 0n} as any;2341 result.result.events.forEach(({event: {data, method, section}}) => {2342 if ((section === 'balances') && (method === 'Transfer')) {2343 transfer = {2344 from: this.helper.address.normalizeSubstrate(data[0]),2345 to: this.helper.address.normalizeSubstrate(data[1]),2346 amount: BigInt(data[2]),2347 };2348 }2349 });2350 let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2351 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2352 isSuccess = isSuccess && BigInt(amount) === transfer.amount;2353 return isSuccess;2354 }2355}23562357class AddressGroup extends HelperGroup<ChainHelperBase> {2358 /**2359 * Normalizes the address to the specified ss58 format, by default ```42```.2360 * @param address substrate address2361 * @param ss58Format format for address conversion, by default ```42```2362 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2363 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2364 */2365 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2366 return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2367 }23682369 /**2370 * Get address in the connected chain format2371 * @param address substrate address2372 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2373 * @returns address in chain format2374 */2375 normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2376 return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2377 }23782379 /**2380 * Get substrate mirror of an ethereum address2381 * @param ethAddress ethereum address2382 * @param toChainFormat false for normalized account2383 * @example ethToSubstrate('0x9F0583DbB855d...')2384 * @returns substrate mirror of a provided ethereum address2385 */2386 ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): TSubstrateAccount {2387 return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2388 }23892390 /**2391 * Get ethereum mirror of a substrate address2392 * @param subAddress substrate account2393 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2394 * @returns ethereum mirror of a provided substrate address2395 */2396 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2397 return CrossAccountId.translateSubToEth(subAddress);2398 }23992400 /**2401 * Encode key to substrate address2402 * @param key key for encoding address2403 * @param ss58Format prefix for encoding to the address of the corresponding network2404 * @returns encoded substrate address2405 */2406 encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format = 42): string {2407 const u8a :Uint8Array = typeof key === 'string'2408 ? hexToU8a(key)2409 : typeof key === 'bigint'2410 ? hexToU8a(key.toString(16))2411 : key;2412 2413 if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2414 throw new Error(`ss58Format is not valid, received ${typeofss58Format} "${ss58Format}"`);2415 }2416 2417 const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2418 if (!allowedDecodedLengths.includes(u8a.length)) {2419 throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2420 }2421 2422 const u8aPrefix = ss58Format < 642423 ? new Uint8Array([ss58Format])2424 : new Uint8Array([2425 ((ss58Format & 0xfc) >> 2) | 0x40,2426 (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2427 ]);24282429 const input = u8aConcat(u8aPrefix, u8a);2430 2431 return base58Encode(u8aConcat(2432 input,2433 blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2434 ));2435 }24362437 /**2438 * Restore substrate address from bigint representation2439 * @param number decimal representation of substrate address2440 * @returns substrate address2441 */2442 restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2443 if (this.helper.api === null) {2444 throw 'Not connected';2445 }2446 const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2447 if (res === undefined || res === null) {2448 throw 'Restore address error';2449 }2450 return res.toString();2451 }24522453 /**2454 * Convert etherium cross account id to substrate cross account id2455 * @param ethCrossAccount etherium cross account2456 * @returns substrate cross account id2457 */2458 convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2459 if (ethCrossAccount.sub === '0') {2460 return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2461 }2462 2463 const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2464 return {Substrate: ss58};2465 }24662467 paraSiblingSovereignAccount(paraid: number) {2468 // We are getting a *sibling* parachain sovereign account,2469 // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2470 const siblingPrefix = '0x7369626c';24712472 const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2473 const suffix = '000000000000000000000000000000000000000000000000';24742475 return siblingPrefix + encodedParaId + suffix;2476 }2477}24782479class StakingGroup extends HelperGroup<UniqueHelper> {2480 /**2481 * Stake tokens for App Promotion2482 * @param signer keyring of signer2483 * @param amountToStake amount of tokens to stake2484 * @param label extra label for log2485 * @returns2486 */2487 async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2488 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2489 const _stakeResult = await this.helper.executeExtrinsic(2490 signer, 'api.tx.appPromotion.stake',2491 [amountToStake], true,2492 );2493 // TODO extract info from stakeResult2494 return true;2495 }24962497 /**2498 * Unstake tokens for App Promotion2499 * @param signer keyring of signer2500 * @param amountToUnstake amount of tokens to unstake2501 * @param label extra label for log2502 * @returns block number where balances will be unlocked2503 */2504 async unstake(signer: TSigner, label?: string): Promise<number> {2505 if(typeof label === 'undefined') label = `${signer.address}`;2506 const _unstakeResult = await this.helper.executeExtrinsic(2507 signer, 'api.tx.appPromotion.unstake',2508 [], true,2509 );2510 // TODO extract block number fron events2511 return 1;2512 }25132514 /**2515 * Get total staked amount for address2516 * @param address substrate or ethereum address2517 * @returns total staked amount2518 */2519 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2520 if (address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2521 return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2522 }25232524 /**2525 * Get total staked per block2526 * @param address substrate or ethereum address2527 * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2528 */2529 async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2530 const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2531 return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => {2532 return {2533 block: block.toBigInt(),2534 amount: amount.toBigInt(),2535 };2536 });2537 }25382539 /**2540 * Get total pending unstake amount for address2541 * @param address substrate or ethereum address2542 * @returns total pending unstake amount2543 */2544 async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2545 return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2546 }25472548 /**2549 * Get pending unstake amount per block for address2550 * @param address substrate or ethereum address2551 * @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 block2552 */2553 async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2554 const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2555 const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => {2556 return {2557 block: block.toBigInt(),2558 amount: amount.toBigInt(),2559 };2560 });2561 return result;2562 }2563}25642565class SchedulerGroup extends HelperGroup<UniqueHelper> {2566 constructor(helper: UniqueHelper) {2567 super(helper);2568 }25692570 cancelScheduled(signer: TSigner, scheduledId: string) {2571 return this.helper.executeExtrinsic(2572 signer,2573 'api.tx.scheduler.cancelNamed',2574 [scheduledId],2575 true,2576 );2577 }25782579 changePriority(signer: TSigner, scheduledId: string, priority: number) {2580 return this.helper.executeExtrinsic(2581 signer,2582 'api.tx.scheduler.changeNamedPriority',2583 [scheduledId, priority],2584 true,2585 );2586 }25872588 scheduleAt<T extends UniqueHelper>(2589 executionBlockNumber: number,2590 options: ISchedulerOptions = {},2591 ) {2592 return this.schedule<T>('schedule', executionBlockNumber, options);2593 }25942595 scheduleAfter<T extends UniqueHelper>(2596 blocksBeforeExecution: number,2597 options: ISchedulerOptions = {},2598 ) {2599 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2600 }26012602 schedule<T extends UniqueHelper>(2603 scheduleFn: 'schedule' | 'scheduleAfter',2604 blocksNum: number,2605 options: ISchedulerOptions = {},2606 ) {2607 // eslint-disable-next-line @typescript-eslint/naming-convention2608 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2609 return this.helper.clone(ScheduledHelperType, {2610 scheduleFn,2611 blocksNum,2612 options,2613 }) as T;2614 }2615}26162617class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {2618 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2619 await this.helper.executeExtrinsic(2620 signer,2621 'api.tx.foreignAssets.registerForeignAsset',2622 [ownerAddress, location, metadata],2623 true,2624 );2625 }26262627 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {2628 await this.helper.executeExtrinsic(2629 signer,2630 'api.tx.foreignAssets.updateForeignAsset',2631 [foreignAssetId, location, metadata],2632 true,2633 );2634 }2635}26362637class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {2638 palletName: string;26392640 constructor(helper: T, palletName: string) {2641 super(helper);26422643 this.palletName = palletName;2644 }26452646 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {2647 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);2648 }2649}26502651class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2652 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {2653 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);2654 }26552656 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {2657 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);2658 }26592660 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {2661 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);2662 }2663}26642665class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2666 async accounts(address: string, currencyId: any) {2667 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;2668 return BigInt(free);2669 }2670}26712672class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {2673 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {2674 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);2675 }26762677 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {2678 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);2679 }26802681 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {2682 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);2683 }26842685 async account(assetId: string | number, address: string) {2686 const accountAsset = (2687 await this.helper.callRpc('api.query.assets.account', [assetId, address])2688 ).toJSON()! as any;26892690 if (accountAsset !== null) {2691 return BigInt(accountAsset['balance']);2692 } else {2693 return null;2694 }2695 }2696}26972698class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {2699 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {2700 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);2701 }2702}27032704class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {2705 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {2706 const apiPrefix = 'api.tx.assetManager.';27072708 const registerTx = this.helper.constructApiCall(2709 apiPrefix + 'registerForeignAsset',2710 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],2711 );27122713 const setUnitsTx = this.helper.constructApiCall(2714 apiPrefix + 'setAssetUnitsPerSecond',2715 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],2716 );27172718 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);2719 const encodedProposal = batchCall?.method.toHex() || '';2720 return encodedProposal;2721 }27222723 async assetTypeId(location: any) {2724 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);2725 }2726}27272728class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {2729 async notePreimage(signer: TSigner, encodedProposal: string) {2730 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);2731 }27322733 externalProposeMajority(proposalHash: string) {2734 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);2735 }27362737 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {2738 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);2739 }27402741 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {2742 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);2743 }2744}27452746class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {2747 collective: string;27482749 constructor(helper: MoonbeamHelper, collective: string) {2750 super(helper);27512752 this.collective = collective;2753 }27542755 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {2756 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);2757 }27582759 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {2760 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);2761 }27622763 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {2764 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);2765 }27662767 async proposalCount() {2768 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));2769 }2770}27712772export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;2773export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;27742775export class UniqueHelper extends ChainHelperBase {2776 balance: BalanceGroup<UniqueHelper>;2777 collection: CollectionGroup;2778 nft: NFTGroup;2779 rft: RFTGroup;2780 ft: FTGroup;2781 staking: StakingGroup;2782 scheduler: SchedulerGroup;2783 foreignAssets: ForeignAssetsGroup;2784 xcm: XcmGroup<UniqueHelper>;2785 xTokens: XTokensGroup<UniqueHelper>;2786 tokens: TokensGroup<UniqueHelper>;27872788 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2789 super(logger, options.helperBase ?? UniqueHelper);27902791 this.balance = new BalanceGroup(this);2792 this.collection = new CollectionGroup(this);2793 this.nft = new NFTGroup(this);2794 this.rft = new RFTGroup(this);2795 this.ft = new FTGroup(this);2796 this.staking = new StakingGroup(this);2797 this.scheduler = new SchedulerGroup(this);2798 this.foreignAssets = new ForeignAssetsGroup(this);2799 this.xcm = new XcmGroup(this, 'polkadotXcm');2800 this.xTokens = new XTokensGroup(this);2801 this.tokens = new TokensGroup(this);2802 }28032804 getSudo<T extends UniqueHelper>() {2805 // eslint-disable-next-line @typescript-eslint/naming-convention2806 const SudoHelperType = SudoHelper(this.helperBase);2807 return this.clone(SudoHelperType) as T;2808 }2809}28102811export class XcmChainHelper extends ChainHelperBase {2812 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {2813 const wsProvider = new WsProvider(wsEndpoint);2814 this.api = new ApiPromise({2815 provider: wsProvider,2816 });2817 await this.api.isReadyOrError;2818 this.network = await UniqueHelper.detectNetwork(this.api);2819 }2820}28212822export class RelayHelper extends XcmChainHelper {2823 xcm: XcmGroup<RelayHelper>;28242825 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2826 super(logger, options.helperBase ?? RelayHelper);28272828 this.xcm = new XcmGroup(this, 'xcmPallet');2829 }2830}28312832export class WestmintHelper extends XcmChainHelper {2833 balance: SubstrateBalanceGroup<WestmintHelper>;2834 xcm: XcmGroup<WestmintHelper>;2835 assets: AssetsGroup<WestmintHelper>;2836 xTokens: XTokensGroup<WestmintHelper>;28372838 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2839 super(logger, options.helperBase ?? WestmintHelper);28402841 this.balance = new SubstrateBalanceGroup(this);2842 this.xcm = new XcmGroup(this, 'polkadotXcm');2843 this.assets = new AssetsGroup(this);2844 this.xTokens = new XTokensGroup(this);2845 }2846}28472848export class MoonbeamHelper extends XcmChainHelper {2849 balance: EthereumBalanceGroup<MoonbeamHelper>;2850 assetManager: MoonbeamAssetManagerGroup;2851 assets: AssetsGroup<MoonbeamHelper>;2852 xTokens: XTokensGroup<MoonbeamHelper>;2853 democracy: MoonbeamDemocracyGroup;2854 collective: {2855 council: MoonbeamCollectiveGroup,2856 techCommittee: MoonbeamCollectiveGroup,2857 };28582859 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2860 super(logger, options.helperBase ?? MoonbeamHelper);28612862 this.balance = new EthereumBalanceGroup(this);2863 this.assetManager = new MoonbeamAssetManagerGroup(this);2864 this.assets = new AssetsGroup(this);2865 this.xTokens = new XTokensGroup(this);2866 this.democracy = new MoonbeamDemocracyGroup(this);2867 this.collective = {2868 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),2869 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),2870 };2871 }2872}28732874export class AcalaHelper extends XcmChainHelper {2875 balance: SubstrateBalanceGroup<AcalaHelper>;2876 assetRegistry: AcalaAssetRegistryGroup;2877 xTokens: XTokensGroup<AcalaHelper>;2878 tokens: TokensGroup<AcalaHelper>;28792880 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2881 super(logger, options.helperBase ?? AcalaHelper);28822883 this.balance = new SubstrateBalanceGroup(this);2884 this.assetRegistry = new AcalaAssetRegistryGroup(this);2885 this.xTokens = new XTokensGroup(this);2886 this.tokens = new TokensGroup(this);2887 }28882889 getSudo<T extends AcalaHelper>() {2890 // eslint-disable-next-line @typescript-eslint/naming-convention2891 const SudoHelperType = SudoHelper(this.helperBase);2892 return this.clone(SudoHelperType) as T;2893 }2894}28952896// eslint-disable-next-line @typescript-eslint/naming-convention2897function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2898 return class extends Base {2899 scheduleFn: 'schedule' | 'scheduleAfter';2900 blocksNum: number;2901 options: ISchedulerOptions;29022903 constructor(...args: any[]) {2904 const logger = args[0] as ILogger;2905 const options = args[1] as {2906 scheduleFn: 'schedule' | 'scheduleAfter',2907 blocksNum: number,2908 options: ISchedulerOptions2909 };29102911 super(logger);29122913 this.scheduleFn = options.scheduleFn;2914 this.blocksNum = options.blocksNum;2915 this.options = options.options;2916 }29172918 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {2919 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);2920 2921 const mandatorySchedArgs = [2922 this.blocksNum,2923 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,2924 this.options.priority ?? null,2925 scheduledTx,2926 ];2927 2928 let schedArgs;2929 let scheduleFn;29302931 if (this.options.scheduledId) {2932 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];29332934 if (this.scheduleFn == 'schedule') {2935 scheduleFn = 'scheduleNamed';2936 } else if (this.scheduleFn == 'scheduleAfter') {2937 scheduleFn = 'scheduleNamedAfter';2938 }2939 } else {2940 schedArgs = mandatorySchedArgs;2941 scheduleFn = this.scheduleFn;2942 }29432944 const extrinsic = 'api.tx.scheduler.' + scheduleFn;29452946 return super.executeExtrinsic(2947 sender,2948 extrinsic,2949 schedArgs,2950 expectSuccess,2951 );2952 }2953 };2954}29552956// eslint-disable-next-line @typescript-eslint/naming-convention2957function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {2958 return class extends Base {2959 constructor(...args: any[]) {2960 super(...args);2961 }29622963 executeExtrinsic (2964 sender: IKeyringPair,2965 extrinsic: string,2966 params: any[],2967 expectSuccess?: boolean,2968 ): Promise<ITransactionResult> {2969 const call = this.constructApiCall(extrinsic, params);2970 return super.executeExtrinsic(2971 sender,2972 'api.tx.sudo.sudo',2973 [call],2974 expectSuccess,2975 );2976 }2977 };2978}29792980export class UniqueBaseCollection {2981 helper: UniqueHelper;2982 collectionId: number;29832984 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2985 this.collectionId = collectionId;2986 this.helper = uniqueHelper;2987 }29882989 async getData() {2990 return await this.helper.collection.getData(this.collectionId);2991 }29922993 async getLastTokenId() {2994 return await this.helper.collection.getLastTokenId(this.collectionId);2995 }29962997 async doesTokenExist(tokenId: number) {2998 return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);2999 }30003001 async getAdmins() {3002 return await this.helper.collection.getAdmins(this.collectionId);3003 }30043005 async getAllowList() {3006 return await this.helper.collection.getAllowList(this.collectionId);3007 }30083009 async getEffectiveLimits() {3010 return await this.helper.collection.getEffectiveLimits(this.collectionId);3011 }30123013 async getProperties(propertyKeys?: string[] | null) {3014 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3015 }30163017 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3018 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3019 }30203021 async getOptions() {3022 return await this.helper.collection.getCollectionOptions(this.collectionId);3023 }30243025 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {3026 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);3027 }30283029 async confirmSponsorship(signer: TSigner) {3030 return await this.helper.collection.confirmSponsorship(signer, this.collectionId);3031 }30323033 async removeSponsor(signer: TSigner) {3034 return await this.helper.collection.removeSponsor(signer, this.collectionId);3035 }30363037 async setLimits(signer: TSigner, limits: ICollectionLimits) {3038 return await this.helper.collection.setLimits(signer, this.collectionId, limits);3039 }30403041 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {3042 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);3043 }30443045 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3046 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);3047 }30483049 async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {3050 return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);3051 }30523053 async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {3054 return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);3055 }30563057 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3058 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);3059 }30603061 async setProperties(signer: TSigner, properties: IProperty[]) {3062 return await this.helper.collection.setProperties(signer, this.collectionId, properties);3063 }30643065 async deleteProperties(signer: TSigner, propertyKeys: string[]) {3066 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);3067 }30683069 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {3070 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);3071 }30723073 async enableNesting(signer: TSigner, permissions: INestingPermissions) {3074 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);3075 }30763077 async disableNesting(signer: TSigner) {3078 return await this.helper.collection.disableNesting(signer, this.collectionId);3079 }30803081 async burn(signer: TSigner) {3082 return await this.helper.collection.burn(signer, this.collectionId);3083 }30843085 scheduleAt<T extends UniqueHelper>(3086 executionBlockNumber: number,3087 options: ISchedulerOptions = {},3088 ) {3089 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3090 return new UniqueBaseCollection(this.collectionId, scheduledHelper);3091 }30923093 scheduleAfter<T extends UniqueHelper>(3094 blocksBeforeExecution: number,3095 options: ISchedulerOptions = {},3096 ) {3097 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3098 return new UniqueBaseCollection(this.collectionId, scheduledHelper);3099 }31003101 getSudo<T extends UniqueHelper>() {3102 return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());3103 }3104}310531063107export class UniqueNFTCollection extends UniqueBaseCollection {3108 getTokenObject(tokenId: number) {3109 return new UniqueNFToken(tokenId, this);3110 }31113112 async getTokensByAddress(addressObj: ICrossAccountId) {3113 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);3114 }31153116 async getToken(tokenId: number, blockHashAt?: string) {3117 return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);3118 }31193120 async getTokenOwner(tokenId: number, blockHashAt?: string) {3121 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3122 }31233124 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3125 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3126 }31273128 async getTokenChildren(tokenId: number, blockHashAt?: string) {3129 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);3130 }31313132 async getPropertyPermissions(propertyKeys: string[] | null = null) {3133 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);3134 }31353136 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3137 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3138 }31393140 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {3141 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);3142 }31433144 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3145 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);3146 }31473148 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {3149 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);3150 }31513152 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {3153 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);3154 }31553156 async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3157 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});3158 }31593160 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) {3161 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);3162 }31633164 async burnToken(signer: TSigner, tokenId: number) {3165 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);3166 }31673168 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {3169 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);3170 }31713172 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3173 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);3174 }31753176 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3177 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3178 }31793180 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3181 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3182 }31833184 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3185 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3186 }31873188 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3189 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3190 }31913192 scheduleAt<T extends UniqueHelper>(3193 executionBlockNumber: number,3194 options: ISchedulerOptions = {},3195 ) {3196 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3197 return new UniqueNFTCollection(this.collectionId, scheduledHelper);3198 }31993200 scheduleAfter<T extends UniqueHelper>(3201 blocksBeforeExecution: number,3202 options: ISchedulerOptions = {},3203 ) {3204 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3205 return new UniqueNFTCollection(this.collectionId, scheduledHelper);3206 }32073208 getSudo<T extends UniqueHelper>() {3209 return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());3210 }3211}321232133214export class UniqueRFTCollection extends UniqueBaseCollection {3215 getTokenObject(tokenId: number) {3216 return new UniqueRFToken(tokenId, this);3217 }32183219 async getToken(tokenId: number, blockHashAt?: string) {3220 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3221 }32223223 async getTokensByAddress(addressObj: ICrossAccountId) {3224 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3225 }32263227 async getTop10TokenOwners(tokenId: number) {3228 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3229 }32303231 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3232 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3233 }32343235 async getTokenTotalPieces(tokenId: number) {3236 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3237 }32383239 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3240 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3241 }32423243 async getPropertyPermissions(propertyKeys: string[] | null = null) {3244 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3245 }32463247 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3248 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3249 }32503251 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {3252 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3253 }32543255 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3256 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3257 }32583259 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {3260 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3261 }32623263 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3264 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3265 }32663267 async mintToken(signer: TSigner, pieces=1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3268 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3269 }32703271 async mintMultipleTokens(signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) {3272 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3273 }32743275 async burnToken(signer: TSigner, tokenId: number, amount=1n) {3276 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3277 }32783279 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n) {3280 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3281 }32823283 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3284 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3285 }32863287 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3288 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3289 }32903291 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3292 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3293 }32943295 scheduleAt<T extends UniqueHelper>(3296 executionBlockNumber: number,3297 options: ISchedulerOptions = {},3298 ) {3299 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3300 return new UniqueRFTCollection(this.collectionId, scheduledHelper);3301 }33023303 scheduleAfter<T extends UniqueHelper>(3304 blocksBeforeExecution: number,3305 options: ISchedulerOptions = {},3306 ) {3307 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3308 return new UniqueRFTCollection(this.collectionId, scheduledHelper);3309 }33103311 getSudo<T extends UniqueHelper>() {3312 return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());3313 }3314}331533163317export class UniqueFTCollection extends UniqueBaseCollection {3318 async getBalance(addressObj: ICrossAccountId) {3319 return await this.helper.ft.getBalance(this.collectionId, addressObj);3320 }33213322 async getTotalPieces() {3323 return await this.helper.ft.getTotalPieces(this.collectionId);3324 }33253326 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3327 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3328 }33293330 async getTop10Owners() {3331 return await this.helper.ft.getTop10Owners(this.collectionId);3332 }33333334 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {3335 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3336 }33373338 async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {3339 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3340 }33413342 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3343 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3344 }33453346 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3347 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3348 }33493350 async burnTokens(signer: TSigner, amount=1n) {3351 return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3352 }33533354 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3355 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3356 }33573358 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3359 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3360 }33613362 scheduleAt<T extends UniqueHelper>(3363 executionBlockNumber: number,3364 options: ISchedulerOptions = {},3365 ) {3366 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3367 return new UniqueFTCollection(this.collectionId, scheduledHelper);3368 }33693370 scheduleAfter<T extends UniqueHelper>(3371 blocksBeforeExecution: number,3372 options: ISchedulerOptions = {},3373 ) {3374 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3375 return new UniqueFTCollection(this.collectionId, scheduledHelper);3376 }33773378 getSudo<T extends UniqueHelper>() {3379 return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());3380 }3381}338233833384export class UniqueBaseToken {3385 collection: UniqueNFTCollection | UniqueRFTCollection;3386 collectionId: number;3387 tokenId: number;33883389 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3390 this.collection = collection;3391 this.collectionId = collection.collectionId;3392 this.tokenId = tokenId;3393 }33943395 async getNextSponsored(addressObj: ICrossAccountId) {3396 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3397 }33983399 async getProperties(propertyKeys?: string[] | null) {3400 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3401 }34023403 async setProperties(signer: TSigner, properties: IProperty[]) {3404 return await this.collection.setTokenProperties(signer, this.tokenId, properties);3405 }34063407 async deleteProperties(signer: TSigner, propertyKeys: string[]) {3408 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3409 }34103411 async doesExist() {3412 return await this.collection.doesTokenExist(this.tokenId);3413 }34143415 nestingAccount() {3416 return this.collection.helper.util.getTokenAccount(this);3417 }34183419 scheduleAt<T extends UniqueHelper>(3420 executionBlockNumber: number,3421 options: ISchedulerOptions = {},3422 ) {3423 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3424 return new UniqueBaseToken(this.tokenId, scheduledCollection);3425 }34263427 scheduleAfter<T extends UniqueHelper>(3428 blocksBeforeExecution: number,3429 options: ISchedulerOptions = {},3430 ) {3431 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3432 return new UniqueBaseToken(this.tokenId, scheduledCollection);3433 }34343435 getSudo<T extends UniqueHelper>() {3436 return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());3437 }3438}343934403441export class UniqueNFToken extends UniqueBaseToken {3442 collection: UniqueNFTCollection;34433444 constructor(tokenId: number, collection: UniqueNFTCollection) {3445 super(tokenId, collection);3446 this.collection = collection;3447 }34483449 async getData(blockHashAt?: string) {3450 return await this.collection.getToken(this.tokenId, blockHashAt);3451 }34523453 async getOwner(blockHashAt?: string) {3454 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3455 }34563457 async getTopmostOwner(blockHashAt?: string) {3458 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3459 }34603461 async getChildren(blockHashAt?: string) {3462 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3463 }34643465 async nest(signer: TSigner, toTokenObj: IToken) {3466 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3467 }34683469 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3470 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3471 }34723473 async transfer(signer: TSigner, addressObj: ICrossAccountId) {3474 return await this.collection.transferToken(signer, this.tokenId, addressObj);3475 }34763477 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3478 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3479 }34803481 async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3482 return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3483 }34843485 async isApproved(toAddressObj: ICrossAccountId) {3486 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3487 }34883489 async burn(signer: TSigner) {3490 return await this.collection.burnToken(signer, this.tokenId);3491 }34923493 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3494 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3495 }34963497 scheduleAt<T extends UniqueHelper>(3498 executionBlockNumber: number,3499 options: ISchedulerOptions = {},3500 ) {3501 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3502 return new UniqueNFToken(this.tokenId, scheduledCollection);3503 }35043505 scheduleAfter<T extends UniqueHelper>(3506 blocksBeforeExecution: number,3507 options: ISchedulerOptions = {},3508 ) {3509 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3510 return new UniqueNFToken(this.tokenId, scheduledCollection);3511 }35123513 getSudo<T extends UniqueHelper>() {3514 return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());3515 }3516}35173518export class UniqueRFToken extends UniqueBaseToken {3519 collection: UniqueRFTCollection;35203521 constructor(tokenId: number, collection: UniqueRFTCollection) {3522 super(tokenId, collection);3523 this.collection = collection;3524 }35253526 async getData(blockHashAt?: string) {3527 return await this.collection.getToken(this.tokenId, blockHashAt);3528 }35293530 async getTop10Owners() {3531 return await this.collection.getTop10TokenOwners(this.tokenId);3532 }35333534 async getBalance(addressObj: ICrossAccountId) {3535 return await this.collection.getTokenBalance(this.tokenId, addressObj);3536 }35373538 async getTotalPieces() {3539 return await this.collection.getTokenTotalPieces(this.tokenId);3540 }35413542 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3543 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3544 }35453546 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {3547 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3548 }35493550 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3551 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3552 }35533554 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3555 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3556 }35573558 async repartition(signer: TSigner, amount: bigint) {3559 return await this.collection.repartitionToken(signer, this.tokenId, amount);3560 }35613562 async burn(signer: TSigner, amount=1n) {3563 return await this.collection.burnToken(signer, this.tokenId, amount);3564 }35653566 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3567 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3568 }35693570 scheduleAt<T extends UniqueHelper>(3571 executionBlockNumber: number,3572 options: ISchedulerOptions = {},3573 ) {3574 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3575 return new UniqueRFToken(this.tokenId, scheduledCollection);3576 }35773578 scheduleAfter<T extends UniqueHelper>(3579 blocksBeforeExecution: number,3580 options: ISchedulerOptions = {},3581 ) {3582 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3583 return new UniqueRFToken(this.tokenId, scheduledCollection);3584 }35853586 getSudo<T extends UniqueHelper>() {3587 return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());3588 }3589}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034/* eslint-disable @typescript-eslint/no-var-requires */5/* eslint-disable function-call-argument-newline */6/* eslint-disable no-prototype-builtins */78import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';11import {IKeyringPair} from '@polkadot/types/types';12import {hexToU8a} from '@polkadot/util/hex';13import {u8aConcat} from '@polkadot/util/u8a';14import {15 IApiListeners,16 IBlock,17 IEvent,18 IChainProperties,19 ICollectionCreationOptions,20 ICollectionLimits,21 ICollectionPermissions,22 ICrossAccountId,23 ICrossAccountIdLower,24 ILogger,25 INestingPermissions,26 IProperty,27 IStakingInfo,28 ISchedulerOptions,29 ISubstrateBalance,30 IToken,31 ITokenPropertyPermission,32 ITransactionResult,33 IUniqueHelperLog,34 TApiAllowedListeners,35 TEthereumAccount,36 TSigner,37 TSubstrateAccount,38 TNetworks,39 IForeignAssetMetadata,40 AcalaAssetMetadata,41 MoonbeamAssetInfo,42 DemocracyStandardAccountVote,43 IEthCrossAccountId,44} from './types';45import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';4647export class CrossAccountId implements ICrossAccountId {48 Substrate?: TSubstrateAccount;49 Ethereum?: TEthereumAccount;5051 constructor(account: ICrossAccountId) {52 if (account.Substrate) this.Substrate = account.Substrate;53 if (account.Ethereum) this.Ethereum = account.Ethereum;54 }5556 static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {57 switch (domain) {58 case 'Substrate': return new CrossAccountId({Substrate: account.address});59 case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();60 }61 }6263 static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {64 return new CrossAccountId({Substrate: address.substrate, Ethereum: address.ethereum});65 }6667 static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {68 return encodeAddress(decodeAddress(address), ss58Format);69 }7071 static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {72 return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});73 }7475 withNormalizedSubstrate(ss58Format = 42): CrossAccountId {76 if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);77 return this;78 }7980 static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {81 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));82 }8384 toEthereum(): CrossAccountId {85 if (this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});86 return this;87 }8889 static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {90 return evmToAddress(address, ss58Format);91 }9293 toSubstrate(ss58Format?: number): CrossAccountId {94 if (this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});95 return this;96 }9798 toLowerCase(): CrossAccountId {99 if (this.Substrate) this.Substrate = this.Substrate.toLowerCase();100 if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();101 return this;102 }103}104105const nesting = {106 toChecksumAddress(address: string): string {107 if (typeof address === 'undefined') return '';108109 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);110111 address = address.toLowerCase().replace(/^0x/i,'');112 const addressHash = keccakAsHex(address).replace(/^0x/i,'');113 const checksumAddress = ['0x'];114115 for (let i = 0; i < address.length; i++) {116 // If ith character is 8 to f then make it uppercase117 if (parseInt(addressHash[i], 16) > 7) {118 checksumAddress.push(address[i].toUpperCase());119 } else {120 checksumAddress.push(address[i]);121 }122 }123 return checksumAddress.join('');124 },125 tokenIdToAddress(collectionId: number, tokenId: number) {126 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);127 },128};129130class UniqueUtil {131 static transactionStatus = {132 NOT_READY: 'NotReady',133 FAIL: 'Fail',134 SUCCESS: 'Success',135 };136137 static chainLogType = {138 EXTRINSIC: 'extrinsic',139 RPC: 'rpc',140 };141142 static getTokenAccount(token: IToken): CrossAccountId {143 return new CrossAccountId({Ethereum: this.getTokenAddress(token)});144 }145146 static getTokenAddress(token: IToken): string {147 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);148 }149150 static getDefaultLogger(): ILogger {151 return {152 log(msg: any, level = 'INFO') {153 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));154 },155 level: {156 ERROR: 'ERROR',157 WARNING: 'WARNING',158 INFO: 'INFO',159 },160 };161 }162163 static vec2str(arr: string[] | number[]) {164 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');165 }166167 static str2vec(string: string) {168 if (typeof string !== 'string') return string;169 return Array.from(string).map(x => x.charCodeAt(0));170 }171172 static fromSeed(seed: string, ss58Format = 42) {173 const keyring = new Keyring({type: 'sr25519', ss58Format});174 return keyring.addFromUri(seed);175 }176177 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {178 if (creationResult.status !== this.transactionStatus.SUCCESS) {179 throw Error('Unable to create collection!');180 }181182 let collectionId = null;183 creationResult.result.events.forEach(({event: {data, method, section}}) => {184 if ((section === 'common') && (method === 'CollectionCreated')) {185 collectionId = parseInt(data[0].toString(), 10);186 }187 });188189 if (collectionId === null) {190 throw Error('No CollectionCreated event was found!');191 }192193 return collectionId;194 }195196 static extractTokensFromCreationResult(creationResult: ITransactionResult): {197 success: boolean,198 tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],199 } {200 if (creationResult.status !== this.transactionStatus.SUCCESS) {201 throw Error('Unable to create tokens!');202 }203 let success = false;204 const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];205 creationResult.result.events.forEach(({event: {data, method, section}}) => {206 if (method === 'ExtrinsicSuccess') {207 success = true;208 } else if ((section === 'common') && (method === 'ItemCreated')) {209 tokens.push({210 collectionId: parseInt(data[0].toString(), 10),211 tokenId: parseInt(data[1].toString(), 10),212 owner: data[2].toHuman(),213 amount: data[3].toBigInt(),214 });215 }216 });217 return {success, tokens};218 }219220 static extractTokensFromBurnResult(burnResult: ITransactionResult): {221 success: boolean,222 tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[],223 } {224 if (burnResult.status !== this.transactionStatus.SUCCESS) {225 throw Error('Unable to burn tokens!');226 }227 let success = false;228 const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[];229 burnResult.result.events.forEach(({event: {data, method, section}}) => {230 if (method === 'ExtrinsicSuccess') {231 success = true;232 } else if ((section === 'common') && (method === 'ItemDestroyed')) {233 tokens.push({234 collectionId: parseInt(data[0].toString(), 10),235 tokenId: parseInt(data[1].toString(), 10),236 owner: data[2].toHuman(),237 amount: data[3].toBigInt(),238 });239 }240 });241 return {success, tokens};242 }243244 static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {245 let eventId = null;246 events.forEach(({event: {data, method, section}}) => {247 if ((section === expectedSection) && (method === expectedMethod)) {248 eventId = parseInt(data[0].toString(), 10);249 }250 });251252 if (eventId === null) {253 throw Error(`No ${expectedMethod} event was found!`);254 }255 return eventId === collectionId;256 }257258 static isTokenTransferSuccess(events: {event: IEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {259 const normalizeAddress = (address: string | ICrossAccountId) => {260 if(typeof address === 'string') return address;261 const obj = {} as any;262 Object.keys(address).forEach(k => {263 obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];264 });265 if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);266 if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();267 return address;268 };269 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;270 events.forEach(({event: {data, method, section}}) => {271 if ((section === 'common') && (method === 'Transfer')) {272 const hData = (data as any).toJSON();273 transfer = {274 collectionId: hData[0],275 tokenId: hData[1],276 from: normalizeAddress(hData[2]),277 to: normalizeAddress(hData[3]),278 amount: BigInt(hData[4]),279 };280 }281 });282 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;283 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);284 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);285 isSuccess = isSuccess && amount === transfer.amount;286 return isSuccess;287 }288289 static bigIntToDecimals(number: bigint, decimals = 18) {290 const numberStr = number.toString();291 const dotPos = numberStr.length - decimals;292293 if (dotPos <= 0) {294 return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;295 } else {296 const intPart = numberStr.substring(0, dotPos);297 const fractPart = numberStr.substring(dotPos);298 return intPart + '.' + fractPart;299 }300 }301}302303class UniqueEventHelper {304 private static extractIndex(index: any): [number, number] | string {305 if(index.toRawType() === '[u8;2]') return [index[0], index[1]];306 return index.toJSON();307 }308309 private static extractSub(data: any, subTypes: any): {[key: string]: any} {310 let obj: any = {};311 let index = 0;312313 if (data.entries) {314 for(const [key, value] of data.entries()) {315 obj[key] = this.extractData(value, subTypes[index]);316 index++;317 }318 } else obj = data.toJSON();319320 return obj;321 }322323 private static toHuman(data: any) {324 return data && data.toHuman ? data.toHuman() : `${data}`;325 }326327 private static extractData(data: any, type: any): any {328 if(!type) return this.toHuman(data);329 if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();330 if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();331 if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);332 return this.toHuman(data);333 }334335 public static extractEvents(events: {event: any, phase: any}[]): IEvent[] {336 const parsedEvents: IEvent[] = [];337338 events.forEach((record) => {339 const {event, phase} = record;340 const types = event.typeDef;341342 const eventData: IEvent = {343 section: event.section.toString(),344 method: event.method.toString(),345 index: this.extractIndex(event.index),346 data: [],347 phase: phase.toJSON(),348 };349350 event.data.forEach((val: any, index: number) => {351 eventData.data.push(this.extractData(val, types[index]));352 });353354 parsedEvents.push(eventData);355 });356357 return parsedEvents;358 }359}360361export class ChainHelperBase {362 helperBase: any;363364 transactionStatus = UniqueUtil.transactionStatus;365 chainLogType = UniqueUtil.chainLogType;366 util: typeof UniqueUtil;367 eventHelper: typeof UniqueEventHelper;368 logger: ILogger;369 api: ApiPromise | null;370 forcedNetwork: TNetworks | null;371 network: TNetworks | null;372 chainLog: IUniqueHelperLog[];373 children: ChainHelperBase[];374 address: AddressGroup;375 chain: ChainGroup;376377 constructor(logger?: ILogger, helperBase?: any) {378 this.helperBase = helperBase;379380 this.util = UniqueUtil;381 this.eventHelper = UniqueEventHelper;382 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();383 this.logger = logger;384 this.api = null;385 this.forcedNetwork = null;386 this.network = null;387 this.chainLog = [];388 this.children = [];389 this.address = new AddressGroup(this);390 this.chain = new ChainGroup(this);391 }392393 clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {394 Object.setPrototypeOf(helperCls.prototype, this);395 const newHelper = new helperCls(this.logger, options);396397 newHelper.api = this.api;398 newHelper.network = this.network;399 newHelper.forceNetwork = this.forceNetwork;400401 this.children.push(newHelper);402403 return newHelper;404 }405406 getApi(): ApiPromise {407 if(this.api === null) throw Error('API not initialized');408 return this.api;409 }410411 clearChainLog(): void {412 this.chainLog = [];413 }414415 forceNetwork(value: TNetworks): void {416 this.forcedNetwork = value;417 }418419 async connect(wsEndpoint: string, listeners?: IApiListeners) {420 if (this.api !== null) throw Error('Already connected');421 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);422 this.api = api;423 this.network = network;424 }425426 async disconnect() {427 for (const child of this.children) {428 child.clearApi();429 }430431 if (this.api === null) return;432 await this.api.disconnect();433 this.clearApi();434 }435436 clearApi() {437 this.api = null;438 this.network = null;439 }440441 static async detectNetwork(api: ApiPromise): Promise<TNetworks> {442 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;443 const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];444445 if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;446447 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;448 return 'opal';449 }450451 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {452 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});453 await api.isReady;454455 const network = await this.detectNetwork(api);456457 await api.disconnect();458459 return network;460 }461462 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{463 api: ApiPromise;464 network: TNetworks;465 }> {466 if(typeof network === 'undefined' || network === null) network = 'opal';467 const supportedRPC = {468 opal: {469 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,470 },471 quartz: {472 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,473 },474 unique: {475 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,476 },477 rococo: {},478 westend: {},479 moonbeam: {},480 moonriver: {},481 acala: {},482 karura: {},483 westmint: {},484 };485 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);486 const rpc = supportedRPC[network];487488 // TODO: investigate how to replace rpc in runtime489 // api._rpcCore.addUserInterfaces(rpc);490491 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});492493 await api.isReadyOrError;494495 if (typeof listeners === 'undefined') listeners = {};496 for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {497 if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;498 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);499 }500501 return {api, network};502 }503504 getTransactionStatus(data: {events: {event: IEvent}[], status: any}) {505 const {events, status} = data;506 if (status.isReady) {507 return this.transactionStatus.NOT_READY;508 }509 if (status.isBroadcast) {510 return this.transactionStatus.NOT_READY;511 }512 if (status.isInBlock || status.isFinalized) {513 const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');514 if (errors.length > 0) {515 return this.transactionStatus.FAIL;516 }517 if (events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {518 return this.transactionStatus.SUCCESS;519 }520 }521522 return this.transactionStatus.FAIL;523 }524525 signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {526 const sign = (callback: any) => {527 if(options !== null) return transaction.signAndSend(sender, options, callback);528 return transaction.signAndSend(sender, callback);529 };530 // eslint-disable-next-line no-async-promise-executor531 return new Promise(async (resolve, reject) => {532 try {533 const unsub = await sign((result: any) => {534 const status = this.getTransactionStatus(result);535536 if (status === this.transactionStatus.SUCCESS) {537 this.logger.log(`${label} successful`);538 unsub();539 resolve({result, status});540 } else if (status === this.transactionStatus.FAIL) {541 let moduleError = null;542543 if (result.hasOwnProperty('dispatchError')) {544 const dispatchError = result['dispatchError'];545546 if (dispatchError) {547 if (dispatchError.isModule) {548 const modErr = dispatchError.asModule;549 const errorMeta = dispatchError.registry.findMetaError(modErr);550551 moduleError = `${errorMeta.section}.${errorMeta.name}`;552 } else {553 moduleError = dispatchError.toHuman();554 }555 } else {556 this.logger.log(result, this.logger.level.ERROR);557 }558 }559560 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);561 unsub();562 reject({status, moduleError, result});563 }564 });565 } catch (e) {566 this.logger.log(e, this.logger.level.ERROR);567 reject(e);568 }569 });570 }571572 async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {573 const api = this.getApi();574 const signingInfo = await api.derive.tx.signingInfo(signer.address);575576 // We need to sign the tx because577 // unsigned transactions does not have an inclusion fee578 tx.sign(signer, {579 blockHash: api.genesisHash,580 genesisHash: api.genesisHash,581 runtimeVersion: api.runtimeVersion,582 nonce: signingInfo.nonce,583 });584585 if (len === null) {586 return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;587 } else {588 return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;589 }590 }591592 constructApiCall(apiCall: string, params: any[]) {593 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);594 let call = this.getApi() as any;595 for(const part of apiCall.slice(4).split('.')) {596 call = call[part];597 }598 return call(...params);599 }600601 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=true, options: Partial<SignerOptions>|null = null/*, failureMessage='expected success'*/) {602 if(this.api === null) throw Error('API not initialized');603 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);604605 const startTime = (new Date()).getTime();606 let result: ITransactionResult;607 let events: IEvent[] = [];608 try {609 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;610 events = this.eventHelper.extractEvents(result.result.events);611 }612 catch(e) {613 if(!(e as object).hasOwnProperty('status')) throw e;614 result = e as ITransactionResult;615 }616617 const endTime = (new Date()).getTime();618619 const log = {620 executedAt: endTime,621 executionTime: endTime - startTime,622 type: this.chainLogType.EXTRINSIC,623 status: result.status,624 call: extrinsic,625 signer: this.getSignerAddress(sender),626 params,627 } as IUniqueHelperLog;628629 if(result.status !== this.transactionStatus.SUCCESS) {630 if (result.moduleError) log.moduleError = result.moduleError;631 else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError;632 }633 if(events.length > 0) log.events = events;634635 this.chainLog.push(log);636637 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {638 if (result.moduleError) throw Error(`${result.moduleError}`);639 else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));640 }641 return result;642 }643644 async callRpc(rpc: string, params?: any[]) {645 if(typeof params === 'undefined') params = [];646 if(this.api === null) throw Error('API not initialized');647 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);648649 const startTime = (new Date()).getTime();650 let result;651 let error = null;652 const log = {653 type: this.chainLogType.RPC,654 call: rpc,655 params,656 } as IUniqueHelperLog;657658 try {659 result = await this.constructApiCall(rpc, params);660 }661 catch(e) {662 error = e;663 }664665 const endTime = (new Date()).getTime();666667 log.executedAt = endTime;668 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';669 log.executionTime = endTime - startTime;670671 this.chainLog.push(log);672673 if(error !== null) throw error;674675 return result;676 }677678 getSignerAddress(signer: IKeyringPair | string): string {679 if(typeof signer === 'string') return signer;680 return signer.address;681 }682683 fetchAllPalletNames(): string[] {684 if(this.api === null) throw Error('API not initialized');685 return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());686 }687688 fetchMissingPalletNames(requiredPallets: string[]): string[] {689 const palletNames = this.fetchAllPalletNames();690 return requiredPallets.filter(p => !palletNames.includes(p));691 }692}693694695class HelperGroup<T extends ChainHelperBase> {696 helper: T;697698 constructor(uniqueHelper: T) {699 this.helper = uniqueHelper;700 }701}702703704class CollectionGroup extends HelperGroup<UniqueHelper> {705 /**706 * Get number of blocks when sponsored transaction is available.707 *708 * @param collectionId ID of collection709 * @param tokenId ID of token710 * @param addressObj address for which the sponsorship is checked711 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});712 * @returns number of blocks or null if sponsorship hasn't been set713 */714 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {715 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();716 }717718 /**719 * Get the number of created collections.720 *721 * @returns number of created collections722 */723 async getTotalCount(): Promise<number> {724 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();725 }726727 /**728 * Get information about the collection with additional data,729 * including the number of tokens it contains, its administrators,730 * the normalized address of the collection's owner, and decoded name and description.731 *732 * @param collectionId ID of collection733 * @example await getData(2)734 * @returns collection information object735 */736 async getData(collectionId: number): Promise<{737 id: number;738 name: string;739 description: string;740 tokensCount: number;741 admins: CrossAccountId[];742 normalizedOwner: TSubstrateAccount;743 raw: any744 } | null> {745 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);746 const humanCollection = collection.toHuman(), collectionData = {747 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],748 raw: humanCollection,749 } as any, jsonCollection = collection.toJSON();750 if (humanCollection === null) return null;751 collectionData.raw.limits = jsonCollection.limits;752 collectionData.raw.permissions = jsonCollection.permissions;753 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);754 for (const key of ['name', 'description']) {755 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);756 }757758 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))759 ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)760 : 0;761 collectionData.admins = await this.getAdmins(collectionId);762763 return collectionData;764 }765766 /**767 * Get the addresses of the collection's administrators, optionally normalized.768 *769 * @param collectionId ID of collection770 * @param normalize whether to normalize the addresses to the default ss58 format771 * @example await getAdmins(1)772 * @returns array of administrators773 */774 async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {775 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();776777 return normalize778 ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())779 : admins;780 }781782 /**783 * Get the addresses added to the collection allow-list, optionally normalized.784 * @param collectionId ID of collection785 * @param normalize whether to normalize the addresses to the default ss58 format786 * @example await getAllowList(1)787 * @returns array of allow-listed addresses788 */789 async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {790 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();791 return normalize792 ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())793 : allowListed;794 }795796 /**797 * Get the effective limits of the collection instead of null for default values798 *799 * @param collectionId ID of collection800 * @example await getEffectiveLimits(2)801 * @returns object of collection limits802 */803 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {804 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();805 }806807 /**808 * Burns the collection if the signer has sufficient permissions and collection is empty.809 *810 * @param signer keyring of signer811 * @param collectionId ID of collection812 * @example await helper.collection.burn(aliceKeyring, 3);813 * @returns ```true``` if extrinsic success, otherwise ```false```814 */815 async burn(signer: TSigner, collectionId: number): Promise<boolean> {816 const result = await this.helper.executeExtrinsic(817 signer,818 'api.tx.unique.destroyCollection', [collectionId],819 true,820 );821822 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');823 }824825 /**826 * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.827 *828 * @param signer keyring of signer829 * @param collectionId ID of collection830 * @param sponsorAddress Sponsor substrate address831 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")832 * @returns ```true``` if extrinsic success, otherwise ```false```833 */834 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {835 const result = await this.helper.executeExtrinsic(836 signer,837 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],838 true,839 );840841 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet');842 }843844 /**845 * Confirms consent to sponsor the collection on behalf of the signer.846 *847 * @param signer keyring of signer848 * @param collectionId ID of collection849 * @example confirmSponsorship(aliceKeyring, 10)850 * @returns ```true``` if extrinsic success, otherwise ```false```851 */852 async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {853 const result = await this.helper.executeExtrinsic(854 signer,855 'api.tx.unique.confirmSponsorship', [collectionId],856 true,857 );858859 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed');860 }861862 /**863 * Removes the sponsor of a collection, regardless if it consented or not.864 *865 * @param signer keyring of signer866 * @param collectionId ID of collection867 * @example removeSponsor(aliceKeyring, 10)868 * @returns ```true``` if extrinsic success, otherwise ```false```869 */870 async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {871 const result = await this.helper.executeExtrinsic(872 signer,873 'api.tx.unique.removeCollectionSponsor', [collectionId],874 true,875 );876877 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorRemoved');878 }879880 /**881 * Sets the limits of the collection. At least one limit must be specified for a correct call.882 *883 * @param signer keyring of signer884 * @param collectionId ID of collection885 * @param limits collection limits object886 * @example887 * await setLimits(888 * aliceKeyring,889 * 10,890 * {891 * sponsorTransferTimeout: 0,892 * ownerCanDestroy: false893 * }894 * )895 * @returns ```true``` if extrinsic success, otherwise ```false```896 */897 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {898 const result = await this.helper.executeExtrinsic(899 signer,900 'api.tx.unique.setCollectionLimits', [collectionId, limits],901 true,902 );903904 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet');905 }906907 /**908 * Changes the owner of the collection to the new Substrate address.909 *910 * @param signer keyring of signer911 * @param collectionId ID of collection912 * @param ownerAddress substrate address of new owner913 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")914 * @returns ```true``` if extrinsic success, otherwise ```false```915 */916 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {917 const result = await this.helper.executeExtrinsic(918 signer,919 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],920 true,921 );922923 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged');924 }925926 /**927 * Adds a collection administrator.928 *929 * @param signer keyring of signer930 * @param collectionId ID of collection931 * @param adminAddressObj Administrator address (substrate or ethereum)932 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})933 * @returns ```true``` if extrinsic success, otherwise ```false```934 */935 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {936 const result = await this.helper.executeExtrinsic(937 signer,938 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],939 true,940 );941942 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded');943 }944945 /**946 * Removes a collection administrator.947 *948 * @param signer keyring of signer949 * @param collectionId ID of collection950 * @param adminAddressObj Administrator address (substrate or ethereum)951 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})952 * @returns ```true``` if extrinsic success, otherwise ```false```953 */954 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {955 const result = await this.helper.executeExtrinsic(956 signer,957 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],958 true,959 );960961 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved');962 }963964 /**965 * Check if user is in allow list.966 *967 * @param collectionId ID of collection968 * @param user Account to check969 * @example await getAdmins(1)970 * @returns is user in allow list971 */972 async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {973 return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();974 }975976 /**977 * Adds an address to allow list978 * @param signer keyring of signer979 * @param collectionId ID of collection980 * @param addressObj address to add to the allow list981 * @returns ```true``` if extrinsic success, otherwise ```false```982 */983 async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {984 const result = await this.helper.executeExtrinsic(985 signer,986 'api.tx.unique.addToAllowList', [collectionId, addressObj],987 true,988 );989990 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');991 }992993 /**994 * Removes an address from allow list995 *996 * @param signer keyring of signer997 * @param collectionId ID of collection998 * @param addressObj address to remove from the allow list999 * @returns ```true``` if extrinsic success, otherwise ```false```1000 */1001 async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1002 const result = await this.helper.executeExtrinsic(1003 signer,1004 'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1005 true,1006 );10071008 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved');1009 }10101011 /**1012 * Sets onchain permissions for selected collection.1013 *1014 * @param signer keyring of signer1015 * @param collectionId ID of collection1016 * @param permissions collection permissions object1017 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1018 * @returns ```true``` if extrinsic success, otherwise ```false```1019 */1020 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1021 const result = await this.helper.executeExtrinsic(1022 signer,1023 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1024 true,1025 );10261027 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet');1028 }10291030 /**1031 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1032 *1033 * @param signer keyring of signer1034 * @param collectionId ID of collection1035 * @param permissions nesting permissions object1036 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1037 * @returns ```true``` if extrinsic success, otherwise ```false```1038 */1039 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1040 return await this.setPermissions(signer, collectionId, {nesting: permissions});1041 }10421043 /**1044 * Disables nesting for selected collection.1045 *1046 * @param signer keyring of signer1047 * @param collectionId ID of collection1048 * @example disableNesting(aliceKeyring, 10);1049 * @returns ```true``` if extrinsic success, otherwise ```false```1050 */1051 async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1052 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1053 }10541055 /**1056 * Sets onchain properties to the collection.1057 *1058 * @param signer keyring of signer1059 * @param collectionId ID of collection1060 * @param properties array of property objects1061 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1062 * @returns ```true``` if extrinsic success, otherwise ```false```1063 */1064 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1065 const result = await this.helper.executeExtrinsic(1066 signer,1067 'api.tx.unique.setCollectionProperties', [collectionId, properties],1068 true,1069 );10701071 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1072 }10731074 /**1075 * Get collection properties.1076 *1077 * @param collectionId ID of collection1078 * @param propertyKeys optionally filter the returned properties to only these keys1079 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1080 * @returns array of key-value pairs1081 */1082 async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1083 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1084 }10851086 async getCollectionOptions(collectionId: number) {1087 return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1088 }10891090 /**1091 * Deletes onchain properties from the collection.1092 *1093 * @param signer keyring of signer1094 * @param collectionId ID of collection1095 * @param propertyKeys array of property keys to delete1096 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1097 * @returns ```true``` if extrinsic success, otherwise ```false```1098 */1099 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1100 const result = await this.helper.executeExtrinsic(1101 signer,1102 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1103 true,1104 );11051106 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1107 }11081109 /**1110 * Changes the owner of the token.1111 *1112 * @param signer keyring of signer1113 * @param collectionId ID of collection1114 * @param tokenId ID of token1115 * @param addressObj address of a new owner1116 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1117 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1118 * @returns true if the token success, otherwise false1119 */1120 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1121 const result = await this.helper.executeExtrinsic(1122 signer,1123 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1124 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1125 );11261127 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1128 }11291130 /**1131 *1132 * Change ownership of a token(s) on behalf of the owner.1133 *1134 * @param signer keyring of signer1135 * @param collectionId ID of collection1136 * @param tokenId ID of token1137 * @param fromAddressObj address on behalf of which the token will be sent1138 * @param toAddressObj new token owner1139 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1140 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1141 * @returns true if the token success, otherwise false1142 */1143 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1144 const result = await this.helper.executeExtrinsic(1145 signer,1146 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1147 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1148 );1149 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1150 }11511152 /**1153 *1154 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1155 *1156 * @param signer keyring of signer1157 * @param collectionId ID of collection1158 * @param tokenId ID of token1159 * @param amount amount of tokens to be burned. For NFT must be set to 1n1160 * @example burnToken(aliceKeyring, 10, 5);1161 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1162 */1163 async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1164 const burnResult = await this.helper.executeExtrinsic(1165 signer,1166 'api.tx.unique.burnItem', [collectionId, tokenId, amount],1167 true, // `Unable to burn token for ${label}`,1168 );1169 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1170 if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1171 return burnedTokens.success;1172 }11731174 /**1175 * Destroys a concrete instance of NFT on behalf of the owner1176 *1177 * @param signer keyring of signer1178 * @param collectionId ID of collection1179 * @param tokenId ID of token1180 * @param fromAddressObj address on behalf of which the token will be burnt1181 * @param amount amount of tokens to be burned. For NFT must be set to 1n1182 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1183 * @returns ```true``` if extrinsic success, otherwise ```false```1184 */1185 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1186 const burnResult = await this.helper.executeExtrinsic(1187 signer,1188 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1189 true, // `Unable to burn token from for ${label}`,1190 );1191 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1192 return burnedTokens.success && burnedTokens.tokens.length > 0;1193 }11941195 /**1196 * Set, change, or remove approved address to transfer the ownership of the NFT.1197 *1198 * @param signer keyring of signer1199 * @param collectionId ID of collection1200 * @param tokenId ID of token1201 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1202 * @param amount amount of token to be approved. For NFT must be set to 1n1203 * @returns ```true``` if extrinsic success, otherwise ```false```1204 */1205 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1206 const approveResult = await this.helper.executeExtrinsic(1207 signer,1208 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1209 true, // `Unable to approve token for ${label}`,1210 );12111212 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1213 }12141215 /**1216 * Get the amount of token pieces approved to transfer or burn. Normally 0.1217 *1218 * @param collectionId ID of collection1219 * @param tokenId ID of token1220 * @param toAccountObj address which is approved to use token pieces1221 * @param fromAccountObj address which may have allowed the use of its owned tokens1222 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1223 * @returns number of approved to transfer pieces1224 */1225 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1226 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1227 }12281229 /**1230 * Get the last created token ID in a collection1231 *1232 * @param collectionId ID of collection1233 * @example getLastTokenId(10);1234 * @returns id of the last created token1235 */1236 async getLastTokenId(collectionId: number): Promise<number> {1237 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1238 }12391240 /**1241 * Check if token exists1242 *1243 * @param collectionId ID of collection1244 * @param tokenId ID of token1245 * @example doesTokenExist(10, 20);1246 * @returns true if the token exists, otherwise false1247 */1248 async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1249 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1250 }1251}12521253class NFTnRFT extends CollectionGroup {1254 /**1255 * Get tokens owned by account1256 *1257 * @param collectionId ID of collection1258 * @param addressObj tokens owner1259 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1260 * @returns array of token ids owned by account1261 */1262 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1263 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1264 }12651266 /**1267 * Get token data1268 *1269 * @param collectionId ID of collection1270 * @param tokenId ID of token1271 * @param propertyKeys optionally filter the token properties to only these keys1272 * @param blockHashAt optionally query the data at some block with this hash1273 * @example getToken(10, 5);1274 * @returns human readable token data1275 */1276 async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1277 properties: IProperty[];1278 owner: CrossAccountId;1279 normalizedOwner: CrossAccountId;1280 }| null> {1281 let tokenData;1282 if(typeof blockHashAt === 'undefined') {1283 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1284 }1285 else {1286 if(propertyKeys.length == 0) {1287 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1288 if(!collection) return null;1289 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1290 }1291 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1292 }1293 tokenData = tokenData.toHuman();1294 if (tokenData === null || tokenData.owner === null) return null;1295 const owner = {} as any;1296 for (const key of Object.keys(tokenData.owner)) {1297 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1298 ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1299 : tokenData.owner[key];1300 }1301 tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1302 return tokenData;1303 }13041305 /**1306 * Set permissions to change token properties1307 *1308 * @param signer keyring of signer1309 * @param collectionId ID of collection1310 * @param permissions permissions to change a property by the collection admin or token owner1311 * @example setTokenPropertyPermissions(1312 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1313 * )1314 * @returns true if extrinsic success otherwise false1315 */1316 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1317 const result = await this.helper.executeExtrinsic(1318 signer,1319 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1320 true,1321 );13221323 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1324 }13251326 /**1327 * Get token property permissions.1328 *1329 * @param collectionId ID of collection1330 * @param propertyKeys optionally filter the returned property permissions to only these keys1331 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1332 * @returns array of key-permission pairs1333 */1334 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1335 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1336 }13371338 /**1339 * Set token properties1340 *1341 * @param signer keyring of signer1342 * @param collectionId ID of collection1343 * @param tokenId ID of token1344 * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1345 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1346 * @returns ```true``` if extrinsic success, otherwise ```false```1347 */1348 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1349 const result = await this.helper.executeExtrinsic(1350 signer,1351 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1352 true,1353 );13541355 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1356 }13571358 /**1359 * Get properties, metadata assigned to a token.1360 *1361 * @param collectionId ID of collection1362 * @param tokenId ID of token1363 * @param propertyKeys optionally filter the returned properties to only these keys1364 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1365 * @returns array of key-value pairs1366 */1367 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1368 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1369 }13701371 /**1372 * Delete the provided properties of a token1373 * @param signer keyring of signer1374 * @param collectionId ID of collection1375 * @param tokenId ID of token1376 * @param propertyKeys property keys to be deleted1377 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1378 * @returns ```true``` if extrinsic success, otherwise ```false```1379 */1380 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1381 const result = await this.helper.executeExtrinsic(1382 signer,1383 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1384 true,1385 );13861387 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1388 }13891390 /**1391 * Mint new collection1392 *1393 * @param signer keyring of signer1394 * @param collectionOptions basic collection options and properties1395 * @param mode NFT or RFT type of a collection1396 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1397 * @returns object of the created collection1398 */1399 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1400 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1401 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1402 for (const key of ['name', 'description', 'tokenPrefix']) {1403 if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);1404 }1405 const creationResult = await this.helper.executeExtrinsic(1406 signer,1407 'api.tx.unique.createCollectionEx', [collectionOptions],1408 true, // errorLabel,1409 );1410 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1411 }14121413 getCollectionObject(_collectionId: number): any {1414 return null;1415 }14161417 getTokenObject(_collectionId: number, _tokenId: number): any {1418 return null;1419 }14201421 /**1422 * Tells whether the given `owner` approves the `operator`.1423 * @param collectionId ID of collection1424 * @param owner owner address1425 * @param operator operator addrees1426 * @returns true if operator is enabled1427 */1428 async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1429 return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1430 }14311432 /** Sets or unsets the approval of a given operator.1433 * The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1434 * @param operator Operator1435 * @param approved Should operator status be granted or revoked?1436 * @returns ```true``` if extrinsic success, otherwise ```false```1437 */1438 async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1439 const result = await this.helper.executeExtrinsic(1440 signer,1441 'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1442 true,1443 );1444 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1445 }1446}144714481449class NFTGroup extends NFTnRFT {1450 /**1451 * Get collection object1452 * @param collectionId ID of collection1453 * @example getCollectionObject(2);1454 * @returns instance of UniqueNFTCollection1455 */1456 getCollectionObject(collectionId: number): UniqueNFTCollection {1457 return new UniqueNFTCollection(collectionId, this.helper);1458 }14591460 /**1461 * Get token object1462 * @param collectionId ID of collection1463 * @param tokenId ID of token1464 * @example getTokenObject(10, 5);1465 * @returns instance of UniqueNFTToken1466 */1467 getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1468 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1469 }14701471 /**1472 * Get token's owner1473 * @param collectionId ID of collection1474 * @param tokenId ID of token1475 * @param blockHashAt optionally query the data at the block with this hash1476 * @example getTokenOwner(10, 5);1477 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1478 */1479 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1480 let owner;1481 if (typeof blockHashAt === 'undefined') {1482 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1483 } else {1484 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1485 }1486 return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1487 }14881489 /**1490 * Is token approved to transfer1491 * @param collectionId ID of collection1492 * @param tokenId ID of token1493 * @param toAccountObj address to be approved1494 * @returns ```true``` if extrinsic success, otherwise ```false```1495 */1496 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1497 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1498 }14991500 /**1501 * Changes the owner of the token.1502 *1503 * @param signer keyring of signer1504 * @param collectionId ID of collection1505 * @param tokenId ID of token1506 * @param addressObj address of a new owner1507 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1508 * @returns ```true``` if extrinsic success, otherwise ```false```1509 */1510 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1511 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1512 }15131514 /**1515 *1516 * Change ownership of a NFT on behalf of the owner.1517 *1518 * @param signer keyring of signer1519 * @param collectionId ID of collection1520 * @param tokenId ID of token1521 * @param fromAddressObj address on behalf of which the token will be sent1522 * @param toAddressObj new token owner1523 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1524 * @returns ```true``` if extrinsic success, otherwise ```false```1525 */1526 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1527 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1528 }15291530 /**1531 * Recursively find the address that owns the token1532 * @param collectionId ID of collection1533 * @param tokenId ID of token1534 * @param blockHashAt1535 * @example getTokenTopmostOwner(10, 5);1536 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1537 */1538 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1539 let owner;1540 if (typeof blockHashAt === 'undefined') {1541 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1542 } else {1543 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1544 }15451546 if (owner === null) return null;15471548 return owner.toHuman();1549 }15501551 /**1552 * Get tokens nested in the provided token1553 * @param collectionId ID of collection1554 * @param tokenId ID of token1555 * @param blockHashAt optionally query the data at the block with this hash1556 * @example getTokenChildren(10, 5);1557 * @returns tokens whose depth of nesting is <= 51558 */1559 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1560 let children;1561 if(typeof blockHashAt === 'undefined') {1562 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1563 } else {1564 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1565 }15661567 return children.toJSON().map((x: any) => {1568 return {collectionId: x.collection, tokenId: x.token};1569 });1570 }15711572 /**1573 * Nest one token into another1574 * @param signer keyring of signer1575 * @param tokenObj token to be nested1576 * @param rootTokenObj token to be parent1577 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1578 * @returns ```true``` if extrinsic success, otherwise ```false```1579 */1580 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1581 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1582 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1583 if(!result) {1584 throw Error('Unable to nest token!');1585 }1586 return result;1587 }15881589 /**1590 * Remove token from nested state1591 * @param signer keyring of signer1592 * @param tokenObj token to unnest1593 * @param rootTokenObj parent of a token1594 * @param toAddressObj address of a new token owner1595 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1596 * @returns ```true``` if extrinsic success, otherwise ```false```1597 */1598 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1599 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1600 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1601 if(!result) {1602 throw Error('Unable to unnest token!');1603 }1604 return result;1605 }16061607 /**1608 * Mint new collection1609 * @param signer keyring of signer1610 * @param collectionOptions Collection options1611 * @example1612 * mintCollection(aliceKeyring, {1613 * name: 'New',1614 * description: 'New collection',1615 * tokenPrefix: 'NEW',1616 * })1617 * @returns object of the created collection1618 */1619 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1620 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1621 }16221623 /**1624 * Mint new token1625 * @param signer keyring of signer1626 * @param data token data1627 * @returns created token object1628 */1629 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1630 const creationResult = await this.helper.executeExtrinsic(1631 signer,1632 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1633 nft: {1634 properties: data.properties,1635 },1636 }],1637 true,1638 );1639 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1640 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1641 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1642 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1643 }16441645 /**1646 * Mint multiple NFT tokens1647 * @param signer keyring of signer1648 * @param collectionId ID of collection1649 * @param tokens array of tokens with owner and properties1650 * @example1651 * mintMultipleTokens(aliceKeyring, 10, [{1652 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1653 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1654 * },{1655 * owner: {Ethereum: "0x9F0583DbB855d..."},1656 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1657 * }]);1658 * @returns ```true``` if extrinsic success, otherwise ```false```1659 */1660 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1661 const creationResult = await this.helper.executeExtrinsic(1662 signer,1663 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1664 true,1665 );1666 const collection = this.getCollectionObject(collectionId);1667 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1668 }16691670 /**1671 * Mint multiple NFT tokens with one owner1672 * @param signer keyring of signer1673 * @param collectionId ID of collection1674 * @param owner tokens owner1675 * @param tokens array of tokens with owner and properties1676 * @example1677 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1678 * properties: [{1679 * key: "gender",1680 * value: "female",1681 * },{1682 * key: "age",1683 * value: "33",1684 * }],1685 * }]);1686 * @returns array of newly created tokens1687 */1688 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise<UniqueNFToken[]> {1689 const rawTokens = [];1690 for (const token of tokens) {1691 const raw = {NFT: {properties: token.properties}};1692 rawTokens.push(raw);1693 }1694 const creationResult = await this.helper.executeExtrinsic(1695 signer,1696 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1697 true,1698 );1699 const collection = this.getCollectionObject(collectionId);1700 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1701 }17021703 /**1704 * Set, change, or remove approved address to transfer the ownership of the NFT.1705 *1706 * @param signer keyring of signer1707 * @param collectionId ID of collection1708 * @param tokenId ID of token1709 * @param toAddressObj address to approve1710 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1711 * @returns ```true``` if extrinsic success, otherwise ```false```1712 */1713 approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId) {1714 return super.approveToken(signer, collectionId, tokenId, toAddressObj, 1n);1715 }1716}171717181719class RFTGroup extends NFTnRFT {1720 /**1721 * Get collection object1722 * @param collectionId ID of collection1723 * @example getCollectionObject(2);1724 * @returns instance of UniqueRFTCollection1725 */1726 getCollectionObject(collectionId: number): UniqueRFTCollection {1727 return new UniqueRFTCollection(collectionId, this.helper);1728 }17291730 /**1731 * Get token object1732 * @param collectionId ID of collection1733 * @param tokenId ID of token1734 * @example getTokenObject(10, 5);1735 * @returns instance of UniqueNFTToken1736 */1737 getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1738 return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1739 }17401741 /**1742 * Get top 10 token owners with the largest number of pieces1743 * @param collectionId ID of collection1744 * @param tokenId ID of token1745 * @example getTokenTop10Owners(10, 5);1746 * @returns array of top 10 owners1747 */1748 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1749 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1750 }17511752 /**1753 * Get number of pieces owned by address1754 * @param collectionId ID of collection1755 * @param tokenId ID of token1756 * @param addressObj address token owner1757 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1758 * @returns number of pieces ownerd by address1759 */1760 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1761 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1762 }17631764 /**1765 * Transfer pieces of token to another address1766 * @param signer keyring of signer1767 * @param collectionId ID of collection1768 * @param tokenId ID of token1769 * @param addressObj address of a new owner1770 * @param amount number of pieces to be transfered1771 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1772 * @returns ```true``` if extrinsic success, otherwise ```false```1773 */1774 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {1775 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1776 }17771778 /**1779 * Change ownership of some pieces of RFT on behalf of the owner.1780 * @param signer keyring of signer1781 * @param collectionId ID of collection1782 * @param tokenId ID of token1783 * @param fromAddressObj address on behalf of which the token will be sent1784 * @param toAddressObj new token owner1785 * @param amount number of pieces to be transfered1786 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1787 * @returns ```true``` if extrinsic success, otherwise ```false```1788 */1789 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1790 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1791 }17921793 /**1794 * Mint new collection1795 * @param signer keyring of signer1796 * @param collectionOptions Collection options1797 * @example1798 * mintCollection(aliceKeyring, {1799 * name: 'New',1800 * description: 'New collection',1801 * tokenPrefix: 'NEW',1802 * })1803 * @returns object of the created collection1804 */1805 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1806 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1807 }18081809 /**1810 * Mint new token1811 * @param signer keyring of signer1812 * @param data token data1813 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1814 * @returns created token object1815 */1816 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1817 const creationResult = await this.helper.executeExtrinsic(1818 signer,1819 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1820 refungible: {1821 pieces: data.pieces,1822 properties: data.properties,1823 },1824 }],1825 true,1826 );1827 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1828 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1829 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1830 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1831 }18321833 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1834 throw Error('Not implemented');1835 const creationResult = await this.helper.executeExtrinsic(1836 signer,1837 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1838 true, // `Unable to mint RFT tokens for ${label}`,1839 );1840 const collection = this.getCollectionObject(collectionId);1841 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1842 }18431844 /**1845 * Mint multiple RFT tokens with one owner1846 * @param signer keyring of signer1847 * @param collectionId ID of collection1848 * @param owner tokens owner1849 * @param tokens array of tokens with properties and pieces1850 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1851 * @returns array of newly created RFT tokens1852 */1853 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise<UniqueRFToken[]> {1854 const rawTokens = [];1855 for (const token of tokens) {1856 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1857 rawTokens.push(raw);1858 }1859 const creationResult = await this.helper.executeExtrinsic(1860 signer,1861 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1862 true,1863 );1864 const collection = this.getCollectionObject(collectionId);1865 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1866 }18671868 /**1869 * Destroys a concrete instance of RFT.1870 * @param signer keyring of signer1871 * @param collectionId ID of collection1872 * @param tokenId ID of token1873 * @param amount number of pieces to be burnt1874 * @example burnToken(aliceKeyring, 10, 5);1875 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1876 */1877 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<boolean> {1878 return await super.burnToken(signer, collectionId, tokenId, amount);1879 }18801881 /**1882 * Destroys a concrete instance of RFT on behalf of the owner.1883 * @param signer keyring of signer1884 * @param collectionId ID of collection1885 * @param tokenId ID of token1886 * @param fromAddressObj address on behalf of which the token will be burnt1887 * @param amount number of pieces to be burnt1888 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)1889 * @returns ```true``` if extrinsic success, otherwise ```false```1890 */1891 async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {1892 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);1893 }18941895 /**1896 * Set, change, or remove approved address to transfer the ownership of the RFT.1897 *1898 * @param signer keyring of signer1899 * @param collectionId ID of collection1900 * @param tokenId ID of token1901 * @param toAddressObj address to approve1902 * @param amount number of pieces to be approved1903 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1904 * @returns true if the token success, otherwise false1905 */1906 approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {1907 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1908 }19091910 /**1911 * Get total number of pieces1912 * @param collectionId ID of collection1913 * @param tokenId ID of token1914 * @example getTokenTotalPieces(10, 5);1915 * @returns number of pieces1916 */1917 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1918 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1919 }19201921 /**1922 * Change number of token pieces. Signer must be the owner of all token pieces.1923 * @param signer keyring of signer1924 * @param collectionId ID of collection1925 * @param tokenId ID of token1926 * @param amount new number of pieces1927 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1928 * @returns true if the repartion was success, otherwise false1929 */1930 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {1931 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1932 const repartitionResult = await this.helper.executeExtrinsic(1933 signer,1934 'api.tx.unique.repartition', [collectionId, tokenId, amount],1935 true,1936 );1937 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');1938 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');1939 }1940}194119421943class FTGroup extends CollectionGroup {1944 /**1945 * Get collection object1946 * @param collectionId ID of collection1947 * @example getCollectionObject(2);1948 * @returns instance of UniqueFTCollection1949 */1950 getCollectionObject(collectionId: number): UniqueFTCollection {1951 return new UniqueFTCollection(collectionId, this.helper);1952 }19531954 /**1955 * Mint new fungible collection1956 * @param signer keyring of signer1957 * @param collectionOptions Collection options1958 * @param decimalPoints number of token decimals1959 * @example1960 * mintCollection(aliceKeyring, {1961 * name: 'New',1962 * description: 'New collection',1963 * tokenPrefix: 'NEW',1964 * }, 18)1965 * @returns newly created fungible collection1966 */1967 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {1968 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1969 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1970 collectionOptions.mode = {fungible: decimalPoints};1971 for (const key of ['name', 'description', 'tokenPrefix']) {1972 if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);1973 }1974 const creationResult = await this.helper.executeExtrinsic(1975 signer,1976 'api.tx.unique.createCollectionEx', [collectionOptions],1977 true,1978 );1979 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1980 }19811982 /**1983 * Mint tokens1984 * @param signer keyring of signer1985 * @param collectionId ID of collection1986 * @param owner address owner of new tokens1987 * @param amount amount of tokens to be meanted1988 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1989 * @returns ```true``` if extrinsic success, otherwise ```false```1990 */1991 async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {1992 const creationResult = await this.helper.executeExtrinsic(1993 signer,1994 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1995 fungible: {1996 value: amount,1997 },1998 }],1999 true, // `Unable to mint fungible tokens for ${label}`,2000 );2001 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2002 }20032004 /**2005 * Mint multiple Fungible tokens with one owner2006 * @param signer keyring of signer2007 * @param collectionId ID of collection2008 * @param owner tokens owner2009 * @param tokens array of tokens with properties and pieces2010 * @returns ```true``` if extrinsic success, otherwise ```false```2011 */2012 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: {value: bigint}[], owner: ICrossAccountId): Promise<boolean> {2013 const rawTokens = [];2014 for (const token of tokens) {2015 const raw = {Fungible: {Value: token.value}};2016 rawTokens.push(raw);2017 }2018 const creationResult = await this.helper.executeExtrinsic(2019 signer,2020 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2021 true,2022 );2023 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2024 }20252026 /**2027 * Get the top 10 owners with the largest balance for the Fungible collection2028 * @param collectionId ID of collection2029 * @example getTop10Owners(10);2030 * @returns array of ```ICrossAccountId```2031 */2032 async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2033 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2034 }20352036 /**2037 * Get account balance2038 * @param collectionId ID of collection2039 * @param addressObj address of owner2040 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2041 * @returns amount of fungible tokens owned by address2042 */2043 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2044 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2045 }20462047 /**2048 * Transfer tokens to address2049 * @param signer keyring of signer2050 * @param collectionId ID of collection2051 * @param toAddressObj address recipient2052 * @param amount amount of tokens to be sent2053 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2054 * @returns ```true``` if extrinsic success, otherwise ```false```2055 */2056 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2057 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2058 }20592060 /**2061 * Transfer some tokens on behalf of the owner.2062 * @param signer keyring of signer2063 * @param collectionId ID of collection2064 * @param fromAddressObj address on behalf of which tokens will be sent2065 * @param toAddressObj address where token to be sent2066 * @param amount number of tokens to be sent2067 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2068 * @returns ```true``` if extrinsic success, otherwise ```false```2069 */2070 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {2071 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2072 }20732074 /**2075 * Destroy some amount of tokens2076 * @param signer keyring of signer2077 * @param collectionId ID of collection2078 * @param amount amount of tokens to be destroyed2079 * @example burnTokens(aliceKeyring, 10, 1000n);2080 * @returns ```true``` if extrinsic success, otherwise ```false```2081 */2082 async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise<boolean> {2083 return await super.burnToken(signer, collectionId, 0, amount);2084 }20852086 /**2087 * Burn some tokens on behalf of the owner.2088 * @param signer keyring of signer2089 * @param collectionId ID of collection2090 * @param fromAddressObj address on behalf of which tokens will be burnt2091 * @param amount amount of tokens to be burnt2092 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2093 * @returns ```true``` if extrinsic success, otherwise ```false```2094 */2095 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {2096 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2097 }20982099 /**2100 * Get total collection supply2101 * @param collectionId2102 * @returns2103 */2104 async getTotalPieces(collectionId: number): Promise<bigint> {2105 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2106 }21072108 /**2109 * Set, change, or remove approved address to transfer tokens.2110 *2111 * @param signer keyring of signer2112 * @param collectionId ID of collection2113 * @param toAddressObj address to be approved2114 * @param amount amount of tokens to be approved2115 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2116 * @returns ```true``` if extrinsic success, otherwise ```false```2117 */2118 approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=1n) {2119 return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2120 }21212122 /**2123 * Get amount of fungible tokens approved to transfer2124 * @param collectionId ID of collection2125 * @param fromAddressObj owner of tokens2126 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2127 * @returns number of tokens approved for the transfer2128 */2129 getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2130 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2131 }2132}213321342135class ChainGroup extends HelperGroup<ChainHelperBase> {2136 /**2137 * Get system properties of a chain2138 * @example getChainProperties();2139 * @returns ss58Format, token decimals, and token symbol2140 */2141 getChainProperties(): IChainProperties {2142 const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2143 return {2144 ss58Format: properties.ss58Format.toJSON(),2145 tokenDecimals: properties.tokenDecimals.toJSON(),2146 tokenSymbol: properties.tokenSymbol.toJSON(),2147 };2148 }21492150 /**2151 * Get chain header2152 * @example getLatestBlockNumber();2153 * @returns the number of the last block2154 */2155 async getLatestBlockNumber(): Promise<number> {2156 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2157 }21582159 /**2160 * Get block hash by block number2161 * @param blockNumber number of block2162 * @example getBlockHashByNumber(12345);2163 * @returns hash of a block2164 */2165 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2166 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2167 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2168 return blockHash;2169 }21702171 // TODO add docs2172 async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2173 const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2174 if (!blockHash) return null;2175 return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2176 }21772178 /**2179 * Get account nonce2180 * @param address substrate address2181 * @example getNonce("5GrwvaEF5zXb26Fz...");2182 * @returns number, account's nonce2183 */2184 async getNonce(address: TSubstrateAccount): Promise<number> {2185 return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2186 }2187}21882189class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2190 /**2191 * Get substrate address balance2192 * @param address substrate address2193 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2194 * @returns amount of tokens on address2195 */2196 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2197 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2198 }21992200 /**2201 * Transfer tokens to substrate address2202 * @param signer keyring of signer2203 * @param address substrate address of a recipient2204 * @param amount amount of tokens to be transfered2205 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2206 * @returns ```true``` if extrinsic success, otherwise ```false```2207 */2208 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2209 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/);22102211 let transfer = {from: null, to: null, amount: 0n} as any;2212 result.result.events.forEach(({event: {data, method, section}}) => {2213 if ((section === 'balances') && (method === 'Transfer')) {2214 transfer = {2215 from: this.helper.address.normalizeSubstrate(data[0]),2216 to: this.helper.address.normalizeSubstrate(data[1]),2217 amount: BigInt(data[2]),2218 };2219 }2220 });2221 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2222 && this.helper.address.normalizeSubstrate(address) === transfer.to2223 && BigInt(amount) === transfer.amount;2224 return isSuccess;2225 }22262227 /**2228 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2229 * @param address substrate address2230 * @returns2231 */2232 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2233 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2234 return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2235 }2236}22372238class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2239 /**2240 * Get ethereum address balance2241 * @param address ethereum address2242 * @example getEthereum("0x9F0583DbB855d...")2243 * @returns amount of tokens on address2244 */2245 async getEthereum(address: TEthereumAccount): Promise<bigint> {2246 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2247 }22482249 /**2250 * Transfer tokens to address2251 * @param signer keyring of signer2252 * @param address Ethereum address of a recipient2253 * @param amount amount of tokens to be transfered2254 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2255 * @returns ```true``` if extrinsic success, otherwise ```false```2256 */2257 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2258 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);22592260 let transfer = {from: null, to: null, amount: 0n} as any;2261 result.result.events.forEach(({event: {data, method, section}}) => {2262 if ((section === 'balances') && (method === 'Transfer')) {2263 transfer = {2264 from: data[0].toString(),2265 to: data[1].toString(),2266 amount: BigInt(data[2]),2267 };2268 }2269 });2270 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2271 && address === transfer.to2272 && BigInt(amount) === transfer.amount;2273 return isSuccess;2274 }2275}22762277class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2278 subBalanceGroup: SubstrateBalanceGroup<T>;2279 ethBalanceGroup: EthereumBalanceGroup<T>;22802281 constructor(helper: T) {2282 super(helper);2283 this.subBalanceGroup = new SubstrateBalanceGroup(helper);2284 this.ethBalanceGroup = new EthereumBalanceGroup(helper);2285 }22862287 getCollectionCreationPrice(): bigint {2288 return 2n * this.getOneTokenNominal();2289 }2290 /**2291 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2292 * @example getOneTokenNominal()2293 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2294 */2295 getOneTokenNominal(): bigint {2296 const chainProperties = this.helper.chain.getChainProperties();2297 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2298 }22992300 /**2301 * Get substrate address balance2302 * @param address substrate address2303 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2304 * @returns amount of tokens on address2305 */2306 getSubstrate(address: TSubstrateAccount): Promise<bigint> {2307 return this.subBalanceGroup.getSubstrate(address);2308 }23092310 /**2311 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2312 * @param address substrate address2313 * @returns2314 */2315 getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2316 return this.subBalanceGroup.getSubstrateFull(address);2317 }23182319 /**2320 * Get ethereum address balance2321 * @param address ethereum address2322 * @example getEthereum("0x9F0583DbB855d...")2323 * @returns amount of tokens on address2324 */2325 getEthereum(address: TEthereumAccount): Promise<bigint> {2326 return this.ethBalanceGroup.getEthereum(address);2327 }23282329 /**2330 * Transfer tokens to substrate address2331 * @param signer keyring of signer2332 * @param address substrate address of a recipient2333 * @param amount amount of tokens to be transfered2334 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2335 * @returns ```true``` if extrinsic success, otherwise ```false```2336 */2337 transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2338 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2339 }23402341 async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2342 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);23432344 let transfer = {from: null, to: null, amount: 0n} as any;2345 result.result.events.forEach(({event: {data, method, section}}) => {2346 if ((section === 'balances') && (method === 'Transfer')) {2347 transfer = {2348 from: this.helper.address.normalizeSubstrate(data[0]),2349 to: this.helper.address.normalizeSubstrate(data[1]),2350 amount: BigInt(data[2]),2351 };2352 }2353 });2354 let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2355 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2356 isSuccess = isSuccess && BigInt(amount) === transfer.amount;2357 return isSuccess;2358 }2359}23602361class AddressGroup extends HelperGroup<ChainHelperBase> {2362 /**2363 * Normalizes the address to the specified ss58 format, by default ```42```.2364 * @param address substrate address2365 * @param ss58Format format for address conversion, by default ```42```2366 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2367 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2368 */2369 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2370 return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2371 }23722373 /**2374 * Get address in the connected chain format2375 * @param address substrate address2376 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2377 * @returns address in chain format2378 */2379 normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2380 return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2381 }23822383 /**2384 * Get substrate mirror of an ethereum address2385 * @param ethAddress ethereum address2386 * @param toChainFormat false for normalized account2387 * @example ethToSubstrate('0x9F0583DbB855d...')2388 * @returns substrate mirror of a provided ethereum address2389 */2390 ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): TSubstrateAccount {2391 return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2392 }23932394 /**2395 * Get ethereum mirror of a substrate address2396 * @param subAddress substrate account2397 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2398 * @returns ethereum mirror of a provided substrate address2399 */2400 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2401 return CrossAccountId.translateSubToEth(subAddress);2402 }24032404 /**2405 * Encode key to substrate address2406 * @param key key for encoding address2407 * @param ss58Format prefix for encoding to the address of the corresponding network2408 * @returns encoded substrate address2409 */2410 encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format = 42): string {2411 const u8a :Uint8Array = typeof key === 'string'2412 ? hexToU8a(key)2413 : typeof key === 'bigint'2414 ? hexToU8a(key.toString(16))2415 : key;2416 2417 if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2418 throw new Error(`ss58Format is not valid, received ${typeofss58Format} "${ss58Format}"`);2419 }2420 2421 const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2422 if (!allowedDecodedLengths.includes(u8a.length)) {2423 throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2424 }2425 2426 const u8aPrefix = ss58Format < 642427 ? new Uint8Array([ss58Format])2428 : new Uint8Array([2429 ((ss58Format & 0xfc) >> 2) | 0x40,2430 (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2431 ]);24322433 const input = u8aConcat(u8aPrefix, u8a);2434 2435 return base58Encode(u8aConcat(2436 input,2437 blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2438 ));2439 }24402441 /**2442 * Restore substrate address from bigint representation2443 * @param number decimal representation of substrate address2444 * @returns substrate address2445 */2446 restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2447 if (this.helper.api === null) {2448 throw 'Not connected';2449 }2450 const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2451 if (res === undefined || res === null) {2452 throw 'Restore address error';2453 }2454 return res.toString();2455 }24562457 /**2458 * Convert etherium cross account id to substrate cross account id2459 * @param ethCrossAccount etherium cross account2460 * @returns substrate cross account id2461 */2462 convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2463 if (ethCrossAccount.sub === '0') {2464 return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2465 }2466 2467 const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2468 return {Substrate: ss58};2469 }24702471 paraSiblingSovereignAccount(paraid: number) {2472 // We are getting a *sibling* parachain sovereign account,2473 // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2474 const siblingPrefix = '0x7369626c';24752476 const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2477 const suffix = '000000000000000000000000000000000000000000000000';24782479 return siblingPrefix + encodedParaId + suffix;2480 }2481}24822483class StakingGroup extends HelperGroup<UniqueHelper> {2484 /**2485 * Stake tokens for App Promotion2486 * @param signer keyring of signer2487 * @param amountToStake amount of tokens to stake2488 * @param label extra label for log2489 * @returns2490 */2491 async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2492 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2493 const _stakeResult = await this.helper.executeExtrinsic(2494 signer, 'api.tx.appPromotion.stake',2495 [amountToStake], true,2496 );2497 // TODO extract info from stakeResult2498 return true;2499 }25002501 /**2502 * Unstake tokens for App Promotion2503 * @param signer keyring of signer2504 * @param amountToUnstake amount of tokens to unstake2505 * @param label extra label for log2506 * @returns block number where balances will be unlocked2507 */2508 async unstake(signer: TSigner, label?: string): Promise<number> {2509 if(typeof label === 'undefined') label = `${signer.address}`;2510 const _unstakeResult = await this.helper.executeExtrinsic(2511 signer, 'api.tx.appPromotion.unstake',2512 [], true,2513 );2514 // TODO extract block number fron events2515 return 1;2516 }25172518 /**2519 * Get total staked amount for address2520 * @param address substrate or ethereum address2521 * @returns total staked amount2522 */2523 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2524 if (address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2525 return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2526 }25272528 /**2529 * Get total staked per block2530 * @param address substrate or ethereum address2531 * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2532 */2533 async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2534 const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2535 return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => {2536 return {2537 block: block.toBigInt(),2538 amount: amount.toBigInt(),2539 };2540 });2541 }25422543 /**2544 * Get total pending unstake amount for address2545 * @param address substrate or ethereum address2546 * @returns total pending unstake amount2547 */2548 async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2549 return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2550 }25512552 /**2553 * Get pending unstake amount per block for address2554 * @param address substrate or ethereum address2555 * @returns array of pending stakes. `block` – the number of the block in which the unstake was made. `amount` - the number of tokens unstaked in the block2556 */2557 async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2558 const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2559 const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => {2560 return {2561 block: block.toBigInt(),2562 amount: amount.toBigInt(),2563 };2564 });2565 return result;2566 }2567}25682569class SchedulerGroup extends HelperGroup<UniqueHelper> {2570 constructor(helper: UniqueHelper) {2571 super(helper);2572 }25732574 cancelScheduled(signer: TSigner, scheduledId: string) {2575 return this.helper.executeExtrinsic(2576 signer,2577 'api.tx.scheduler.cancelNamed',2578 [scheduledId],2579 true,2580 );2581 }25822583 changePriority(signer: TSigner, scheduledId: string, priority: number) {2584 return this.helper.executeExtrinsic(2585 signer,2586 'api.tx.scheduler.changeNamedPriority',2587 [scheduledId, priority],2588 true,2589 );2590 }25912592 scheduleAt<T extends UniqueHelper>(2593 executionBlockNumber: number,2594 options: ISchedulerOptions = {},2595 ) {2596 return this.schedule<T>('schedule', executionBlockNumber, options);2597 }25982599 scheduleAfter<T extends UniqueHelper>(2600 blocksBeforeExecution: number,2601 options: ISchedulerOptions = {},2602 ) {2603 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2604 }26052606 schedule<T extends UniqueHelper>(2607 scheduleFn: 'schedule' | 'scheduleAfter',2608 blocksNum: number,2609 options: ISchedulerOptions = {},2610 ) {2611 // eslint-disable-next-line @typescript-eslint/naming-convention2612 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2613 return this.helper.clone(ScheduledHelperType, {2614 scheduleFn,2615 blocksNum,2616 options,2617 }) as T;2618 }2619}26202621class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {2622 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2623 await this.helper.executeExtrinsic(2624 signer,2625 'api.tx.foreignAssets.registerForeignAsset',2626 [ownerAddress, location, metadata],2627 true,2628 );2629 }26302631 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {2632 await this.helper.executeExtrinsic(2633 signer,2634 'api.tx.foreignAssets.updateForeignAsset',2635 [foreignAssetId, location, metadata],2636 true,2637 );2638 }2639}26402641class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {2642 palletName: string;26432644 constructor(helper: T, palletName: string) {2645 super(helper);26462647 this.palletName = palletName;2648 }26492650 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {2651 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);2652 }2653}26542655class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2656 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {2657 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);2658 }26592660 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {2661 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);2662 }26632664 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: number) {2665 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);2666 }2667}26682669class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {2670 async accounts(address: string, currencyId: any) {2671 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;2672 return BigInt(free);2673 }2674}26752676class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {2677 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {2678 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);2679 }26802681 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {2682 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);2683 }26842685 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {2686 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);2687 }26882689 async account(assetId: string | number, address: string) {2690 const accountAsset = (2691 await this.helper.callRpc('api.query.assets.account', [assetId, address])2692 ).toJSON()! as any;26932694 if (accountAsset !== null) {2695 return BigInt(accountAsset['balance']);2696 } else {2697 return null;2698 }2699 }2700}27012702class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {2703 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {2704 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);2705 }2706}27072708class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {2709 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {2710 const apiPrefix = 'api.tx.assetManager.';27112712 const registerTx = this.helper.constructApiCall(2713 apiPrefix + 'registerForeignAsset',2714 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],2715 );27162717 const setUnitsTx = this.helper.constructApiCall(2718 apiPrefix + 'setAssetUnitsPerSecond',2719 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],2720 );27212722 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);2723 const encodedProposal = batchCall?.method.toHex() || '';2724 return encodedProposal;2725 }27262727 async assetTypeId(location: any) {2728 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);2729 }2730}27312732class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {2733 async notePreimage(signer: TSigner, encodedProposal: string) {2734 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);2735 }27362737 externalProposeMajority(proposalHash: string) {2738 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);2739 }27402741 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {2742 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);2743 }27442745 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {2746 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);2747 }2748}27492750class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {2751 collective: string;27522753 constructor(helper: MoonbeamHelper, collective: string) {2754 super(helper);27552756 this.collective = collective;2757 }27582759 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {2760 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);2761 }27622763 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {2764 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);2765 }27662767 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {2768 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);2769 }27702771 async proposalCount() {2772 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));2773 }2774}27752776export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;2777export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;27782779export class UniqueHelper extends ChainHelperBase {2780 balance: BalanceGroup<UniqueHelper>;2781 collection: CollectionGroup;2782 nft: NFTGroup;2783 rft: RFTGroup;2784 ft: FTGroup;2785 staking: StakingGroup;2786 scheduler: SchedulerGroup;2787 foreignAssets: ForeignAssetsGroup;2788 xcm: XcmGroup<UniqueHelper>;2789 xTokens: XTokensGroup<UniqueHelper>;2790 tokens: TokensGroup<UniqueHelper>;27912792 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2793 super(logger, options.helperBase ?? UniqueHelper);27942795 this.balance = new BalanceGroup(this);2796 this.collection = new CollectionGroup(this);2797 this.nft = new NFTGroup(this);2798 this.rft = new RFTGroup(this);2799 this.ft = new FTGroup(this);2800 this.staking = new StakingGroup(this);2801 this.scheduler = new SchedulerGroup(this);2802 this.foreignAssets = new ForeignAssetsGroup(this);2803 this.xcm = new XcmGroup(this, 'polkadotXcm');2804 this.xTokens = new XTokensGroup(this);2805 this.tokens = new TokensGroup(this);2806 }28072808 getSudo<T extends UniqueHelper>() {2809 // eslint-disable-next-line @typescript-eslint/naming-convention2810 const SudoHelperType = SudoHelper(this.helperBase);2811 return this.clone(SudoHelperType) as T;2812 }2813}28142815export class XcmChainHelper extends ChainHelperBase {2816 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {2817 const wsProvider = new WsProvider(wsEndpoint);2818 this.api = new ApiPromise({2819 provider: wsProvider,2820 });2821 await this.api.isReadyOrError;2822 this.network = await UniqueHelper.detectNetwork(this.api);2823 }2824}28252826export class RelayHelper extends XcmChainHelper {2827 xcm: XcmGroup<RelayHelper>;28282829 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2830 super(logger, options.helperBase ?? RelayHelper);28312832 this.xcm = new XcmGroup(this, 'xcmPallet');2833 }2834}28352836export class WestmintHelper extends XcmChainHelper {2837 balance: SubstrateBalanceGroup<WestmintHelper>;2838 xcm: XcmGroup<WestmintHelper>;2839 assets: AssetsGroup<WestmintHelper>;2840 xTokens: XTokensGroup<WestmintHelper>;28412842 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2843 super(logger, options.helperBase ?? WestmintHelper);28442845 this.balance = new SubstrateBalanceGroup(this);2846 this.xcm = new XcmGroup(this, 'polkadotXcm');2847 this.assets = new AssetsGroup(this);2848 this.xTokens = new XTokensGroup(this);2849 }2850}28512852export class MoonbeamHelper extends XcmChainHelper {2853 balance: EthereumBalanceGroup<MoonbeamHelper>;2854 assetManager: MoonbeamAssetManagerGroup;2855 assets: AssetsGroup<MoonbeamHelper>;2856 xTokens: XTokensGroup<MoonbeamHelper>;2857 democracy: MoonbeamDemocracyGroup;2858 collective: {2859 council: MoonbeamCollectiveGroup,2860 techCommittee: MoonbeamCollectiveGroup,2861 };28622863 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2864 super(logger, options.helperBase ?? MoonbeamHelper);28652866 this.balance = new EthereumBalanceGroup(this);2867 this.assetManager = new MoonbeamAssetManagerGroup(this);2868 this.assets = new AssetsGroup(this);2869 this.xTokens = new XTokensGroup(this);2870 this.democracy = new MoonbeamDemocracyGroup(this);2871 this.collective = {2872 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),2873 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),2874 };2875 }2876}28772878export class AcalaHelper extends XcmChainHelper {2879 balance: SubstrateBalanceGroup<AcalaHelper>;2880 assetRegistry: AcalaAssetRegistryGroup;2881 xTokens: XTokensGroup<AcalaHelper>;2882 tokens: TokensGroup<AcalaHelper>;28832884 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2885 super(logger, options.helperBase ?? AcalaHelper);28862887 this.balance = new SubstrateBalanceGroup(this);2888 this.assetRegistry = new AcalaAssetRegistryGroup(this);2889 this.xTokens = new XTokensGroup(this);2890 this.tokens = new TokensGroup(this);2891 }28922893 getSudo<T extends AcalaHelper>() {2894 // eslint-disable-next-line @typescript-eslint/naming-convention2895 const SudoHelperType = SudoHelper(this.helperBase);2896 return this.clone(SudoHelperType) as T;2897 }2898}28992900// eslint-disable-next-line @typescript-eslint/naming-convention2901function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2902 return class extends Base {2903 scheduleFn: 'schedule' | 'scheduleAfter';2904 blocksNum: number;2905 options: ISchedulerOptions;29062907 constructor(...args: any[]) {2908 const logger = args[0] as ILogger;2909 const options = args[1] as {2910 scheduleFn: 'schedule' | 'scheduleAfter',2911 blocksNum: number,2912 options: ISchedulerOptions2913 };29142915 super(logger);29162917 this.scheduleFn = options.scheduleFn;2918 this.blocksNum = options.blocksNum;2919 this.options = options.options;2920 }29212922 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {2923 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);2924 2925 const mandatorySchedArgs = [2926 this.blocksNum,2927 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,2928 this.options.priority ?? null,2929 scheduledTx,2930 ];2931 2932 let schedArgs;2933 let scheduleFn;29342935 if (this.options.scheduledId) {2936 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];29372938 if (this.scheduleFn == 'schedule') {2939 scheduleFn = 'scheduleNamed';2940 } else if (this.scheduleFn == 'scheduleAfter') {2941 scheduleFn = 'scheduleNamedAfter';2942 }2943 } else {2944 schedArgs = mandatorySchedArgs;2945 scheduleFn = this.scheduleFn;2946 }29472948 const extrinsic = 'api.tx.scheduler.' + scheduleFn;29492950 return super.executeExtrinsic(2951 sender,2952 extrinsic,2953 schedArgs,2954 expectSuccess,2955 );2956 }2957 };2958}29592960// eslint-disable-next-line @typescript-eslint/naming-convention2961function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {2962 return class extends Base {2963 constructor(...args: any[]) {2964 super(...args);2965 }29662967 executeExtrinsic (2968 sender: IKeyringPair,2969 extrinsic: string,2970 params: any[],2971 expectSuccess?: boolean,2972 ): Promise<ITransactionResult> {2973 const call = this.constructApiCall(extrinsic, params);2974 return super.executeExtrinsic(2975 sender,2976 'api.tx.sudo.sudo',2977 [call],2978 expectSuccess,2979 );2980 }2981 };2982}29832984export class UniqueBaseCollection {2985 helper: UniqueHelper;2986 collectionId: number;29872988 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2989 this.collectionId = collectionId;2990 this.helper = uniqueHelper;2991 }29922993 async getData() {2994 return await this.helper.collection.getData(this.collectionId);2995 }29962997 async getLastTokenId() {2998 return await this.helper.collection.getLastTokenId(this.collectionId);2999 }30003001 async doesTokenExist(tokenId: number) {3002 return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);3003 }30043005 async getAdmins() {3006 return await this.helper.collection.getAdmins(this.collectionId);3007 }30083009 async getAllowList() {3010 return await this.helper.collection.getAllowList(this.collectionId);3011 }30123013 async getEffectiveLimits() {3014 return await this.helper.collection.getEffectiveLimits(this.collectionId);3015 }30163017 async getProperties(propertyKeys?: string[] | null) {3018 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3019 }30203021 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3022 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3023 }30243025 async getOptions() {3026 return await this.helper.collection.getCollectionOptions(this.collectionId);3027 }30283029 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {3030 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);3031 }30323033 async confirmSponsorship(signer: TSigner) {3034 return await this.helper.collection.confirmSponsorship(signer, this.collectionId);3035 }30363037 async removeSponsor(signer: TSigner) {3038 return await this.helper.collection.removeSponsor(signer, this.collectionId);3039 }30403041 async setLimits(signer: TSigner, limits: ICollectionLimits) {3042 return await this.helper.collection.setLimits(signer, this.collectionId, limits);3043 }30443045 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {3046 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);3047 }30483049 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3050 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);3051 }30523053 async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {3054 return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);3055 }30563057 async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {3058 return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);3059 }30603061 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3062 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);3063 }30643065 async setProperties(signer: TSigner, properties: IProperty[]) {3066 return await this.helper.collection.setProperties(signer, this.collectionId, properties);3067 }30683069 async deleteProperties(signer: TSigner, propertyKeys: string[]) {3070 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);3071 }30723073 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {3074 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);3075 }30763077 async enableNesting(signer: TSigner, permissions: INestingPermissions) {3078 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);3079 }30803081 async disableNesting(signer: TSigner) {3082 return await this.helper.collection.disableNesting(signer, this.collectionId);3083 }30843085 async burn(signer: TSigner) {3086 return await this.helper.collection.burn(signer, this.collectionId);3087 }30883089 scheduleAt<T extends UniqueHelper>(3090 executionBlockNumber: number,3091 options: ISchedulerOptions = {},3092 ) {3093 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3094 return new UniqueBaseCollection(this.collectionId, scheduledHelper);3095 }30963097 scheduleAfter<T extends UniqueHelper>(3098 blocksBeforeExecution: number,3099 options: ISchedulerOptions = {},3100 ) {3101 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3102 return new UniqueBaseCollection(this.collectionId, scheduledHelper);3103 }31043105 getSudo<T extends UniqueHelper>() {3106 return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());3107 }3108}310931103111export class UniqueNFTCollection extends UniqueBaseCollection {3112 getTokenObject(tokenId: number) {3113 return new UniqueNFToken(tokenId, this);3114 }31153116 async getTokensByAddress(addressObj: ICrossAccountId) {3117 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);3118 }31193120 async getToken(tokenId: number, blockHashAt?: string) {3121 return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);3122 }31233124 async getTokenOwner(tokenId: number, blockHashAt?: string) {3125 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3126 }31273128 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3129 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3130 }31313132 async getTokenChildren(tokenId: number, blockHashAt?: string) {3133 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);3134 }31353136 async getPropertyPermissions(propertyKeys: string[] | null = null) {3137 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);3138 }31393140 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3141 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3142 }31433144 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {3145 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);3146 }31473148 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3149 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);3150 }31513152 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {3153 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);3154 }31553156 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {3157 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);3158 }31593160 async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3161 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});3162 }31633164 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) {3165 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);3166 }31673168 async burnToken(signer: TSigner, tokenId: number) {3169 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);3170 }31713172 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {3173 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);3174 }31753176 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3177 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);3178 }31793180 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3181 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3182 }31833184 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3185 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3186 }31873188 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3189 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3190 }31913192 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3193 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3194 }31953196 scheduleAt<T extends UniqueHelper>(3197 executionBlockNumber: number,3198 options: ISchedulerOptions = {},3199 ) {3200 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3201 return new UniqueNFTCollection(this.collectionId, scheduledHelper);3202 }32033204 scheduleAfter<T extends UniqueHelper>(3205 blocksBeforeExecution: number,3206 options: ISchedulerOptions = {},3207 ) {3208 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3209 return new UniqueNFTCollection(this.collectionId, scheduledHelper);3210 }32113212 getSudo<T extends UniqueHelper>() {3213 return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());3214 }3215}321632173218export class UniqueRFTCollection extends UniqueBaseCollection {3219 getTokenObject(tokenId: number) {3220 return new UniqueRFToken(tokenId, this);3221 }32223223 async getToken(tokenId: number, blockHashAt?: string) {3224 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3225 }32263227 async getTokensByAddress(addressObj: ICrossAccountId) {3228 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3229 }32303231 async getTop10TokenOwners(tokenId: number) {3232 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3233 }32343235 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3236 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3237 }32383239 async getTokenTotalPieces(tokenId: number) {3240 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3241 }32423243 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3244 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3245 }32463247 async getPropertyPermissions(propertyKeys: string[] | null = null) {3248 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3249 }32503251 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3252 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3253 }32543255 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {3256 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3257 }32583259 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3260 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3261 }32623263 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) {3264 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3265 }32663267 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3268 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3269 }32703271 async mintToken(signer: TSigner, pieces=1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3272 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3273 }32743275 async mintMultipleTokens(signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) {3276 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3277 }32783279 async burnToken(signer: TSigner, tokenId: number, amount=1n) {3280 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3281 }32823283 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n) {3284 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3285 }32863287 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3288 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3289 }32903291 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3292 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3293 }32943295 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3296 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3297 }32983299 scheduleAt<T extends UniqueHelper>(3300 executionBlockNumber: number,3301 options: ISchedulerOptions = {},3302 ) {3303 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3304 return new UniqueRFTCollection(this.collectionId, scheduledHelper);3305 }33063307 scheduleAfter<T extends UniqueHelper>(3308 blocksBeforeExecution: number,3309 options: ISchedulerOptions = {},3310 ) {3311 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3312 return new UniqueRFTCollection(this.collectionId, scheduledHelper);3313 }33143315 getSudo<T extends UniqueHelper>() {3316 return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());3317 }3318}331933203321export class UniqueFTCollection extends UniqueBaseCollection {3322 async getBalance(addressObj: ICrossAccountId) {3323 return await this.helper.ft.getBalance(this.collectionId, addressObj);3324 }33253326 async getTotalPieces() {3327 return await this.helper.ft.getTotalPieces(this.collectionId);3328 }33293330 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3331 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3332 }33333334 async getTop10Owners() {3335 return await this.helper.ft.getTop10Owners(this.collectionId);3336 }33373338 async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) {3339 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3340 }33413342 async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) {3343 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3344 }33453346 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3347 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3348 }33493350 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3351 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3352 }33533354 async burnTokens(signer: TSigner, amount=1n) {3355 return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3356 }33573358 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3359 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3360 }33613362 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3363 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3364 }33653366 scheduleAt<T extends UniqueHelper>(3367 executionBlockNumber: number,3368 options: ISchedulerOptions = {},3369 ) {3370 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);3371 return new UniqueFTCollection(this.collectionId, scheduledHelper);3372 }33733374 scheduleAfter<T extends UniqueHelper>(3375 blocksBeforeExecution: number,3376 options: ISchedulerOptions = {},3377 ) {3378 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);3379 return new UniqueFTCollection(this.collectionId, scheduledHelper);3380 }33813382 getSudo<T extends UniqueHelper>() {3383 return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());3384 }3385}338633873388export class UniqueBaseToken {3389 collection: UniqueNFTCollection | UniqueRFTCollection;3390 collectionId: number;3391 tokenId: number;33923393 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3394 this.collection = collection;3395 this.collectionId = collection.collectionId;3396 this.tokenId = tokenId;3397 }33983399 async getNextSponsored(addressObj: ICrossAccountId) {3400 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3401 }34023403 async getProperties(propertyKeys?: string[] | null) {3404 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3405 }34063407 async setProperties(signer: TSigner, properties: IProperty[]) {3408 return await this.collection.setTokenProperties(signer, this.tokenId, properties);3409 }34103411 async deleteProperties(signer: TSigner, propertyKeys: string[]) {3412 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3413 }34143415 async doesExist() {3416 return await this.collection.doesTokenExist(this.tokenId);3417 }34183419 nestingAccount() {3420 return this.collection.helper.util.getTokenAccount(this);3421 }34223423 scheduleAt<T extends UniqueHelper>(3424 executionBlockNumber: number,3425 options: ISchedulerOptions = {},3426 ) {3427 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3428 return new UniqueBaseToken(this.tokenId, scheduledCollection);3429 }34303431 scheduleAfter<T extends UniqueHelper>(3432 blocksBeforeExecution: number,3433 options: ISchedulerOptions = {},3434 ) {3435 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3436 return new UniqueBaseToken(this.tokenId, scheduledCollection);3437 }34383439 getSudo<T extends UniqueHelper>() {3440 return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());3441 }3442}344334443445export class UniqueNFToken extends UniqueBaseToken {3446 collection: UniqueNFTCollection;34473448 constructor(tokenId: number, collection: UniqueNFTCollection) {3449 super(tokenId, collection);3450 this.collection = collection;3451 }34523453 async getData(blockHashAt?: string) {3454 return await this.collection.getToken(this.tokenId, blockHashAt);3455 }34563457 async getOwner(blockHashAt?: string) {3458 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3459 }34603461 async getTopmostOwner(blockHashAt?: string) {3462 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3463 }34643465 async getChildren(blockHashAt?: string) {3466 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3467 }34683469 async nest(signer: TSigner, toTokenObj: IToken) {3470 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3471 }34723473 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3474 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3475 }34763477 async transfer(signer: TSigner, addressObj: ICrossAccountId) {3478 return await this.collection.transferToken(signer, this.tokenId, addressObj);3479 }34803481 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3482 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3483 }34843485 async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3486 return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3487 }34883489 async isApproved(toAddressObj: ICrossAccountId) {3490 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3491 }34923493 async burn(signer: TSigner) {3494 return await this.collection.burnToken(signer, this.tokenId);3495 }34963497 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3498 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3499 }35003501 scheduleAt<T extends UniqueHelper>(3502 executionBlockNumber: number,3503 options: ISchedulerOptions = {},3504 ) {3505 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3506 return new UniqueNFToken(this.tokenId, scheduledCollection);3507 }35083509 scheduleAfter<T extends UniqueHelper>(3510 blocksBeforeExecution: number,3511 options: ISchedulerOptions = {},3512 ) {3513 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3514 return new UniqueNFToken(this.tokenId, scheduledCollection);3515 }35163517 getSudo<T extends UniqueHelper>() {3518 return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());3519 }3520}35213522export class UniqueRFToken extends UniqueBaseToken {3523 collection: UniqueRFTCollection;35243525 constructor(tokenId: number, collection: UniqueRFTCollection) {3526 super(tokenId, collection);3527 this.collection = collection;3528 }35293530 async getData(blockHashAt?: string) {3531 return await this.collection.getToken(this.tokenId, blockHashAt);3532 }35333534 async getTop10Owners() {3535 return await this.collection.getTop10TokenOwners(this.tokenId);3536 }35373538 async getBalance(addressObj: ICrossAccountId) {3539 return await this.collection.getTokenBalance(this.tokenId, addressObj);3540 }35413542 async getTotalPieces() {3543 return await this.collection.getTokenTotalPieces(this.tokenId);3544 }35453546 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3547 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3548 }35493550 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) {3551 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3552 }35533554 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {3555 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3556 }35573558 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) {3559 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3560 }35613562 async repartition(signer: TSigner, amount: bigint) {3563 return await this.collection.repartitionToken(signer, this.tokenId, amount);3564 }35653566 async burn(signer: TSigner, amount=1n) {3567 return await this.collection.burnToken(signer, this.tokenId, amount);3568 }35693570 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) {3571 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3572 }35733574 scheduleAt<T extends UniqueHelper>(3575 executionBlockNumber: number,3576 options: ISchedulerOptions = {},3577 ) {3578 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);3579 return new UniqueRFToken(this.tokenId, scheduledCollection);3580 }35813582 scheduleAfter<T extends UniqueHelper>(3583 blocksBeforeExecution: number,3584 options: ISchedulerOptions = {},3585 ) {3586 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);3587 return new UniqueRFToken(this.tokenId, scheduledCollection);3588 }35893590 getSudo<T extends UniqueHelper>() {3591 return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());3592 }3593}