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 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}