difftreelog
add Apache-2.0 license identifier
in: master
2 files changed
tests/src/util/playgrounds/index.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/index.ts
+++ b/tests/src/util/playgrounds/index.ts
@@ -1,3 +1,6 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
import {IKeyringPair} from '@polkadot/types/types';
import {UniqueHelper} from './unique';
import config from '../../config';
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth1/* eslint-disable @typescript-eslint/no-var-requires */2/* eslint-disable function-call-argument-newline */3/* eslint-disable no-prototype-builtins */45import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';6import {ApiInterfaceEvents} from '@polkadot/api/types';7import {IKeyringPair} from '@polkadot/types/types';8import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';91011const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {12 const address = {} as ICrossAccountId;13 if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate;14 if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum;15 return address;16};171819const nesting = {20 toChecksumAddress(address: string): string {21 if (typeof address === 'undefined') return '';2223 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);2425 address = address.toLowerCase().replace(/^0x/i,'');26 const addressHash = keccakAsHex(address).replace(/^0x/i,'');27 const checksumAddress = ['0x'];2829 for (let i = 0; i < address.length; i++) {30 // If ith character is 8 to f then make it uppercase31 if (parseInt(addressHash[i], 16) > 7) {32 checksumAddress.push(address[i].toUpperCase());33 } else {34 checksumAddress.push(address[i]);35 }36 }37 return checksumAddress.join('');38 },39 tokenIdToAddress(collectionId: number, tokenId: number) {40 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);41 },42};434445interface IChainEvent {46 data: any;47 method: string;48 section: string;49}5051interface ITransactionResult {52 status: 'Fail' | 'Success';53 result: {54 events: {55 event: IChainEvent56 }[];57 },58 moduleError?: string;59}6061interface ILogger {62 log: (msg: any, level?: string) => void;63 level: {64 ERROR: 'ERROR';65 WARNING: 'WARNING';66 INFO: 'INFO';67 [key: string]: string;68 }69}7071interface IUniqueHelperLog {72 executedAt: number;73 executionTime: number;74 type: 'extrinsic' | 'rpc';75 status: 'Fail' | 'Success';76 call: string;77 params: any[];78 moduleError?: string;79 events?: any;80}8182interface IApiListeners {83 connected?: (...args: any[]) => any;84 disconnected?: (...args: any[]) => any;85 error?: (...args: any[]) => any;86 ready?: (...args: any[]) => any; 87 decorated?: (...args: any[]) => any;88}8990interface ICrossAccountId {91 Substrate?: TSubstrateAccount;92 Ethereum?: TEthereumAccount;93}9495interface ICrossAccountIdLower {96 substrate?: TSubstrateAccount;97 ethereum?: TEthereumAccount;98}99100interface ICollectionLimits {101 accountTokenOwnershipLimit?: number | null;102 sponsoredDataSize?: number | null;103 sponsoredDataRateLimit?: {blocks: number} | {sponsoringDisabled: null} | null;104 tokenLimit?: number | null;105 sponsorTransferTimeout?: number | null;106 sponsorApproveTimeout?: number | null;107 ownerCanTransfer?: boolean | null;108 ownerCanDestroy?: boolean | null;109 transfersEnabled?: boolean | null;110}111112interface INestingPermissions {113 tokenOwner?: boolean;114 collectionAdmin?: boolean;115 restricted?: number[] | null;116}117118interface ICollectionPermissions {119 access?: 'Normal' | 'AllowList';120 mintMode?: boolean;121 nesting?: INestingPermissions;122}123124interface IProperty {125 key: string;126 value: string;127}128129interface ITokenPropertyPermission {130 key: string;131 permission: {132 mutable: boolean;133 tokenOwner: boolean;134 collectionAdmin: boolean;135 }136}137138interface IToken {139 collectionId: number;140 tokenId: number;141}142143interface ICollectionCreationOptions {144 name: string | number[];145 description: string | number[];146 tokenPrefix: string | number[];147 mode?: {148 nft?: null;149 refungible?: null;150 fungible?: number;151 }152 permissions?: ICollectionPermissions;153 properties?: IProperty[];154 tokenPropertyPermissions?: ITokenPropertyPermission[];155 limits?: ICollectionLimits;156 pendingSponsor?: TSubstrateAccount;157}158159interface IChainProperties {160 ss58Format: number;161 tokenDecimals: number[];162 tokenSymbol: string[]163}164165type TSubstrateAccount = string;166type TEthereumAccount = string;167type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';168type TUniqueNetworks = 'opal' | 'quartz' | 'unique';169type TSigner = IKeyringPair; // | 'string'170171class UniqueUtil {172 static transactionStatus = {173 NOT_READY: 'NotReady',174 FAIL: 'Fail',175 SUCCESS: 'Success',176 };177178 static chainLogType = {179 EXTRINSIC: 'extrinsic',180 RPC: 'rpc',181 };182183 static getNestingTokenAddress(collectionId: number, tokenId: number) {184 return nesting.tokenIdToAddress(collectionId, tokenId);185 }186187 static getDefaultLogger(): ILogger {188 return {189 log(msg: any, level = 'INFO') {190 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));191 },192 level: {193 ERROR: 'ERROR',194 WARNING: 'WARNING',195 INFO: 'INFO',196 },197 };198 }199200 static vec2str(arr: string[] | number[]) {201 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');202 }203204 static str2vec(string: string) {205 if (typeof string !== 'string') return string;206 return Array.from(string).map(x => x.charCodeAt(0));207 }208209 static fromSeed(seed: string, ss58Format = 42) {210 const keyring = new Keyring({type: 'sr25519', ss58Format});211 return keyring.addFromUri(seed);212 }213214 static normalizeSubstrateAddress(address: string, ss58Format = 42) {215 return encodeAddress(decodeAddress(address), ss58Format);216 }217218 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') {219 if (creationResult.status !== this.transactionStatus.SUCCESS) {220 throw Error(`Unable to create collection for ${label}`);221 }222223 let collectionId = null;224 creationResult.result.events.forEach(({event: {data, method, section}}) => {225 if ((section === 'common') && (method === 'CollectionCreated')) {226 collectionId = parseInt(data[0].toString(), 10);227 }228 });229230 if (collectionId === null) {231 throw Error(`No CollectionCreated event for ${label}`);232 }233234 return collectionId;235 }236237 static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') {238 if (creationResult.status !== this.transactionStatus.SUCCESS) {239 throw Error(`Unable to create tokens for ${label}`);240 }241 let success = false;242 const tokens = [] as any;243 creationResult.result.events.forEach(({event: {data, method, section}}) => {244 if (method === 'ExtrinsicSuccess') {245 success = true;246 } else if ((section === 'common') && (method === 'ItemCreated')) {247 tokens.push({248 collectionId: parseInt(data[0].toString(), 10),249 tokenId: parseInt(data[1].toString(), 10),250 owner: data[2].toJSON(),251 });252 }253 });254 return {success, tokens};255 }256257 static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') {258 if (burnResult.status !== this.transactionStatus.SUCCESS) {259 throw Error(`Unable to burn tokens for ${label}`);260 }261 let success = false;262 const tokens = [] as any;263 burnResult.result.events.forEach(({event: {data, method, section}}) => {264 if (method === 'ExtrinsicSuccess') {265 success = true;266 } else if ((section === 'common') && (method === 'ItemDestroyed')) {267 tokens.push({268 collectionId: parseInt(data[0].toString(), 10),269 tokenId: parseInt(data[1].toString(), 10),270 owner: data[2].toJSON(),271 });272 }273 });274 return {success, tokens};275 }276277 static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) {278 let eventId = null;279 events.forEach(({event: {data, method, section}}) => {280 if ((section === expectedSection) && (method === expectedMethod)) {281 eventId = parseInt(data[0].toString(), 10);282 }283 });284285 if (eventId === null) {286 throw Error(`No ${expectedMethod} event for ${label}`);287 }288 return eventId === collectionId;289 }290291 static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {292 const normalizeAddress = (address: string | ICrossAccountId) => {293 if(typeof address === 'string') return address;294 const obj = {} as any;295 Object.keys(address).forEach(k => {296 obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];297 });298 if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)};299 if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()};300 return address;301 };302 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;303 events.forEach(({event: {data, method, section}}) => {304 if ((section === 'common') && (method === 'Transfer')) {305 const hData = (data as any).toJSON();306 transfer = {307 collectionId: hData[0],308 tokenId: hData[1],309 from: normalizeAddress(hData[2]),310 to: normalizeAddress(hData[3]),311 amount: BigInt(hData[4]),312 };313 }314 });315 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;316 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);317 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);318 isSuccess = isSuccess && amount === transfer.amount;319 return isSuccess;320 }321}322323324class ChainHelperBase {325 transactionStatus = UniqueUtil.transactionStatus;326 chainLogType = UniqueUtil.chainLogType;327 util: typeof UniqueUtil;328 logger: ILogger;329 api: ApiPromise | null;330 forcedNetwork: TUniqueNetworks | null;331 network: TUniqueNetworks | null;332 chainLog: IUniqueHelperLog[];333334 constructor(logger?: ILogger) {335 this.util = UniqueUtil;336 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();337 this.logger = logger;338 this.api = null;339 this.forcedNetwork = null;340 this.network = null;341 this.chainLog = [];342 }343344 clearChainLog(): void {345 this.chainLog = [];346 }347348 forceNetwork(value: TUniqueNetworks): void {349 this.forcedNetwork = value;350 }351352 async connect(wsEndpoint: string, listeners?: IApiListeners) {353 if (this.api !== null) throw Error('Already connected');354 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);355 this.api = api;356 this.network = network;357 }358359 async disconnect() {360 if (this.api === null) return;361 await this.api.disconnect();362 this.api = null;363 this.network = null;364 }365366 static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {367 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;368 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;369 return 'opal';370 }371372 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {373 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});374 await api.isReady;375376 const network = await this.detectNetwork(api);377378 await api.disconnect();379380 return network;381 }382383 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{ 384 api: ApiPromise; 385 network: TUniqueNetworks; 386 }> {387 if(typeof network === 'undefined' || network === null) network = 'opal';388 const supportedRPC = {389 opal: {390 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,391 },392 quartz: {393 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,394 },395 unique: {396 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,397 },398 };399 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);400 const rpc = supportedRPC[network];401402 // TODO: investigate how to replace rpc in runtime403 // api._rpcCore.addUserInterfaces(rpc);404405 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});406407 await api.isReadyOrError;408409 if (typeof listeners === 'undefined') listeners = {};410 for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {411 if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;412 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);413 }414415 return {api, network};416 }417418 getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) {419 const {events, status} = data;420 if (status.isReady) {421 return this.transactionStatus.NOT_READY;422 }423 if (status.isBroadcast) {424 return this.transactionStatus.NOT_READY;425 }426 if (status.isInBlock || status.isFinalized) {427 const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed');428 if (errors.length > 0) {429 return this.transactionStatus.FAIL;430 }431 if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {432 return this.transactionStatus.SUCCESS;433 }434 }435436 return this.transactionStatus.FAIL;437 }438439 signTransaction(sender: TSigner, transaction: any, label = 'transaction', options = null) {440 const sign = (callback: any) => {441 if(options !== null) return transaction.signAndSend(sender, options, callback);442 return transaction.signAndSend(sender, callback);443 };444 return new Promise(async (resolve, reject) => {445 try {446 const unsub = await sign((result: any) => {447 const status = this.getTransactionStatus(result);448449 if (status === this.transactionStatus.SUCCESS) {450 this.logger.log(`${label} successful`);451 unsub();452 resolve({result, status});453 } else if (status === this.transactionStatus.FAIL) {454 let moduleError = null;455456 if (result.hasOwnProperty('dispatchError')) {457 const dispatchError = result['dispatchError'];458459 if (dispatchError && dispatchError.isModule) {460 const modErr = dispatchError.asModule;461 const errorMeta = dispatchError.registry.findMetaError(modErr);462463 moduleError = `${errorMeta.section}.${errorMeta.name}`;464 }465 else {466 this.logger.log(result, this.logger.level.ERROR);467 }468 }469470 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);471 unsub();472 reject({status, moduleError, result});473 }474 });475 } catch (e) {476 this.logger.log(e, this.logger.level.ERROR);477 reject(e);478 }479 });480 }481482 constructApiCall(apiCall: string, params: any[]) {483 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);484 let call = this.api as any;485 for(const part of apiCall.slice(4).split('.')) {486 call = call[part];487 }488 return call(...params);489 }490491 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') {492 if(this.api === null) throw Error('API not initialized');493 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);494495 const startTime = (new Date()).getTime();496 let result: ITransactionResult;497 let events = [];498 try {499 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult;500 events = result.result.events.map((x: any) => x.toHuman());501 }502 catch(e) {503 if(!(e as object).hasOwnProperty('status')) throw e;504 result = e as ITransactionResult;505 }506507 const endTime = (new Date()).getTime();508509 const log = {510 executedAt: endTime,511 executionTime: endTime - startTime,512 type: this.chainLogType.EXTRINSIC,513 status: result.status,514 call: extrinsic,515 params,516 } as IUniqueHelperLog;517518 if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError;519 if(events.length > 0) log.events = events;520521 this.chainLog.push(log);522523 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage);524 return result;525 }526527 async callRpc(rpc: string, params?: any[]) {528 if(typeof params === 'undefined') params = [];529 if(this.api === null) throw Error('API not initialized');530 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);531532 const startTime = (new Date()).getTime();533 let result;534 let error = null;535 const log = {536 type: this.chainLogType.RPC,537 call: rpc,538 params,539 } as IUniqueHelperLog;540541 try {542 result = await this.constructApiCall(rpc, params);543 }544 catch(e) {545 error = e;546 }547548 const endTime = (new Date()).getTime();549550 log.executedAt = endTime;551 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';552 log.executionTime = endTime - startTime;553554 this.chainLog.push(log);555556 if(error !== null) throw error;557558 return result;559 }560561 getSignerAddress(signer: IKeyringPair | string): string {562 if(typeof signer === 'string') return signer;563 return signer.address;564 }565}566567568class HelperGroup {569 helper: UniqueHelper;570571 constructor(uniqueHelper: UniqueHelper) {572 this.helper = uniqueHelper;573 }574}575576577class CollectionGroup extends HelperGroup {578 /**579 * Get number of blocks when sponsored transaction is available.580 *581 * @param collectionId ID of collection582 * @param tokenId ID of token583 * @param addressObj address for which the sponsorship is checked584 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});585 * @returns number of blocks or null if sponsorship hasn't been set586 */587 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {588 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();589 }590591 /**592 * Get the number of created collections.593 * 594 * @returns number of created collections595 */596 async getTotalCount(): Promise<number> {597 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();598 }599600 /**601 * Get information about the collection with additional data, including the number of tokens it contains, its administrators, the normalized address of the collection's owner, and decoded name and description.602 * 603 * @param collectionId ID of collection604 * @example await getData(2)605 * @returns collection information object606 */607 async getData(collectionId: number): Promise<{608 id: number;609 name: string;610 description: string;611 tokensCount: number;612 admins: ICrossAccountId[];613 normalizedOwner: TSubstrateAccount;614 raw: any615 } | null> {616 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);617 const humanCollection = collection.toHuman(), collectionData = {618 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],619 raw: humanCollection,620 } as any, jsonCollection = collection.toJSON();621 if (humanCollection === null) return null;622 collectionData.raw.limits = jsonCollection.limits;623 collectionData.raw.permissions = jsonCollection.permissions;624 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);625 for (const key of ['name', 'description']) {626 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);627 }628629 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0;630 collectionData.admins = await this.getAdmins(collectionId);631632 return collectionData;633 }634635 /**636 * Get the normalized addresses of the collection's administrators.637 * 638 * @param collectionId ID of collection639 * @example await getAdmins(1)640 * @returns array of administrators641 */642 async getAdmins(collectionId: number): Promise<ICrossAccountId[]> {643 const normalized = [];644 for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) {645 if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)});646 else normalized.push(admin);647 }648 return normalized;649 }650651 /**652 * Get the effective limits of the collection instead of null for default values653 * 654 * @param collectionId ID of collection655 * @example await getEffectiveLimits(2)656 * @returns object of collection limits657 */658 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {659 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();660 }661662 /**663 * Burns the collection if the signer has sufficient permissions and collection is empty.664 * 665 * @param signer keyring of signer666 * @param collectionId ID of collection667 * @param label extra label for log668 * @example await helper.collection.burn(aliceKeyring, 3);669 * @returns ```true``` if extrinsic success, otherwise ```false```670 */671 async burn(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {672 if(typeof label === 'undefined') label = `collection #${collectionId}`;673 const result = await this.helper.executeExtrinsic(674 signer,675 'api.tx.unique.destroyCollection', [collectionId],676 true, `Unable to burn collection for ${label}`,677 );678679 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label);680 }681682 /**683 * Sets the sponsor for the collection (Requires the Substrate address).684 * 685 * @param signer keyring of signer686 * @param collectionId ID of collection687 * @param sponsorAddress Sponsor substrate address688 * @param label extra label for log689 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")690 * @returns ```true``` if extrinsic success, otherwise ```false```691 */692 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise<boolean> {693 if(typeof label === 'undefined') label = `collection #${collectionId}`;694 const result = await this.helper.executeExtrinsic(695 signer,696 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],697 true, `Unable to set collection sponsor for ${label}`,698 );699700 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label);701 }702703 /**704 * Confirms consent to sponsor the collection on behalf of the signer.705 * 706 * @param signer keyring of signer707 * @param collectionId ID of collection708 * @param label extra label for log709 * @example confirmSponsorship(aliceKeyring, 10)710 * @returns ```true``` if extrinsic success, otherwise ```false```711 */712 async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {713 if(typeof label === 'undefined') label = `collection #${collectionId}`;714 const result = await this.helper.executeExtrinsic(715 signer,716 'api.tx.unique.confirmSponsorship', [collectionId],717 true, `Unable to confirm collection sponsorship for ${label}`,718 );719720 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label);721 }722723 /**724 * Sets the limits of the collection. At least one limit must be specified for a correct call.725 * 726 * @param signer keyring of signer727 * @param collectionId ID of collection728 * @param limits collection limits object729 * @param label extra label for log730 * @example731 * await setLimits(732 * aliceKeyring,733 * 10,734 * {735 * sponsorTransferTimeout: 0,736 * ownerCanDestroy: false737 * }738 * )739 * @returns ```true``` if extrinsic success, otherwise ```false```740 */741 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise<boolean> {742 if(typeof label === 'undefined') label = `collection #${collectionId}`;743 const result = await this.helper.executeExtrinsic(744 signer,745 'api.tx.unique.setCollectionLimits', [collectionId, limits],746 true, `Unable to set collection limits for ${label}`,747 );748749 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label);750 }751752 /**753 * Changes the owner of the collection to the new Substrate address.754 * 755 * @param signer keyring of signer756 * @param collectionId ID of collection757 * @param ownerAddress substrate address of new owner758 * @param label extra label for log759 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")760 * @returns ```true``` if extrinsic success, otherwise ```false```761 */762 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise<boolean> {763 if(typeof label === 'undefined') label = `collection #${collectionId}`;764 const result = await this.helper.executeExtrinsic(765 signer,766 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],767 true, `Unable to change collection owner for ${label}`,768 );769770 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label);771 }772773 /**774 * Adds a collection administrator. 775 * 776 * @param signer keyring of signer777 * @param collectionId ID of collection778 * @param adminAddressObj Administrator address (substrate or ethereum)779 * @param label extra label for log780 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})781 * @returns ```true``` if extrinsic success, otherwise ```false```782 */783 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {784 if(typeof label === 'undefined') label = `collection #${collectionId}`;785 const result = await this.helper.executeExtrinsic(786 signer,787 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],788 true, `Unable to add collection admin for ${label}`,789 );790791 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label);792 }793794 /**795 * Removes a collection administrator.796 * 797 * @param signer keyring of signer798 * @param collectionId ID of collection799 * @param adminAddressObj Administrator address (substrate or ethereum)800 * @param label extra label for log801 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})802 * @returns ```true``` if extrinsic success, otherwise ```false```803 */804 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {805 if(typeof label === 'undefined') label = `collection #${collectionId}`;806 const result = await this.helper.executeExtrinsic(807 signer,808 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],809 true, `Unable to remove collection admin for ${label}`,810 );811812 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label);813 }814815 /**816 * Sets onchain permissions for selected collection.817 * 818 * @param signer keyring of signer819 * @param collectionId ID of collection820 * @param permissions collection permissions object821 * @param label extra label for log822 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});823 * @returns ```true``` if extrinsic success, otherwise ```false```824 */825 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise<boolean> {826 if(typeof label === 'undefined') label = `collection #${collectionId}`;827 const result = await this.helper.executeExtrinsic(828 signer,829 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],830 true, `Unable to set collection permissions for ${label}`,831 );832833 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label);834 }835836 /**837 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.838 * 839 * @param signer keyring of signer840 * @param collectionId ID of collection841 * @param permissions nesting permissions object842 * @param label extra label for log843 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});844 * @returns ```true``` if extrinsic success, otherwise ```false```845 */846 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise<boolean> {847 return await this.setPermissions(signer, collectionId, {nesting: permissions}, label);848 }849850 /**851 * Disables nesting for selected collection.852 * 853 * @param signer keyring of signer854 * @param collectionId ID of collection855 * @param label extra label for log856 * @example disableNesting(aliceKeyring, 10);857 * @returns ```true``` if extrinsic success, otherwise ```false```858 */859 async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {860 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label);861 }862863 /**864 * Sets onchain properties to the collection.865 * 866 * @param signer keyring of signer867 * @param collectionId ID of collection868 * @param properties array of property objects869 * @param label extra label for log870 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);871 * @returns ```true``` if extrinsic success, otherwise ```false```872 */873 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {874 if(typeof label === 'undefined') label = `collection #${collectionId}`;875 const result = await this.helper.executeExtrinsic(876 signer,877 'api.tx.unique.setCollectionProperties', [collectionId, properties],878 true, `Unable to set collection properties for ${label}`,879 );880881 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);882 }883884 /**885 * Deletes onchain properties from the collection.886 * 887 * @param signer keyring of signer888 * @param collectionId ID of collection889 * @param propertyKeys array of property keys to delete890 * @param label891 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);892 * @returns ```true``` if extrinsic success, otherwise ```false```893 */894 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {895 if(typeof label === 'undefined') label = `collection #${collectionId}`;896 const result = await this.helper.executeExtrinsic(897 signer,898 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],899 true, `Unable to delete collection properties for ${label}`,900 );901902 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);903 }904905 /**906 * Changes the owner of the token.907 * 908 * @param signer keyring of signer909 * @param collectionId ID of collection910 * @param tokenId ID of token911 * @param addressObj address of a new owner912 * @param amount amount of tokens to be transfered. For NFT must be set to 1n913 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})914 * @returns true if the token success, otherwise false915 */916 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {917 const result = await this.helper.executeExtrinsic(918 signer,919 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],920 true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,921 );922923 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);924 }925926 /**927 * 928 * Change ownership of a token(s) on behalf of the owner. 929 * 930 * @param signer keyring of signer931 * @param collectionId ID of collection932 * @param tokenId ID of token933 * @param fromAddressObj address on behalf of which the token will be sent934 * @param toAddressObj new token owner935 * @param amount amount of tokens to be transfered. For NFT must be set to 1n936 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})937 * @returns true if the token success, otherwise false938 */939 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {940 const result = await this.helper.executeExtrinsic(941 signer,942 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],943 true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,944 );945 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);946 }947948 /**949 * 950 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.951 * 952 * @param signer keyring of signer953 * @param collectionId ID of collection954 * @param tokenId ID of token955 * @param label 956 * @param amount amount of tokens to be burned. For NFT must be set to 1n957 * @example burnToken(aliceKeyring, 10, 5);958 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```959 */960 async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{961 success: boolean,962 token: number | null963 }> {964 if(typeof label === 'undefined') label = `collection #${collectionId}`;965 const burnResult = await this.helper.executeExtrinsic(966 signer,967 'api.tx.unique.burnItem', [collectionId, tokenId, amount],968 true, `Unable to burn token for ${label}`,969 );970 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);971 if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');972 return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};973 }974975 /**976 * Destroys a concrete instance of NFT on behalf of the owner977 * 978 * @param signer keyring of signer979 * @param collectionId ID of collection980 * @param fromAddressObj address on behalf of which the token will be burnt981 * @param tokenId ID of token982 * @param label 983 * @param amount amount of tokens to be burned. For NFT must be set to 1n984 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})985 * @returns ```true``` if extrinsic success, otherwise ```false```986 */987 async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {988 if(typeof label === 'undefined') label = `collection #${collectionId}`;989 const burnResult = await this.helper.executeExtrinsic(990 signer,991 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],992 true, `Unable to burn token from for ${label}`,993 );994 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);995 return burnedTokens.success && burnedTokens.tokens.length > 0;996 }997998 /**999 * Set, change, or remove approved address to transfer the ownership of the NFT.1000 * 1001 * @param signer keyring of signer1002 * @param collectionId ID of collection1003 * @param tokenId ID of token1004 * @param toAddressObj 1005 * @param label 1006 * @param amount amount of token to be approved. For NFT must be set to 1n1007 * @returns ```true``` if extrinsic success, otherwise ```false```1008 */1009 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {1010 if(typeof label === 'undefined') label = `collection #${collectionId}`;1011 const approveResult = await this.helper.executeExtrinsic(1012 signer, 1013 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1014 true, `Unable to approve token for ${label}`,1015 );10161017 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);1018 }10191020 /**1021 * Get the amount of token pieces approved to transfer1022 * @param collectionId ID of collection1023 * @param tokenId ID of token1024 * @param toAccountObj 1025 * @param fromAccountObj1026 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1027 * @returns number of approved to transfer pieces1028 */1029 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1030 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1031 }10321033 /**1034 * Get the last created token id1035 * @param collectionId ID of collection1036 * @example getLastTokenId(10);1037 * @returns id of the last created token1038 */1039 async getLastTokenId(collectionId: number): Promise<number> {1040 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1041 }10421043 /**1044 * Check if token exists1045 * @param collectionId ID of collection1046 * @param tokenId ID of token1047 * @example isTokenExists(10, 20);1048 * @returns true if the token exists, otherwise false1049 */1050 async isTokenExists(collectionId: number, tokenId: number): Promise<boolean> {1051 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1052 }1053}10541055class NFTnRFT extends CollectionGroup {1056 /**1057 * Get tokens owned by account1058 * 1059 * @param collectionId ID of collection1060 * @param addressObj tokens owner1061 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1062 * @returns array of token ids owned by account1063 */1064 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1065 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1066 }10671068 /**1069 * Get token data1070 * @param collectionId ID of collection1071 * @param tokenId ID of token1072 * @param blockHashAt 1073 * @param propertyKeys1074 * @example getToken(10, 5);1075 * @returns human readable token data 1076 */1077 async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{1078 properties: IProperty[];1079 owner: ICrossAccountId;1080 normalizedOwner: ICrossAccountId;1081 }| null> {1082 let tokenData;1083 if(typeof blockHashAt === 'undefined') {1084 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1085 }1086 else {1087 if(typeof propertyKeys === 'undefined') {1088 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1089 if(!collection) return null;1090 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1091 }1092 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1093 }1094 tokenData = tokenData.toHuman();1095 if (tokenData === null || tokenData.owner === null) return null;1096 const owner = {} as any;1097 for (const key of Object.keys(tokenData.owner)) {1098 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];1099 }1100 tokenData.normalizedOwner = crossAccountIdFromLower(owner);1101 return tokenData;1102 }11031104 /**1105 * Set permissions to change token properties1106 * @param signer keyring of signer1107 * @param collectionId ID of collection1108 * @param permissions permissions to change a property by the collection owner or admin1109 * @param label 1110 * @example setTokenPropertyPermissions(1111 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1112 * )1113 * @returns true if extrinsic success otherwise false1114 */1115 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {1116 if(typeof label === 'undefined') label = `collection #${collectionId}`;1117 const result = await this.helper.executeExtrinsic(1118 signer,1119 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1120 true, `Unable to set token property permissions for ${label}`,1121 );11221123 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);1124 }11251126 /**1127 * Set token properties1128 * @param signer keyring of signer1129 * @param collectionId ID of collection1130 * @param tokenId ID of token1131 * @param properties 1132 * @param label 1133 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1134 * @returns ```true``` if extrinsic success, otherwise ```false```1135 */1136 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {1137 if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1138 const result = await this.helper.executeExtrinsic(1139 signer,1140 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1141 true, `Unable to set token properties for ${label}`,1142 );11431144 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);1145 }11461147 /**1148 * Delete the provided properties of a token1149 * @param signer keyring of signer1150 * @param collectionId ID of collection1151 * @param tokenId ID of token1152 * @param propertyKeys property keys to be deleted 1153 * @param label 1154 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1155 * @returns ```true``` if extrinsic success, otherwise ```false```1156 */1157 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {1158 if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1159 const result = await this.helper.executeExtrinsic(1160 signer,1161 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1162 true, `Unable to delete token properties for ${label}`,1163 );11641165 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);1166 }11671168 /**1169 * Mint new collection1170 * @param signer keyring of signer1171 * @param collectionOptions basic collection options and properties 1172 * @param mode NFT or RFT type of a collection1173 * @param errorLabel 1174 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1175 * @returns object of the created collection1176 */1177 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {1178 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1179 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1180 for (const key of ['name', 'description', 'tokenPrefix']) {1181 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);1182 }1183 const creationResult = await this.helper.executeExtrinsic(1184 signer,1185 'api.tx.unique.createCollectionEx', [collectionOptions],1186 true, errorLabel,1187 );1188 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1189 }11901191 getCollectionObject(collectionId: number): any {1192 return null;1193 }11941195 getTokenObject(collectionId: number, tokenId: number): any {1196 return null;1197 }1198}119912001201class NFTGroup extends NFTnRFT {1202 /**1203 * Get collection object1204 * @param collectionId ID of collection1205 * @example getCollectionObject(2);1206 * @returns instance of UniqueNFTCollection1207 */1208 getCollectionObject(collectionId: number): UniqueNFTCollection {1209 return new UniqueNFTCollection(collectionId, this.helper);1210 }12111212 /**1213 * Get token object1214 * @param collectionId ID of collection1215 * @param tokenId ID of token1216 * @example getTokenObject(10, 5);1217 * @returns instance of UniqueNFTToken1218 */1219 getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {1220 return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));1221 }12221223 /**1224 * Get token's owner1225 * @param collectionId ID of collection1226 * @param tokenId ID of token1227 * @param blockHashAt 1228 * @example getTokenOwner(10, 5);1229 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1230 */1231 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {1232 let owner;1233 if (typeof blockHashAt === 'undefined') {1234 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1235 } else {1236 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1237 }1238 return crossAccountIdFromLower(owner.toJSON());1239 }12401241 /**1242 * Is token approved to transfer1243 * @param collectionId ID of collection1244 * @param tokenId ID of token1245 * @param toAccountObj address to be approved1246 * @returns ```true``` if extrinsic success, otherwise ```false```1247 */1248 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1249 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1250 }12511252 /**1253 * Changes the owner of the token.1254 * 1255 * @param signer keyring of signer1256 * @param collectionId ID of collection1257 * @param tokenId ID of token1258 * @param addressObj address of a new owner1259 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1260 * @returns ```true``` if extrinsic success, otherwise ```false```1261 */1262 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1263 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1264 }12651266 /**1267 * 1268 * Change ownership of a NFT on behalf of the owner. 1269 * 1270 * @param signer keyring of signer1271 * @param collectionId ID of collection1272 * @param tokenId ID of token1273 * @param fromAddressObj address on behalf of which the token will be sent1274 * @param toAddressObj new token owner1275 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1276 * @returns ```true``` if extrinsic success, otherwise ```false```1277 */1278 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1279 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1280 }12811282 /**1283 * Recursively find the address that owns the token1284 * @param collectionId ID of collection1285 * @param tokenId ID of token1286 * @param blockHashAt 1287 * @example getTokenTopmostOwner(10, 5);1288 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1289 */1290 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {1291 let owner;1292 if (typeof blockHashAt === 'undefined') {1293 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1294 } else {1295 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1296 }12971298 if (owner === null) return null;12991300 owner = owner.toHuman();13011302 return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;1303 }13041305 /**1306 * Get tokens nested in the provided token1307 * @param collectionId ID of collection1308 * @param tokenId ID of token1309 * @param blockHashAt 1310 * @example getTokenChildren(10, 5);1311 * @returns tokens whose depth of nesting is <= 5 1312 */1313 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1314 let children;1315 if(typeof blockHashAt === 'undefined') {1316 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1317 } else {1318 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1319 }13201321 return children.toJSON().map((x: any) => {1322 return {collectionId: x.collection, tokenId: x.token};1323 });1324 }13251326 /**1327 * Nest one token into another1328 * @param signer keyring of signer1329 * @param tokenObj token to be nested1330 * @param rootTokenObj token to be parent1331 * @param label 1332 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1333 * @returns ```true``` if extrinsic success, otherwise ```false```1334 */1335 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {1336 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1337 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1338 if(!result) {1339 throw Error(`Unable to nest token for ${label}`);1340 }1341 return result;1342 }13431344 /**1345 * Remove token from nested state1346 * @param signer keyring of signer1347 * @param tokenObj token to unnest1348 * @param rootTokenObj parent of a token1349 * @param toAddressObj address of a new token owner 1350 * @param label 1351 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1352 * @returns ```true``` if extrinsic success, otherwise ```false```1353 */1354 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {1355 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1356 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1357 if(!result) {1358 throw Error(`Unable to unnest token for ${label}`);1359 }1360 return result;1361 }13621363 /**1364 * Mint new collection1365 * @param signer keyring of signer1366 * @param collectionOptions Collection options1367 * @param label 1368 * @example 1369 * mintCollection(aliceKeyring, {1370 * name: 'New',1371 * description: 'New collection',1372 * tokenPrefix: 'NEW',1373 * })1374 * @returns object of the created collection1375 */1376 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {1377 return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;1378 }13791380 /**1381 * Mint new token1382 * @param signer keyring of signer1383 * @param data token data1384 * @param label 1385 * @returns created token object1386 */1387 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {1388 if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1389 const creationResult = await this.helper.executeExtrinsic(1390 signer,1391 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1392 nft: {1393 properties: data.properties,1394 },1395 }],1396 true, `Unable to mint NFT token for ${label}`,1397 );1398 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1399 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1400 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1401 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1402 }14031404 /**1405 * Mint multiple NFT tokens1406 * @param signer keyring of signer1407 * @param collectionId ID of collection1408 * @param tokens array of tokens with owner and properties1409 * @param label 1410 * @example 1411 * mintMultipleTokens(aliceKeyring, 10, [{1412 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1413 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1414 * },{1415 * owner: {Ethereum: "0x9F0583DbB855d..."},1416 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1417 * }]);1418 * @returns ```true``` if extrinsic success, otherwise ```false```1419 */1420 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1421 if(typeof label === 'undefined') label = `collection #${collectionId}`;1422 const creationResult = await this.helper.executeExtrinsic(1423 signer,1424 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1425 true, `Unable to mint NFT tokens for ${label}`,1426 );1427 const collection = this.getCollectionObject(collectionId);1428 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1429 }14301431 /**1432 * Mint multiple NFT tokens with one owner1433 * @param signer keyring of signer1434 * @param collectionId ID of collection1435 * @param owner tokens owner1436 * @param tokens array of tokens with owner and properties1437 * @param label 1438 * @example1439 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1440 * properties: [{1441 * key: "gender",1442 * value: "female",1443 * },{1444 * key: "age",1445 * value: "33",1446 * }],1447 * }]);1448 * @returns array of newly created tokens1449 */1450 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1451 if(typeof label === 'undefined') label = `collection #${collectionId}`;1452 const rawTokens = [];1453 for (const token of tokens) {1454 const raw = {NFT: {properties: token.properties}};1455 rawTokens.push(raw);1456 }1457 const creationResult = await this.helper.executeExtrinsic(1458 signer,1459 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1460 true, `Unable to mint NFT tokens for ${label}`,1461 );1462 const collection = this.getCollectionObject(collectionId);1463 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1464 }14651466 /**1467 * Destroys a concrete instance of NFT.1468 * @param signer keyring of signer1469 * @param collectionId ID of collection1470 * @param tokenId ID of token1471 * @param label 1472 * @example burnToken(aliceKeyring, 10, 5);1473 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1474 */1475 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {1476 return await super.burnToken(signer, collectionId, tokenId, label, 1n);1477 }14781479 /**1480 * Set, change, or remove approved address to transfer the ownership of the NFT.1481 * 1482 * @param signer keyring of signer1483 * @param collectionId ID of collection1484 * @param tokenId ID of token1485 * @param toAddressObj address to approve1486 * @param label 1487 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1488 * @returns ```true``` if extrinsic success, otherwise ```false```1489 */1490 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {1491 return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, 1n);1492 }1493}149414951496class RFTGroup extends NFTnRFT {1497 /**1498 * Get collection object1499 * @param collectionId ID of collection1500 * @example getCollectionObject(2);1501 * @returns instance of UniqueRFTCollection1502 */1503 getCollectionObject(collectionId: number): UniqueRFTCollection {1504 return new UniqueRFTCollection(collectionId, this.helper);1505 }15061507 /**1508 * Get token object1509 * @param collectionId ID of collection1510 * @param tokenId ID of token1511 * @example getTokenObject(10, 5);1512 * @returns instance of UniqueNFTToken1513 */1514 getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {1515 return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));1516 }15171518 /**1519 * Get top 10 token owners with the largest number of pieces 1520 * @param collectionId ID of collection1521 * @param tokenId ID of token1522 * @example getTokenTop10Owners(10, 5);1523 * @returns array of top 10 owners1524 */1525 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {1526 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);1527 }15281529 /**1530 * Get number of pieces owned by address1531 * @param collectionId ID of collection1532 * @param tokenId ID of token1533 * @param addressObj address token owner1534 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1535 * @returns number of pieces ownerd by address1536 */1537 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1538 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1539 }15401541 /**1542 * Transfer pieces of token to another address1543 * @param signer keyring of signer1544 * @param collectionId ID of collection1545 * @param tokenId ID of token1546 * @param addressObj address of a new owner1547 * @param amount number of pieces to be transfered1548 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1549 * @returns ```true``` if extrinsic success, otherwise ```false```1550 */1551 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {1552 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1553 }15541555 /**1556 * Change ownership of some pieces of RFT on behalf of the owner. 1557 * @param signer keyring of signer1558 * @param collectionId ID of collection1559 * @param tokenId ID of token1560 * @param fromAddressObj address on behalf of which the token will be sent1561 * @param toAddressObj new token owner1562 * @param amount number of pieces to be transfered1563 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1564 * @returns ```true``` if extrinsic success, otherwise ```false```1565 */1566 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {1567 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1568 }15691570 /**1571 * Mint new collection1572 * @param signer keyring of signer1573 * @param collectionOptions Collection options1574 * @param label 1575 * @example1576 * mintCollection(aliceKeyring, {1577 * name: 'New',1578 * description: 'New collection',1579 * tokenPrefix: 'NEW',1580 * })1581 * @returns object of the created collection1582 */1583 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {1584 return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;1585 }15861587 /**1588 * Mint new token1589 * @param signer keyring of signer1590 * @param data token data1591 * @param label 1592 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1593 * @returns created token object1594 */1595 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {1596 if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1597 const creationResult = await this.helper.executeExtrinsic(1598 signer,1599 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1600 refungible: {1601 pieces: data.pieces,1602 properties: data.properties,1603 },1604 }],1605 true, `Unable to mint RFT token for ${label}`,1606 );1607 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1608 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1609 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1610 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1611 }16121613 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1614 throw Error('Not implemented');1615 if(typeof label === 'undefined') label = `collection #${collectionId}`;1616 const creationResult = await this.helper.executeExtrinsic(1617 signer,1618 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1619 true, `Unable to mint RFT tokens for ${label}`,1620 );1621 const collection = this.getCollectionObject(collectionId);1622 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1623 }16241625 /**1626 * Mint multiple RFT tokens with one owner1627 * @param signer keyring of signer1628 * @param collectionId ID of collection1629 * @param owner tokens owner1630 * @param tokens array of tokens with properties and pieces1631 * @param label 1632 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1633 * @returns array of newly created RFT tokens1634 */1635 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1636 if(typeof label === 'undefined') label = `collection #${collectionId}`;1637 const rawTokens = [];1638 for (const token of tokens) {1639 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1640 rawTokens.push(raw);1641 }1642 const creationResult = await this.helper.executeExtrinsic(1643 signer,1644 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1645 true, `Unable to mint RFT tokens for ${label}`,1646 );1647 const collection = this.getCollectionObject(collectionId);1648 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1649 }16501651 /**1652 * Destroys a concrete instance of RFT.1653 * @param signer keyring of signer1654 * @param collectionId ID of collection1655 * @param tokenId ID of token1656 * @param label 1657 * @param amount number of pieces to be burnt1658 * @example burnToken(aliceKeyring, 10, 5);1659 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1660 */1661 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {1662 return await super.burnToken(signer, collectionId, tokenId, label, amount);1663 }16641665 /**1666 * Set, change, or remove approved address to transfer the ownership of the RFT.1667 * 1668 * @param signer keyring of signer1669 * @param collectionId ID of collection1670 * @param tokenId ID of token1671 * @param toAddressObj address to approve1672 * @param label 1673 * @param amount number of pieces to be approved1674 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1675 * @returns true if the token success, otherwise false1676 */1677 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {1678 return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1679 }16801681 /**1682 * Get total number of pieces1683 * @param collectionId ID of collection1684 * @param tokenId ID of token1685 * @example getTokenTotalPieces(10, 5);1686 * @returns number of pieces1687 */1688 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1689 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1690 }16911692 /**1693 * Change number of token pieces. Signer must be the owner of all token pieces.1694 * @param signer keyring of signer1695 * @param collectionId ID of collection1696 * @param tokenId ID of token1697 * @param amount new number of pieces1698 * @param label 1699 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1700 * @returns true if the repartion was success, otherwise false1701 */1702 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {1703 if(typeof label === 'undefined') label = `collection #${collectionId}`;1704 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1705 const repartitionResult = await this.helper.executeExtrinsic(1706 signer,1707 'api.tx.unique.repartition', [collectionId, tokenId, amount],1708 true, `Unable to repartition RFT token for ${label}`,1709 );1710 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);1711 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);1712 }1713}171417151716class FTGroup extends CollectionGroup {1717 /**1718 * Get collection object1719 * @param collectionId ID of collection1720 * @example getCollectionObject(2);1721 * @returns instance of UniqueFTCollection1722 */1723 getCollectionObject(collectionId: number): UniqueFTCollection {1724 return new UniqueFTCollection(collectionId, this.helper);1725 }17261727 /**1728 * Mint new fungible collection1729 * @param signer keyring of signer1730 * @param collectionOptions Collection options1731 * @param decimalPoints number of token decimals 1732 * @param errorLabel 1733 * @example1734 * mintCollection(aliceKeyring, {1735 * name: 'New',1736 * description: 'New collection',1737 * tokenPrefix: 'NEW',1738 * }, 18)1739 * @returns newly created fungible collection1740 */1741 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {1742 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1743 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1744 collectionOptions.mode = {fungible: decimalPoints};1745 for (const key of ['name', 'description', 'tokenPrefix']) {1746 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);1747 }1748 const creationResult = await this.helper.executeExtrinsic(1749 signer,1750 'api.tx.unique.createCollectionEx', [collectionOptions],1751 true, errorLabel,1752 );1753 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1754 }17551756 /**1757 * Mint tokens1758 * @param signer keyring of signer1759 * @param collectionId ID of collection1760 * @param owner address owner of new tokens1761 * @param amount amount of tokens to be meanted1762 * @param label 1763 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1764 * @returns ```true``` if extrinsic success, otherwise ```false``` 1765 */1766 async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {1767 if(typeof label === 'undefined') label = `collection #${collectionId}`;1768 const creationResult = await this.helper.executeExtrinsic(1769 signer,1770 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1771 fungible: {1772 value: amount,1773 },1774 }],1775 true, `Unable to mint fungible tokens for ${label}`,1776 );1777 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1778 }17791780 /**1781 * Mint multiple Fungible tokens with one owner1782 * @param signer keyring of signer1783 * @param collectionId ID of collection1784 * @param owner tokens owner1785 * @param tokens array of tokens with properties and pieces1786 * @param label 1787 * @returns ```true``` if extrinsic success, otherwise ```false``` 1788 */1789 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {1790 if(typeof label === 'undefined') label = `collection #${collectionId}`;1791 const rawTokens = [];1792 for (const token of tokens) {1793 const raw = {Fungible: {Value: token.value}};1794 rawTokens.push(raw);1795 }1796 const creationResult = await this.helper.executeExtrinsic(1797 signer,1798 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1799 true, `Unable to mint RFT tokens for ${label}`,1800 );1801 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1802 }18031804 /**1805 * Get the top 10 owners with the largest balance for the Fungible collection 1806 * @param collectionId ID of collection1807 * @example getTop10Owners(10);1808 * @returns array of ```ICrossAccountId```1809 */1810 async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {1811 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);1812 }18131814 /**1815 * Get account balance1816 * @param collectionId ID of collection1817 * @param addressObj address of owner1818 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})1819 * @returns amount of fungible tokens owned by address1820 */1821 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1822 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1823 }18241825 /**1826 * Transfer tokens to address1827 * @param signer keyring of signer1828 * @param collectionId ID of collection1829 * @param toAddressObj address recepient1830 * @param amount amount of tokens to be sent1831 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1832 * @returns ```true``` if extrinsic success, otherwise ```false``` 1833 */1834 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {1835 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1836 }18371838 /**1839 * Transfer some tokens on behalf of the owner.1840 * @param signer keyring of signer1841 * @param collectionId ID of collection1842 * @param fromAddressObj address on behalf of which tokens will be sent1843 * @param toAddressObj address where token to be sent1844 * @param amount number of tokens to be sent1845 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);1846 * @returns ```true``` if extrinsic success, otherwise ```false``` 1847 */1848 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {1849 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1850 }18511852 /**1853 * Destroy some amount of tokens1854 * @param signer keyring of signer1855 * @param collectionId ID of collection1856 * @param amount amount of tokens to be destroyed1857 * @param label 1858 * @example burnTokens(aliceKeyring, 10, 1000n);1859 * @returns ```true``` if extrinsic success, otherwise ```false``` 1860 */1861 async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {1862 return (await super.burnToken(signer, collectionId, 0, label, amount)).success;1863 }18641865 /**1866 * Burn some tokens on behalf of the owner.1867 * @param signer keyring of signer1868 * @param collectionId ID of collection1869 * @param fromAddressObj address on behalf of which tokens will be burnt1870 * @param amount amount of tokens to be burnt1871 * @param label 1872 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1873 * @returns ```true``` if extrinsic success, otherwise ```false``` 1874 */1875 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {1876 return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);1877 }18781879 /**1880 * Get total collection supply1881 * @param collectionId 1882 * @returns 1883 */1884 async getTotalPieces(collectionId: number): Promise<bigint> {1885 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();1886 }18871888 /**1889 * Set, change, or remove approved address to transfer tokens.1890 * 1891 * @param signer keyring of signer1892 * @param collectionId ID of collection1893 * @param toAddressObj address to be approved1894 * @param amount amount of tokens to be approved1895 * @param label 1896 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)1897 * @returns ```true``` if extrinsic success, otherwise ```false``` 1898 */1899 async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {1900 return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);1901 }19021903 /**1904 * Get amount of fungible tokens approved to transfer1905 * @param collectionId ID of collection1906 * @param fromAddressObj owner of tokens1907 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner1908 * @returns number of tokens approved for the transfer1909 */1910 async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {1911 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);1912 }1913}191419151916class ChainGroup extends HelperGroup {1917 /**1918 * Get system properties of a chain1919 * @example getChainProperties();1920 * @returns ss58Format, token decimals, and token symbol1921 */1922 getChainProperties(): IChainProperties {1923 const properties = (this.helper.api as any).registry.getChainProperties().toJSON();1924 return {1925 ss58Format: properties.ss58Format.toJSON(),1926 tokenDecimals: properties.tokenDecimals.toJSON(),1927 tokenSymbol: properties.tokenSymbol.toJSON(),1928 };1929 }19301931 /**1932 * Get chain header1933 * @example getLatestBlockNumber();1934 * @returns the number of the last block1935 */1936 async getLatestBlockNumber(): Promise<number> {1937 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();1938 }19391940 /**1941 * Get block hash by block number1942 * @param blockNumber number of block1943 * @example getBlockHashByNumber(12345);1944 * @returns hash of a block1945 */1946 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {1947 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();1948 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;1949 return blockHash;1950 }19511952 /**1953 * Get account nonce1954 * @param address substrate address1955 * @example getNonce("5GrwvaEF5zXb26Fz...");1956 * @returns number, account's nonce1957 */1958 async getNonce(address: TSubstrateAccount): Promise<number> {1959 return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber();1960 }1961}196219631964class BalanceGroup extends HelperGroup {1965 /**1966 * Representation of the native token in the smallest unit1967 * @example getOneTokenNominal()1968 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.1969 */1970 getOneTokenNominal(): bigint {1971 const chainProperties = this.helper.chain.getChainProperties();1972 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);1973 }19741975 /**1976 * Get substrate address balance1977 * @param address substrate address1978 * @example getSubstrate("5GrwvaEF5zXb26Fz...")1979 * @returns amount of tokens on address1980 */1981 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {1982 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();1983 }19841985 /**1986 * Get ethereum address balance1987 * @param address ethereum address1988 * @example getEthereum("0x9F0583DbB855d...")1989 * @returns amount of tokens on address1990 */1991 async getEthereum(address: TEthereumAccount): Promise<bigint> {1992 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();1993 }19941995 /**1996 * Transfer tokens to substrate address1997 * @param signer keyring of signer1998 * @param address substrate address of a recepient1999 * @param amount amount of tokens to be transfered2000 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2001 * @returns ```true``` if extrinsic success, otherwise ```false```2002 */2003 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2004 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}`);20052006 let transfer = {from: null, to: null, amount: 0n} as any;2007 result.result.events.forEach(({event: {data, method, section}}) => {2008 if ((section === 'balances') && (method === 'Transfer')) {2009 transfer = {2010 from: this.helper.address.normalizeSubstrate(data[0]),2011 to: this.helper.address.normalizeSubstrate(data[1]),2012 amount: BigInt(data[2]),2013 };2014 }2015 });2016 let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;2017 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;2018 isSuccess = isSuccess && BigInt(amount) === transfer.amount;2019 return isSuccess;2020 }2021}202220232024class AddressGroup extends HelperGroup {2025 /**2026 * Normalizes the address to the specified ss58 format, by default ```42```.2027 * @param address substrate address2028 * @param ss58Format format for address conversion, by default ```42```2029 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2030 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2031 */2032 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2033 return this.helper.util.normalizeSubstrateAddress(address, ss58Format);2034 }20352036 /**2037 * Get address in the connected chain format2038 * @param address substrate address2039 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2040 * @returns address in chain format2041 */2042 async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise<TSubstrateAccount> {2043 const info = this.helper.chain.getChainProperties();2044 return encodeAddress(decodeAddress(address), info.ss58Format);2045 }20462047 /**2048 * Get substrate mirror of an ethereum address2049 * @param ethAddress ethereum address2050 * @param toChainFormat false for normalized account2051 * @example ethToSubstrate('0x9F0583DbB855d...')2052 * @returns substrate mirror of a provided ethereum address2053 */2054 async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise<TSubstrateAccount> {2055 if(!toChainFormat) return evmToAddress(ethAddress);2056 const info = this.helper.chain.getChainProperties();2057 return evmToAddress(ethAddress, info.ss58Format);2058 }20592060 /**2061 * Get ethereum mirror of a substrate address2062 * @param subAddress substrate account2063 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2064 * @returns ethereum mirror of a provided substrate address2065 */2066 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2067 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join(''));2068 }2069}207020712072export class UniqueHelper extends ChainHelperBase {2073 chain: ChainGroup;2074 balance: BalanceGroup;2075 address: AddressGroup;2076 collection: CollectionGroup;2077 nft: NFTGroup;2078 rft: RFTGroup;2079 ft: FTGroup;20802081 constructor(logger?: ILogger) {2082 super(logger);2083 this.chain = new ChainGroup(this);2084 this.balance = new BalanceGroup(this);2085 this.address = new AddressGroup(this);2086 this.collection = new CollectionGroup(this);2087 this.nft = new NFTGroup(this);2088 this.rft = new RFTGroup(this);2089 this.ft = new FTGroup(this);2090 } 2091}209220932094class UniqueCollectionBase {2095 helper: UniqueHelper;2096 collectionId: number;20972098 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2099 this.collectionId = collectionId;2100 this.helper = uniqueHelper;2101 }21022103 async getData() {2104 return await this.helper.collection.getData(this.collectionId);2105 }21062107 async getLastTokenId() {2108 return await this.helper.collection.getLastTokenId(this.collectionId);2109 }21102111 async isTokenExists(tokenId: number) {2112 return await this.helper.collection.isTokenExists(this.collectionId, tokenId);2113 }21142115 async getAdmins() {2116 return await this.helper.collection.getAdmins(this.collectionId);2117 }21182119 async getEffectiveLimits() {2120 return await this.helper.collection.getEffectiveLimits(this.collectionId);2121 }21222123 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) {2124 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label);2125 }21262127 async confirmSponsorship(signer: TSigner, label?: string) {2128 return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label);2129 }21302131 async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) {2132 return await this.helper.collection.setLimits(signer, this.collectionId, limits, label);2133 }21342135 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) {2136 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label);2137 }21382139 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2140 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label);2141 }21422143 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2144 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label);2145 }21462147 async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2148 return await this.helper.collection.setProperties(signer, this.collectionId, properties, label);2149 }21502151 async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2152 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label);2153 }21542155 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2156 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2157 }21582159 async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) {2160 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label);2161 }21622163 async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) {2164 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label);2165 }21662167 async disableNesting(signer: TSigner, label?: string) {2168 return await this.helper.collection.disableNesting(signer, this.collectionId, label);2169 }21702171 async burn(signer: TSigner, label?: string) {2172 return await this.helper.collection.burn(signer, this.collectionId, label);2173 }2174}217521762177class UniqueNFTCollection extends UniqueCollectionBase {2178 getTokenObject(tokenId: number) {2179 return new UniqueNFTToken(tokenId, this);2180 }21812182 async getTokensByAddress(addressObj: ICrossAccountId) {2183 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);2184 }21852186 async getToken(tokenId: number, blockHashAt?: string) {2187 return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt);2188 }21892190 async getTokenOwner(tokenId: number, blockHashAt?: string) {2191 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);2192 }21932194 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {2195 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);2196 }21972198 async getTokenChildren(tokenId: number, blockHashAt?: string) {2199 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2200 }22012202 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2203 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2204 }22052206 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2207 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);2208 }22092210 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {2211 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label);2212 }22132214 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {2215 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);2216 }22172218 async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) {2219 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label);2220 }22212222 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) {2223 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label);2224 }22252226 async burnToken(signer: TSigner, tokenId: number, label?: string) {2227 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label);2228 }22292230 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2231 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2232 }22332234 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2235 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2236 }22372238 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2239 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2240 }22412242 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) {2243 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label);2244 }22452246 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2247 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label);2248 }2249}225022512252class UniqueRFTCollection extends UniqueCollectionBase {2253 getTokenObject(tokenId: number) {2254 return new UniqueRFTToken(tokenId, this);2255 }22562257 async getTokensByAddress(addressObj: ICrossAccountId) {2258 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2259 }22602261 async getTop10TokenOwners(tokenId: number) {2262 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);2263 }22642265 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {2266 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);2267 }22682269 async getTokenTotalPieces(tokenId: number) {2270 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2271 }22722273 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) {2274 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2275 }22762277 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2278 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);2279 }22802281 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2282 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount);2283 }22842285 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2286 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);2287 }22882289 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) {2290 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label);2291 }22922293 async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) {2294 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label);2295 }22962297 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) {2298 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label);2299 }23002301 async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) {2302 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount);2303 }23042305 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2306 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2307 }23082309 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2310 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2311 }23122313 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2314 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2315 }2316}231723182319class UniqueFTCollection extends UniqueCollectionBase {2320 async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) {2321 return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label);2322 }23232324 async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) {2325 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label);2326 }23272328 async getBalance(addressObj: ICrossAccountId) {2329 return await this.helper.ft.getBalance(this.collectionId, addressObj);2330 }23312332 async getTop10Owners() {2333 return await this.helper.ft.getTop10Owners(this.collectionId);2334 }23352336 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) {2337 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);2338 }23392340 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {2341 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);2342 }23432344 async burnTokens(signer: TSigner, amount: bigint, label?: string) {2345 return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label);2346 }23472348 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) {2349 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label);2350 }23512352 async getTotalPieces() {2353 return await this.helper.ft.getTotalPieces(this.collectionId);2354 }23552356 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2357 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label);2358 }23592360 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2361 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);2362 }2363}236423652366class UniqueTokenBase implements IToken {2367 collection: UniqueNFTCollection | UniqueRFTCollection;2368 collectionId: number;2369 tokenId: number;23702371 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {2372 this.collection = collection;2373 this.collectionId = collection.collectionId;2374 this.tokenId = tokenId;2375 }23762377 async getNextSponsored(addressObj: ICrossAccountId) {2378 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2379 }23802381 async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2382 return await this.collection.setTokenProperties(signer, this.tokenId, properties, label);2383 }23842385 async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2386 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label);2387 }2388}238923902391class UniqueNFTToken extends UniqueTokenBase {2392 collection: UniqueNFTCollection;23932394 constructor(tokenId: number, collection: UniqueNFTCollection) {2395 super(tokenId, collection);2396 this.collection = collection;2397 }23982399 async getData(blockHashAt?: string) {2400 return await this.collection.getToken(this.tokenId, blockHashAt);2401 }24022403 async getOwner(blockHashAt?: string) {2404 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);2405 }24062407 async getTopmostOwner(blockHashAt?: string) {2408 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);2409 }24102411 async getChildren(blockHashAt?: string) {2412 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);2413 }24142415 async nest(signer: TSigner, toTokenObj: IToken, label?: string) {2416 return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label);2417 }24182419 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2420 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label);2421 }24222423 async transfer(signer: TSigner, addressObj: ICrossAccountId) {2424 return await this.collection.transferToken(signer, this.tokenId, addressObj);2425 }24262427 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2428 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);2429 }24302431 async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) {2432 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label);2433 }24342435 async isApproved(toAddressObj: ICrossAccountId) {2436 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);2437 }24382439 async burn(signer: TSigner, label?: string) {2440 return await this.collection.burnToken(signer, this.tokenId, label);2441 }2442}24432444class UniqueRFTToken extends UniqueTokenBase {2445 collection: UniqueRFTCollection;24462447 constructor(tokenId: number, collection: UniqueRFTCollection) {2448 super(tokenId, collection);2449 this.collection = collection;2450 }24512452 async getTop10Owners() {2453 return await this.collection.getTop10TokenOwners(this.tokenId);2454 }24552456 async getBalance(addressObj: ICrossAccountId) {2457 return await this.collection.getTokenBalance(this.tokenId, addressObj);2458 }24592460 async getTotalPieces() {2461 return await this.collection.getTokenTotalPieces(this.tokenId);2462 }24632464 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) {2465 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);2466 }24672468 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2469 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);2470 }24712472 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2473 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label);2474 }24752476 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {2477 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);2478 }24792480 async repartition(signer: TSigner, amount: bigint, label?: string) {2481 return await this.collection.repartitionToken(signer, this.tokenId, amount, label);2482 }24832484 async burn(signer: TSigner, amount=100n, label?: string) {2485 return await this.collection.burnToken(signer, this.tokenId, amount, label);2486 }2487}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034/* eslint-disable @typescript-eslint/no-var-requires */5/* eslint-disable function-call-argument-newline */6/* eslint-disable no-prototype-builtins */78import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {ApiInterfaceEvents} from '@polkadot/api/types';10import {IKeyringPair} from '@polkadot/types/types';11import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';121314const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {15 const address = {} as ICrossAccountId;16 if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate;17 if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum;18 return address;19};202122const nesting = {23 toChecksumAddress(address: string): string {24 if (typeof address === 'undefined') return '';2526 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);2728 address = address.toLowerCase().replace(/^0x/i,'');29 const addressHash = keccakAsHex(address).replace(/^0x/i,'');30 const checksumAddress = ['0x'];3132 for (let i = 0; i < address.length; i++) {33 // If ith character is 8 to f then make it uppercase34 if (parseInt(addressHash[i], 16) > 7) {35 checksumAddress.push(address[i].toUpperCase());36 } else {37 checksumAddress.push(address[i]);38 }39 }40 return checksumAddress.join('');41 },42 tokenIdToAddress(collectionId: number, tokenId: number) {43 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);44 },45};464748interface IChainEvent {49 data: any;50 method: string;51 section: string;52}5354interface ITransactionResult {55 status: 'Fail' | 'Success';56 result: {57 events: {58 event: IChainEvent59 }[];60 },61 moduleError?: string;62}6364interface ILogger {65 log: (msg: any, level?: string) => void;66 level: {67 ERROR: 'ERROR';68 WARNING: 'WARNING';69 INFO: 'INFO';70 [key: string]: string;71 }72}7374interface IUniqueHelperLog {75 executedAt: number;76 executionTime: number;77 type: 'extrinsic' | 'rpc';78 status: 'Fail' | 'Success';79 call: string;80 params: any[];81 moduleError?: string;82 events?: any;83}8485interface IApiListeners {86 connected?: (...args: any[]) => any;87 disconnected?: (...args: any[]) => any;88 error?: (...args: any[]) => any;89 ready?: (...args: any[]) => any; 90 decorated?: (...args: any[]) => any;91}9293interface ICrossAccountId {94 Substrate?: TSubstrateAccount;95 Ethereum?: TEthereumAccount;96}9798interface ICrossAccountIdLower {99 substrate?: TSubstrateAccount;100 ethereum?: TEthereumAccount;101}102103interface ICollectionLimits {104 accountTokenOwnershipLimit?: number | null;105 sponsoredDataSize?: number | null;106 sponsoredDataRateLimit?: {blocks: number} | {sponsoringDisabled: null} | null;107 tokenLimit?: number | null;108 sponsorTransferTimeout?: number | null;109 sponsorApproveTimeout?: number | null;110 ownerCanTransfer?: boolean | null;111 ownerCanDestroy?: boolean | null;112 transfersEnabled?: boolean | null;113}114115interface INestingPermissions {116 tokenOwner?: boolean;117 collectionAdmin?: boolean;118 restricted?: number[] | null;119}120121interface ICollectionPermissions {122 access?: 'Normal' | 'AllowList';123 mintMode?: boolean;124 nesting?: INestingPermissions;125}126127interface IProperty {128 key: string;129 value: string;130}131132interface ITokenPropertyPermission {133 key: string;134 permission: {135 mutable: boolean;136 tokenOwner: boolean;137 collectionAdmin: boolean;138 }139}140141interface IToken {142 collectionId: number;143 tokenId: number;144}145146interface ICollectionCreationOptions {147 name: string | number[];148 description: string | number[];149 tokenPrefix: string | number[];150 mode?: {151 nft?: null;152 refungible?: null;153 fungible?: number;154 }155 permissions?: ICollectionPermissions;156 properties?: IProperty[];157 tokenPropertyPermissions?: ITokenPropertyPermission[];158 limits?: ICollectionLimits;159 pendingSponsor?: TSubstrateAccount;160}161162interface IChainProperties {163 ss58Format: number;164 tokenDecimals: number[];165 tokenSymbol: string[]166}167168type TSubstrateAccount = string;169type TEthereumAccount = string;170type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';171type TUniqueNetworks = 'opal' | 'quartz' | 'unique';172type TSigner = IKeyringPair; // | 'string'173174class UniqueUtil {175 static transactionStatus = {176 NOT_READY: 'NotReady',177 FAIL: 'Fail',178 SUCCESS: 'Success',179 };180181 static chainLogType = {182 EXTRINSIC: 'extrinsic',183 RPC: 'rpc',184 };185186 static getNestingTokenAddress(collectionId: number, tokenId: number) {187 return nesting.tokenIdToAddress(collectionId, tokenId);188 }189190 static getDefaultLogger(): ILogger {191 return {192 log(msg: any, level = 'INFO') {193 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));194 },195 level: {196 ERROR: 'ERROR',197 WARNING: 'WARNING',198 INFO: 'INFO',199 },200 };201 }202203 static vec2str(arr: string[] | number[]) {204 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');205 }206207 static str2vec(string: string) {208 if (typeof string !== 'string') return string;209 return Array.from(string).map(x => x.charCodeAt(0));210 }211212 static fromSeed(seed: string, ss58Format = 42) {213 const keyring = new Keyring({type: 'sr25519', ss58Format});214 return keyring.addFromUri(seed);215 }216217 static normalizeSubstrateAddress(address: string, ss58Format = 42) {218 return encodeAddress(decodeAddress(address), ss58Format);219 }220221 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') {222 if (creationResult.status !== this.transactionStatus.SUCCESS) {223 throw Error(`Unable to create collection for ${label}`);224 }225226 let collectionId = null;227 creationResult.result.events.forEach(({event: {data, method, section}}) => {228 if ((section === 'common') && (method === 'CollectionCreated')) {229 collectionId = parseInt(data[0].toString(), 10);230 }231 });232233 if (collectionId === null) {234 throw Error(`No CollectionCreated event for ${label}`);235 }236237 return collectionId;238 }239240 static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') {241 if (creationResult.status !== this.transactionStatus.SUCCESS) {242 throw Error(`Unable to create tokens for ${label}`);243 }244 let success = false;245 const tokens = [] as any;246 creationResult.result.events.forEach(({event: {data, method, section}}) => {247 if (method === 'ExtrinsicSuccess') {248 success = true;249 } else if ((section === 'common') && (method === 'ItemCreated')) {250 tokens.push({251 collectionId: parseInt(data[0].toString(), 10),252 tokenId: parseInt(data[1].toString(), 10),253 owner: data[2].toJSON(),254 });255 }256 });257 return {success, tokens};258 }259260 static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') {261 if (burnResult.status !== this.transactionStatus.SUCCESS) {262 throw Error(`Unable to burn tokens for ${label}`);263 }264 let success = false;265 const tokens = [] as any;266 burnResult.result.events.forEach(({event: {data, method, section}}) => {267 if (method === 'ExtrinsicSuccess') {268 success = true;269 } else if ((section === 'common') && (method === 'ItemDestroyed')) {270 tokens.push({271 collectionId: parseInt(data[0].toString(), 10),272 tokenId: parseInt(data[1].toString(), 10),273 owner: data[2].toJSON(),274 });275 }276 });277 return {success, tokens};278 }279280 static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) {281 let eventId = null;282 events.forEach(({event: {data, method, section}}) => {283 if ((section === expectedSection) && (method === expectedMethod)) {284 eventId = parseInt(data[0].toString(), 10);285 }286 });287288 if (eventId === null) {289 throw Error(`No ${expectedMethod} event for ${label}`);290 }291 return eventId === collectionId;292 }293294 static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {295 const normalizeAddress = (address: string | ICrossAccountId) => {296 if(typeof address === 'string') return address;297 const obj = {} as any;298 Object.keys(address).forEach(k => {299 obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];300 });301 if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)};302 if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()};303 return address;304 };305 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;306 events.forEach(({event: {data, method, section}}) => {307 if ((section === 'common') && (method === 'Transfer')) {308 const hData = (data as any).toJSON();309 transfer = {310 collectionId: hData[0],311 tokenId: hData[1],312 from: normalizeAddress(hData[2]),313 to: normalizeAddress(hData[3]),314 amount: BigInt(hData[4]),315 };316 }317 });318 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;319 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);320 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);321 isSuccess = isSuccess && amount === transfer.amount;322 return isSuccess;323 }324}325326327class ChainHelperBase {328 transactionStatus = UniqueUtil.transactionStatus;329 chainLogType = UniqueUtil.chainLogType;330 util: typeof UniqueUtil;331 logger: ILogger;332 api: ApiPromise | null;333 forcedNetwork: TUniqueNetworks | null;334 network: TUniqueNetworks | null;335 chainLog: IUniqueHelperLog[];336337 constructor(logger?: ILogger) {338 this.util = UniqueUtil;339 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();340 this.logger = logger;341 this.api = null;342 this.forcedNetwork = null;343 this.network = null;344 this.chainLog = [];345 }346347 clearChainLog(): void {348 this.chainLog = [];349 }350351 forceNetwork(value: TUniqueNetworks): void {352 this.forcedNetwork = value;353 }354355 async connect(wsEndpoint: string, listeners?: IApiListeners) {356 if (this.api !== null) throw Error('Already connected');357 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);358 this.api = api;359 this.network = network;360 }361362 async disconnect() {363 if (this.api === null) return;364 await this.api.disconnect();365 this.api = null;366 this.network = null;367 }368369 static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {370 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;371 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;372 return 'opal';373 }374375 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {376 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});377 await api.isReady;378379 const network = await this.detectNetwork(api);380381 await api.disconnect();382383 return network;384 }385386 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{ 387 api: ApiPromise; 388 network: TUniqueNetworks; 389 }> {390 if(typeof network === 'undefined' || network === null) network = 'opal';391 const supportedRPC = {392 opal: {393 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,394 },395 quartz: {396 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,397 },398 unique: {399 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,400 },401 };402 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);403 const rpc = supportedRPC[network];404405 // TODO: investigate how to replace rpc in runtime406 // api._rpcCore.addUserInterfaces(rpc);407408 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});409410 await api.isReadyOrError;411412 if (typeof listeners === 'undefined') listeners = {};413 for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {414 if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;415 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);416 }417418 return {api, network};419 }420421 getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) {422 const {events, status} = data;423 if (status.isReady) {424 return this.transactionStatus.NOT_READY;425 }426 if (status.isBroadcast) {427 return this.transactionStatus.NOT_READY;428 }429 if (status.isInBlock || status.isFinalized) {430 const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed');431 if (errors.length > 0) {432 return this.transactionStatus.FAIL;433 }434 if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {435 return this.transactionStatus.SUCCESS;436 }437 }438439 return this.transactionStatus.FAIL;440 }441442 signTransaction(sender: TSigner, transaction: any, label = 'transaction', options = null) {443 const sign = (callback: any) => {444 if(options !== null) return transaction.signAndSend(sender, options, callback);445 return transaction.signAndSend(sender, callback);446 };447 return new Promise(async (resolve, reject) => {448 try {449 const unsub = await sign((result: any) => {450 const status = this.getTransactionStatus(result);451452 if (status === this.transactionStatus.SUCCESS) {453 this.logger.log(`${label} successful`);454 unsub();455 resolve({result, status});456 } else if (status === this.transactionStatus.FAIL) {457 let moduleError = null;458459 if (result.hasOwnProperty('dispatchError')) {460 const dispatchError = result['dispatchError'];461462 if (dispatchError && dispatchError.isModule) {463 const modErr = dispatchError.asModule;464 const errorMeta = dispatchError.registry.findMetaError(modErr);465466 moduleError = `${errorMeta.section}.${errorMeta.name}`;467 }468 else {469 this.logger.log(result, this.logger.level.ERROR);470 }471 }472473 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);474 unsub();475 reject({status, moduleError, result});476 }477 });478 } catch (e) {479 this.logger.log(e, this.logger.level.ERROR);480 reject(e);481 }482 });483 }484485 constructApiCall(apiCall: string, params: any[]) {486 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);487 let call = this.api as any;488 for(const part of apiCall.slice(4).split('.')) {489 call = call[part];490 }491 return call(...params);492 }493494 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') {495 if(this.api === null) throw Error('API not initialized');496 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);497498 const startTime = (new Date()).getTime();499 let result: ITransactionResult;500 let events = [];501 try {502 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult;503 events = result.result.events.map((x: any) => x.toHuman());504 }505 catch(e) {506 if(!(e as object).hasOwnProperty('status')) throw e;507 result = e as ITransactionResult;508 }509510 const endTime = (new Date()).getTime();511512 const log = {513 executedAt: endTime,514 executionTime: endTime - startTime,515 type: this.chainLogType.EXTRINSIC,516 status: result.status,517 call: extrinsic,518 params,519 } as IUniqueHelperLog;520521 if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError;522 if(events.length > 0) log.events = events;523524 this.chainLog.push(log);525526 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage);527 return result;528 }529530 async callRpc(rpc: string, params?: any[]) {531 if(typeof params === 'undefined') params = [];532 if(this.api === null) throw Error('API not initialized');533 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);534535 const startTime = (new Date()).getTime();536 let result;537 let error = null;538 const log = {539 type: this.chainLogType.RPC,540 call: rpc,541 params,542 } as IUniqueHelperLog;543544 try {545 result = await this.constructApiCall(rpc, params);546 }547 catch(e) {548 error = e;549 }550551 const endTime = (new Date()).getTime();552553 log.executedAt = endTime;554 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';555 log.executionTime = endTime - startTime;556557 this.chainLog.push(log);558559 if(error !== null) throw error;560561 return result;562 }563564 getSignerAddress(signer: IKeyringPair | string): string {565 if(typeof signer === 'string') return signer;566 return signer.address;567 }568}569570571class HelperGroup {572 helper: UniqueHelper;573574 constructor(uniqueHelper: UniqueHelper) {575 this.helper = uniqueHelper;576 }577}578579580class CollectionGroup extends HelperGroup {581 /**582 * Get number of blocks when sponsored transaction is available.583 *584 * @param collectionId ID of collection585 * @param tokenId ID of token586 * @param addressObj address for which the sponsorship is checked587 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});588 * @returns number of blocks or null if sponsorship hasn't been set589 */590 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {591 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();592 }593594 /**595 * Get the number of created collections.596 * 597 * @returns number of created collections598 */599 async getTotalCount(): Promise<number> {600 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();601 }602603 /**604 * Get information about the collection with additional data, including the number of tokens it contains, its administrators, the normalized address of the collection's owner, and decoded name and description.605 * 606 * @param collectionId ID of collection607 * @example await getData(2)608 * @returns collection information object609 */610 async getData(collectionId: number): Promise<{611 id: number;612 name: string;613 description: string;614 tokensCount: number;615 admins: ICrossAccountId[];616 normalizedOwner: TSubstrateAccount;617 raw: any618 } | null> {619 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);620 const humanCollection = collection.toHuman(), collectionData = {621 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],622 raw: humanCollection,623 } as any, jsonCollection = collection.toJSON();624 if (humanCollection === null) return null;625 collectionData.raw.limits = jsonCollection.limits;626 collectionData.raw.permissions = jsonCollection.permissions;627 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);628 for (const key of ['name', 'description']) {629 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);630 }631632 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0;633 collectionData.admins = await this.getAdmins(collectionId);634635 return collectionData;636 }637638 /**639 * Get the normalized addresses of the collection's administrators.640 * 641 * @param collectionId ID of collection642 * @example await getAdmins(1)643 * @returns array of administrators644 */645 async getAdmins(collectionId: number): Promise<ICrossAccountId[]> {646 const normalized = [];647 for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) {648 if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)});649 else normalized.push(admin);650 }651 return normalized;652 }653654 /**655 * Get the effective limits of the collection instead of null for default values656 * 657 * @param collectionId ID of collection658 * @example await getEffectiveLimits(2)659 * @returns object of collection limits660 */661 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {662 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();663 }664665 /**666 * Burns the collection if the signer has sufficient permissions and collection is empty.667 * 668 * @param signer keyring of signer669 * @param collectionId ID of collection670 * @param label extra label for log671 * @example await helper.collection.burn(aliceKeyring, 3);672 * @returns ```true``` if extrinsic success, otherwise ```false```673 */674 async burn(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {675 if(typeof label === 'undefined') label = `collection #${collectionId}`;676 const result = await this.helper.executeExtrinsic(677 signer,678 'api.tx.unique.destroyCollection', [collectionId],679 true, `Unable to burn collection for ${label}`,680 );681682 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label);683 }684685 /**686 * Sets the sponsor for the collection (Requires the Substrate address).687 * 688 * @param signer keyring of signer689 * @param collectionId ID of collection690 * @param sponsorAddress Sponsor substrate address691 * @param label extra label for log692 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")693 * @returns ```true``` if extrinsic success, otherwise ```false```694 */695 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise<boolean> {696 if(typeof label === 'undefined') label = `collection #${collectionId}`;697 const result = await this.helper.executeExtrinsic(698 signer,699 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],700 true, `Unable to set collection sponsor for ${label}`,701 );702703 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label);704 }705706 /**707 * Confirms consent to sponsor the collection on behalf of the signer.708 * 709 * @param signer keyring of signer710 * @param collectionId ID of collection711 * @param label extra label for log712 * @example confirmSponsorship(aliceKeyring, 10)713 * @returns ```true``` if extrinsic success, otherwise ```false```714 */715 async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {716 if(typeof label === 'undefined') label = `collection #${collectionId}`;717 const result = await this.helper.executeExtrinsic(718 signer,719 'api.tx.unique.confirmSponsorship', [collectionId],720 true, `Unable to confirm collection sponsorship for ${label}`,721 );722723 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label);724 }725726 /**727 * Sets the limits of the collection. At least one limit must be specified for a correct call.728 * 729 * @param signer keyring of signer730 * @param collectionId ID of collection731 * @param limits collection limits object732 * @param label extra label for log733 * @example734 * await setLimits(735 * aliceKeyring,736 * 10,737 * {738 * sponsorTransferTimeout: 0,739 * ownerCanDestroy: false740 * }741 * )742 * @returns ```true``` if extrinsic success, otherwise ```false```743 */744 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise<boolean> {745 if(typeof label === 'undefined') label = `collection #${collectionId}`;746 const result = await this.helper.executeExtrinsic(747 signer,748 'api.tx.unique.setCollectionLimits', [collectionId, limits],749 true, `Unable to set collection limits for ${label}`,750 );751752 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label);753 }754755 /**756 * Changes the owner of the collection to the new Substrate address.757 * 758 * @param signer keyring of signer759 * @param collectionId ID of collection760 * @param ownerAddress substrate address of new owner761 * @param label extra label for log762 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")763 * @returns ```true``` if extrinsic success, otherwise ```false```764 */765 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise<boolean> {766 if(typeof label === 'undefined') label = `collection #${collectionId}`;767 const result = await this.helper.executeExtrinsic(768 signer,769 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],770 true, `Unable to change collection owner for ${label}`,771 );772773 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label);774 }775776 /**777 * Adds a collection administrator. 778 * 779 * @param signer keyring of signer780 * @param collectionId ID of collection781 * @param adminAddressObj Administrator address (substrate or ethereum)782 * @param label extra label for log783 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})784 * @returns ```true``` if extrinsic success, otherwise ```false```785 */786 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {787 if(typeof label === 'undefined') label = `collection #${collectionId}`;788 const result = await this.helper.executeExtrinsic(789 signer,790 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],791 true, `Unable to add collection admin for ${label}`,792 );793794 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label);795 }796797 /**798 * Removes a collection administrator.799 * 800 * @param signer keyring of signer801 * @param collectionId ID of collection802 * @param adminAddressObj Administrator address (substrate or ethereum)803 * @param label extra label for log804 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})805 * @returns ```true``` if extrinsic success, otherwise ```false```806 */807 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {808 if(typeof label === 'undefined') label = `collection #${collectionId}`;809 const result = await this.helper.executeExtrinsic(810 signer,811 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],812 true, `Unable to remove collection admin for ${label}`,813 );814815 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label);816 }817818 /**819 * Sets onchain permissions for selected collection.820 * 821 * @param signer keyring of signer822 * @param collectionId ID of collection823 * @param permissions collection permissions object824 * @param label extra label for log825 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});826 * @returns ```true``` if extrinsic success, otherwise ```false```827 */828 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise<boolean> {829 if(typeof label === 'undefined') label = `collection #${collectionId}`;830 const result = await this.helper.executeExtrinsic(831 signer,832 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],833 true, `Unable to set collection permissions for ${label}`,834 );835836 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label);837 }838839 /**840 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.841 * 842 * @param signer keyring of signer843 * @param collectionId ID of collection844 * @param permissions nesting permissions object845 * @param label extra label for log846 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});847 * @returns ```true``` if extrinsic success, otherwise ```false```848 */849 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise<boolean> {850 return await this.setPermissions(signer, collectionId, {nesting: permissions}, label);851 }852853 /**854 * Disables nesting for selected collection.855 * 856 * @param signer keyring of signer857 * @param collectionId ID of collection858 * @param label extra label for log859 * @example disableNesting(aliceKeyring, 10);860 * @returns ```true``` if extrinsic success, otherwise ```false```861 */862 async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {863 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label);864 }865866 /**867 * Sets onchain properties to the collection.868 * 869 * @param signer keyring of signer870 * @param collectionId ID of collection871 * @param properties array of property objects872 * @param label extra label for log873 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);874 * @returns ```true``` if extrinsic success, otherwise ```false```875 */876 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {877 if(typeof label === 'undefined') label = `collection #${collectionId}`;878 const result = await this.helper.executeExtrinsic(879 signer,880 'api.tx.unique.setCollectionProperties', [collectionId, properties],881 true, `Unable to set collection properties for ${label}`,882 );883884 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);885 }886887 /**888 * Deletes onchain properties from the collection.889 * 890 * @param signer keyring of signer891 * @param collectionId ID of collection892 * @param propertyKeys array of property keys to delete893 * @param label894 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);895 * @returns ```true``` if extrinsic success, otherwise ```false```896 */897 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {898 if(typeof label === 'undefined') label = `collection #${collectionId}`;899 const result = await this.helper.executeExtrinsic(900 signer,901 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],902 true, `Unable to delete collection properties for ${label}`,903 );904905 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);906 }907908 /**909 * Changes the owner of the token.910 * 911 * @param signer keyring of signer912 * @param collectionId ID of collection913 * @param tokenId ID of token914 * @param addressObj address of a new owner915 * @param amount amount of tokens to be transfered. For NFT must be set to 1n916 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})917 * @returns true if the token success, otherwise false918 */919 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {920 const result = await this.helper.executeExtrinsic(921 signer,922 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],923 true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,924 );925926 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);927 }928929 /**930 * 931 * Change ownership of a token(s) on behalf of the owner. 932 * 933 * @param signer keyring of signer934 * @param collectionId ID of collection935 * @param tokenId ID of token936 * @param fromAddressObj address on behalf of which the token will be sent937 * @param toAddressObj new token owner938 * @param amount amount of tokens to be transfered. For NFT must be set to 1n939 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})940 * @returns true if the token success, otherwise false941 */942 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {943 const result = await this.helper.executeExtrinsic(944 signer,945 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],946 true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,947 );948 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);949 }950951 /**952 * 953 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.954 * 955 * @param signer keyring of signer956 * @param collectionId ID of collection957 * @param tokenId ID of token958 * @param label 959 * @param amount amount of tokens to be burned. For NFT must be set to 1n960 * @example burnToken(aliceKeyring, 10, 5);961 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```962 */963 async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{964 success: boolean,965 token: number | null966 }> {967 if(typeof label === 'undefined') label = `collection #${collectionId}`;968 const burnResult = await this.helper.executeExtrinsic(969 signer,970 'api.tx.unique.burnItem', [collectionId, tokenId, amount],971 true, `Unable to burn token for ${label}`,972 );973 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);974 if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');975 return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};976 }977978 /**979 * Destroys a concrete instance of NFT on behalf of the owner980 * 981 * @param signer keyring of signer982 * @param collectionId ID of collection983 * @param fromAddressObj address on behalf of which the token will be burnt984 * @param tokenId ID of token985 * @param label 986 * @param amount amount of tokens to be burned. For NFT must be set to 1n987 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})988 * @returns ```true``` if extrinsic success, otherwise ```false```989 */990 async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {991 if(typeof label === 'undefined') label = `collection #${collectionId}`;992 const burnResult = await this.helper.executeExtrinsic(993 signer,994 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],995 true, `Unable to burn token from for ${label}`,996 );997 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);998 return burnedTokens.success && burnedTokens.tokens.length > 0;999 }10001001 /**1002 * Set, change, or remove approved address to transfer the ownership of the NFT.1003 * 1004 * @param signer keyring of signer1005 * @param collectionId ID of collection1006 * @param tokenId ID of token1007 * @param toAddressObj 1008 * @param label 1009 * @param amount amount of token to be approved. For NFT must be set to 1n1010 * @returns ```true``` if extrinsic success, otherwise ```false```1011 */1012 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {1013 if(typeof label === 'undefined') label = `collection #${collectionId}`;1014 const approveResult = await this.helper.executeExtrinsic(1015 signer, 1016 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1017 true, `Unable to approve token for ${label}`,1018 );10191020 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);1021 }10221023 /**1024 * Get the amount of token pieces approved to transfer1025 * @param collectionId ID of collection1026 * @param tokenId ID of token1027 * @param toAccountObj 1028 * @param fromAccountObj1029 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1030 * @returns number of approved to transfer pieces1031 */1032 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1033 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1034 }10351036 /**1037 * Get the last created token id1038 * @param collectionId ID of collection1039 * @example getLastTokenId(10);1040 * @returns id of the last created token1041 */1042 async getLastTokenId(collectionId: number): Promise<number> {1043 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1044 }10451046 /**1047 * Check if token exists1048 * @param collectionId ID of collection1049 * @param tokenId ID of token1050 * @example isTokenExists(10, 20);1051 * @returns true if the token exists, otherwise false1052 */1053 async isTokenExists(collectionId: number, tokenId: number): Promise<boolean> {1054 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1055 }1056}10571058class NFTnRFT extends CollectionGroup {1059 /**1060 * Get tokens owned by account1061 * 1062 * @param collectionId ID of collection1063 * @param addressObj tokens owner1064 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1065 * @returns array of token ids owned by account1066 */1067 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1068 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1069 }10701071 /**1072 * Get token data1073 * @param collectionId ID of collection1074 * @param tokenId ID of token1075 * @param blockHashAt 1076 * @param propertyKeys1077 * @example getToken(10, 5);1078 * @returns human readable token data 1079 */1080 async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{1081 properties: IProperty[];1082 owner: ICrossAccountId;1083 normalizedOwner: ICrossAccountId;1084 }| null> {1085 let tokenData;1086 if(typeof blockHashAt === 'undefined') {1087 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1088 }1089 else {1090 if(typeof propertyKeys === 'undefined') {1091 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1092 if(!collection) return null;1093 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1094 }1095 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1096 }1097 tokenData = tokenData.toHuman();1098 if (tokenData === null || tokenData.owner === null) return null;1099 const owner = {} as any;1100 for (const key of Object.keys(tokenData.owner)) {1101 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];1102 }1103 tokenData.normalizedOwner = crossAccountIdFromLower(owner);1104 return tokenData;1105 }11061107 /**1108 * Set permissions to change token properties1109 * @param signer keyring of signer1110 * @param collectionId ID of collection1111 * @param permissions permissions to change a property by the collection owner or admin1112 * @param label 1113 * @example setTokenPropertyPermissions(1114 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1115 * )1116 * @returns true if extrinsic success otherwise false1117 */1118 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {1119 if(typeof label === 'undefined') label = `collection #${collectionId}`;1120 const result = await this.helper.executeExtrinsic(1121 signer,1122 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1123 true, `Unable to set token property permissions for ${label}`,1124 );11251126 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);1127 }11281129 /**1130 * Set token properties1131 * @param signer keyring of signer1132 * @param collectionId ID of collection1133 * @param tokenId ID of token1134 * @param properties 1135 * @param label 1136 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1137 * @returns ```true``` if extrinsic success, otherwise ```false```1138 */1139 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {1140 if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1141 const result = await this.helper.executeExtrinsic(1142 signer,1143 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1144 true, `Unable to set token properties for ${label}`,1145 );11461147 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);1148 }11491150 /**1151 * Delete the provided properties of a token1152 * @param signer keyring of signer1153 * @param collectionId ID of collection1154 * @param tokenId ID of token1155 * @param propertyKeys property keys to be deleted 1156 * @param label 1157 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1158 * @returns ```true``` if extrinsic success, otherwise ```false```1159 */1160 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {1161 if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1162 const result = await this.helper.executeExtrinsic(1163 signer,1164 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1165 true, `Unable to delete token properties for ${label}`,1166 );11671168 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);1169 }11701171 /**1172 * Mint new collection1173 * @param signer keyring of signer1174 * @param collectionOptions basic collection options and properties 1175 * @param mode NFT or RFT type of a collection1176 * @param errorLabel 1177 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1178 * @returns object of the created collection1179 */1180 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {1181 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1182 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1183 for (const key of ['name', 'description', 'tokenPrefix']) {1184 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);1185 }1186 const creationResult = await this.helper.executeExtrinsic(1187 signer,1188 'api.tx.unique.createCollectionEx', [collectionOptions],1189 true, errorLabel,1190 );1191 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1192 }11931194 getCollectionObject(collectionId: number): any {1195 return null;1196 }11971198 getTokenObject(collectionId: number, tokenId: number): any {1199 return null;1200 }1201}120212031204class NFTGroup extends NFTnRFT {1205 /**1206 * Get collection object1207 * @param collectionId ID of collection1208 * @example getCollectionObject(2);1209 * @returns instance of UniqueNFTCollection1210 */1211 getCollectionObject(collectionId: number): UniqueNFTCollection {1212 return new UniqueNFTCollection(collectionId, this.helper);1213 }12141215 /**1216 * Get token object1217 * @param collectionId ID of collection1218 * @param tokenId ID of token1219 * @example getTokenObject(10, 5);1220 * @returns instance of UniqueNFTToken1221 */1222 getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {1223 return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));1224 }12251226 /**1227 * Get token's owner1228 * @param collectionId ID of collection1229 * @param tokenId ID of token1230 * @param blockHashAt 1231 * @example getTokenOwner(10, 5);1232 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1233 */1234 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {1235 let owner;1236 if (typeof blockHashAt === 'undefined') {1237 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1238 } else {1239 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1240 }1241 return crossAccountIdFromLower(owner.toJSON());1242 }12431244 /**1245 * Is token approved to transfer1246 * @param collectionId ID of collection1247 * @param tokenId ID of token1248 * @param toAccountObj address to be approved1249 * @returns ```true``` if extrinsic success, otherwise ```false```1250 */1251 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1252 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1253 }12541255 /**1256 * Changes the owner of the token.1257 * 1258 * @param signer keyring of signer1259 * @param collectionId ID of collection1260 * @param tokenId ID of token1261 * @param addressObj address of a new owner1262 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1263 * @returns ```true``` if extrinsic success, otherwise ```false```1264 */1265 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1266 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1267 }12681269 /**1270 * 1271 * Change ownership of a NFT on behalf of the owner. 1272 * 1273 * @param signer keyring of signer1274 * @param collectionId ID of collection1275 * @param tokenId ID of token1276 * @param fromAddressObj address on behalf of which the token will be sent1277 * @param toAddressObj new token owner1278 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1279 * @returns ```true``` if extrinsic success, otherwise ```false```1280 */1281 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1282 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1283 }12841285 /**1286 * Recursively find the address that owns the token1287 * @param collectionId ID of collection1288 * @param tokenId ID of token1289 * @param blockHashAt 1290 * @example getTokenTopmostOwner(10, 5);1291 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1292 */1293 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {1294 let owner;1295 if (typeof blockHashAt === 'undefined') {1296 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1297 } else {1298 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1299 }13001301 if (owner === null) return null;13021303 owner = owner.toHuman();13041305 return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;1306 }13071308 /**1309 * Get tokens nested in the provided token1310 * @param collectionId ID of collection1311 * @param tokenId ID of token1312 * @param blockHashAt 1313 * @example getTokenChildren(10, 5);1314 * @returns tokens whose depth of nesting is <= 5 1315 */1316 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1317 let children;1318 if(typeof blockHashAt === 'undefined') {1319 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1320 } else {1321 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1322 }13231324 return children.toJSON().map((x: any) => {1325 return {collectionId: x.collection, tokenId: x.token};1326 });1327 }13281329 /**1330 * Nest one token into another1331 * @param signer keyring of signer1332 * @param tokenObj token to be nested1333 * @param rootTokenObj token to be parent1334 * @param label 1335 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1336 * @returns ```true``` if extrinsic success, otherwise ```false```1337 */1338 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {1339 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1340 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1341 if(!result) {1342 throw Error(`Unable to nest token for ${label}`);1343 }1344 return result;1345 }13461347 /**1348 * Remove token from nested state1349 * @param signer keyring of signer1350 * @param tokenObj token to unnest1351 * @param rootTokenObj parent of a token1352 * @param toAddressObj address of a new token owner 1353 * @param label 1354 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1355 * @returns ```true``` if extrinsic success, otherwise ```false```1356 */1357 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {1358 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1359 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1360 if(!result) {1361 throw Error(`Unable to unnest token for ${label}`);1362 }1363 return result;1364 }13651366 /**1367 * Mint new collection1368 * @param signer keyring of signer1369 * @param collectionOptions Collection options1370 * @param label 1371 * @example 1372 * mintCollection(aliceKeyring, {1373 * name: 'New',1374 * description: 'New collection',1375 * tokenPrefix: 'NEW',1376 * })1377 * @returns object of the created collection1378 */1379 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {1380 return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;1381 }13821383 /**1384 * Mint new token1385 * @param signer keyring of signer1386 * @param data token data1387 * @param label 1388 * @returns created token object1389 */1390 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {1391 if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1392 const creationResult = await this.helper.executeExtrinsic(1393 signer,1394 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1395 nft: {1396 properties: data.properties,1397 },1398 }],1399 true, `Unable to mint NFT token for ${label}`,1400 );1401 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1402 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1403 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1404 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1405 }14061407 /**1408 * Mint multiple NFT tokens1409 * @param signer keyring of signer1410 * @param collectionId ID of collection1411 * @param tokens array of tokens with owner and properties1412 * @param label 1413 * @example 1414 * mintMultipleTokens(aliceKeyring, 10, [{1415 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1416 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1417 * },{1418 * owner: {Ethereum: "0x9F0583DbB855d..."},1419 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1420 * }]);1421 * @returns ```true``` if extrinsic success, otherwise ```false```1422 */1423 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1424 if(typeof label === 'undefined') label = `collection #${collectionId}`;1425 const creationResult = await this.helper.executeExtrinsic(1426 signer,1427 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1428 true, `Unable to mint NFT tokens for ${label}`,1429 );1430 const collection = this.getCollectionObject(collectionId);1431 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1432 }14331434 /**1435 * Mint multiple NFT tokens with one owner1436 * @param signer keyring of signer1437 * @param collectionId ID of collection1438 * @param owner tokens owner1439 * @param tokens array of tokens with owner and properties1440 * @param label 1441 * @example1442 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1443 * properties: [{1444 * key: "gender",1445 * value: "female",1446 * },{1447 * key: "age",1448 * value: "33",1449 * }],1450 * }]);1451 * @returns array of newly created tokens1452 */1453 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1454 if(typeof label === 'undefined') label = `collection #${collectionId}`;1455 const rawTokens = [];1456 for (const token of tokens) {1457 const raw = {NFT: {properties: token.properties}};1458 rawTokens.push(raw);1459 }1460 const creationResult = await this.helper.executeExtrinsic(1461 signer,1462 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1463 true, `Unable to mint NFT tokens for ${label}`,1464 );1465 const collection = this.getCollectionObject(collectionId);1466 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1467 }14681469 /**1470 * Destroys a concrete instance of NFT.1471 * @param signer keyring of signer1472 * @param collectionId ID of collection1473 * @param tokenId ID of token1474 * @param label 1475 * @example burnToken(aliceKeyring, 10, 5);1476 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1477 */1478 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {1479 return await super.burnToken(signer, collectionId, tokenId, label, 1n);1480 }14811482 /**1483 * Set, change, or remove approved address to transfer the ownership of the NFT.1484 * 1485 * @param signer keyring of signer1486 * @param collectionId ID of collection1487 * @param tokenId ID of token1488 * @param toAddressObj address to approve1489 * @param label 1490 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1491 * @returns ```true``` if extrinsic success, otherwise ```false```1492 */1493 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {1494 return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, 1n);1495 }1496}149714981499class RFTGroup extends NFTnRFT {1500 /**1501 * Get collection object1502 * @param collectionId ID of collection1503 * @example getCollectionObject(2);1504 * @returns instance of UniqueRFTCollection1505 */1506 getCollectionObject(collectionId: number): UniqueRFTCollection {1507 return new UniqueRFTCollection(collectionId, this.helper);1508 }15091510 /**1511 * Get token object1512 * @param collectionId ID of collection1513 * @param tokenId ID of token1514 * @example getTokenObject(10, 5);1515 * @returns instance of UniqueNFTToken1516 */1517 getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {1518 return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));1519 }15201521 /**1522 * Get top 10 token owners with the largest number of pieces 1523 * @param collectionId ID of collection1524 * @param tokenId ID of token1525 * @example getTokenTop10Owners(10, 5);1526 * @returns array of top 10 owners1527 */1528 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {1529 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);1530 }15311532 /**1533 * Get number of pieces owned by address1534 * @param collectionId ID of collection1535 * @param tokenId ID of token1536 * @param addressObj address token owner1537 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1538 * @returns number of pieces ownerd by address1539 */1540 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1541 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1542 }15431544 /**1545 * Transfer pieces of token to another address1546 * @param signer keyring of signer1547 * @param collectionId ID of collection1548 * @param tokenId ID of token1549 * @param addressObj address of a new owner1550 * @param amount number of pieces to be transfered1551 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1552 * @returns ```true``` if extrinsic success, otherwise ```false```1553 */1554 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {1555 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1556 }15571558 /**1559 * Change ownership of some pieces of RFT on behalf of the owner. 1560 * @param signer keyring of signer1561 * @param collectionId ID of collection1562 * @param tokenId ID of token1563 * @param fromAddressObj address on behalf of which the token will be sent1564 * @param toAddressObj new token owner1565 * @param amount number of pieces to be transfered1566 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1567 * @returns ```true``` if extrinsic success, otherwise ```false```1568 */1569 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {1570 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1571 }15721573 /**1574 * Mint new collection1575 * @param signer keyring of signer1576 * @param collectionOptions Collection options1577 * @param label 1578 * @example1579 * mintCollection(aliceKeyring, {1580 * name: 'New',1581 * description: 'New collection',1582 * tokenPrefix: 'NEW',1583 * })1584 * @returns object of the created collection1585 */1586 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {1587 return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;1588 }15891590 /**1591 * Mint new token1592 * @param signer keyring of signer1593 * @param data token data1594 * @param label 1595 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1596 * @returns created token object1597 */1598 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {1599 if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1600 const creationResult = await this.helper.executeExtrinsic(1601 signer,1602 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1603 refungible: {1604 pieces: data.pieces,1605 properties: data.properties,1606 },1607 }],1608 true, `Unable to mint RFT token for ${label}`,1609 );1610 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1611 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1612 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1613 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1614 }16151616 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1617 throw Error('Not implemented');1618 if(typeof label === 'undefined') label = `collection #${collectionId}`;1619 const creationResult = await this.helper.executeExtrinsic(1620 signer,1621 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1622 true, `Unable to mint RFT tokens for ${label}`,1623 );1624 const collection = this.getCollectionObject(collectionId);1625 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1626 }16271628 /**1629 * Mint multiple RFT tokens with one owner1630 * @param signer keyring of signer1631 * @param collectionId ID of collection1632 * @param owner tokens owner1633 * @param tokens array of tokens with properties and pieces1634 * @param label 1635 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1636 * @returns array of newly created RFT tokens1637 */1638 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1639 if(typeof label === 'undefined') label = `collection #${collectionId}`;1640 const rawTokens = [];1641 for (const token of tokens) {1642 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1643 rawTokens.push(raw);1644 }1645 const creationResult = await this.helper.executeExtrinsic(1646 signer,1647 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1648 true, `Unable to mint RFT tokens for ${label}`,1649 );1650 const collection = this.getCollectionObject(collectionId);1651 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1652 }16531654 /**1655 * Destroys a concrete instance of RFT.1656 * @param signer keyring of signer1657 * @param collectionId ID of collection1658 * @param tokenId ID of token1659 * @param label 1660 * @param amount number of pieces to be burnt1661 * @example burnToken(aliceKeyring, 10, 5);1662 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1663 */1664 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {1665 return await super.burnToken(signer, collectionId, tokenId, label, amount);1666 }16671668 /**1669 * Set, change, or remove approved address to transfer the ownership of the RFT.1670 * 1671 * @param signer keyring of signer1672 * @param collectionId ID of collection1673 * @param tokenId ID of token1674 * @param toAddressObj address to approve1675 * @param label 1676 * @param amount number of pieces to be approved1677 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1678 * @returns true if the token success, otherwise false1679 */1680 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {1681 return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1682 }16831684 /**1685 * Get total number of pieces1686 * @param collectionId ID of collection1687 * @param tokenId ID of token1688 * @example getTokenTotalPieces(10, 5);1689 * @returns number of pieces1690 */1691 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1692 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1693 }16941695 /**1696 * Change number of token pieces. Signer must be the owner of all token pieces.1697 * @param signer keyring of signer1698 * @param collectionId ID of collection1699 * @param tokenId ID of token1700 * @param amount new number of pieces1701 * @param label 1702 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1703 * @returns true if the repartion was success, otherwise false1704 */1705 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {1706 if(typeof label === 'undefined') label = `collection #${collectionId}`;1707 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1708 const repartitionResult = await this.helper.executeExtrinsic(1709 signer,1710 'api.tx.unique.repartition', [collectionId, tokenId, amount],1711 true, `Unable to repartition RFT token for ${label}`,1712 );1713 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);1714 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);1715 }1716}171717181719class FTGroup extends CollectionGroup {1720 /**1721 * Get collection object1722 * @param collectionId ID of collection1723 * @example getCollectionObject(2);1724 * @returns instance of UniqueFTCollection1725 */1726 getCollectionObject(collectionId: number): UniqueFTCollection {1727 return new UniqueFTCollection(collectionId, this.helper);1728 }17291730 /**1731 * Mint new fungible collection1732 * @param signer keyring of signer1733 * @param collectionOptions Collection options1734 * @param decimalPoints number of token decimals 1735 * @param errorLabel 1736 * @example1737 * mintCollection(aliceKeyring, {1738 * name: 'New',1739 * description: 'New collection',1740 * tokenPrefix: 'NEW',1741 * }, 18)1742 * @returns newly created fungible collection1743 */1744 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {1745 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1746 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1747 collectionOptions.mode = {fungible: decimalPoints};1748 for (const key of ['name', 'description', 'tokenPrefix']) {1749 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);1750 }1751 const creationResult = await this.helper.executeExtrinsic(1752 signer,1753 'api.tx.unique.createCollectionEx', [collectionOptions],1754 true, errorLabel,1755 );1756 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1757 }17581759 /**1760 * Mint tokens1761 * @param signer keyring of signer1762 * @param collectionId ID of collection1763 * @param owner address owner of new tokens1764 * @param amount amount of tokens to be meanted1765 * @param label 1766 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1767 * @returns ```true``` if extrinsic success, otherwise ```false``` 1768 */1769 async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {1770 if(typeof label === 'undefined') label = `collection #${collectionId}`;1771 const creationResult = await this.helper.executeExtrinsic(1772 signer,1773 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1774 fungible: {1775 value: amount,1776 },1777 }],1778 true, `Unable to mint fungible tokens for ${label}`,1779 );1780 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1781 }17821783 /**1784 * Mint multiple Fungible tokens with one owner1785 * @param signer keyring of signer1786 * @param collectionId ID of collection1787 * @param owner tokens owner1788 * @param tokens array of tokens with properties and pieces1789 * @param label 1790 * @returns ```true``` if extrinsic success, otherwise ```false``` 1791 */1792 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {1793 if(typeof label === 'undefined') label = `collection #${collectionId}`;1794 const rawTokens = [];1795 for (const token of tokens) {1796 const raw = {Fungible: {Value: token.value}};1797 rawTokens.push(raw);1798 }1799 const creationResult = await this.helper.executeExtrinsic(1800 signer,1801 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1802 true, `Unable to mint RFT tokens for ${label}`,1803 );1804 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1805 }18061807 /**1808 * Get the top 10 owners with the largest balance for the Fungible collection 1809 * @param collectionId ID of collection1810 * @example getTop10Owners(10);1811 * @returns array of ```ICrossAccountId```1812 */1813 async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {1814 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);1815 }18161817 /**1818 * Get account balance1819 * @param collectionId ID of collection1820 * @param addressObj address of owner1821 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})1822 * @returns amount of fungible tokens owned by address1823 */1824 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1825 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1826 }18271828 /**1829 * Transfer tokens to address1830 * @param signer keyring of signer1831 * @param collectionId ID of collection1832 * @param toAddressObj address recepient1833 * @param amount amount of tokens to be sent1834 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1835 * @returns ```true``` if extrinsic success, otherwise ```false``` 1836 */1837 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {1838 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1839 }18401841 /**1842 * Transfer some tokens on behalf of the owner.1843 * @param signer keyring of signer1844 * @param collectionId ID of collection1845 * @param fromAddressObj address on behalf of which tokens will be sent1846 * @param toAddressObj address where token to be sent1847 * @param amount number of tokens to be sent1848 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);1849 * @returns ```true``` if extrinsic success, otherwise ```false``` 1850 */1851 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {1852 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1853 }18541855 /**1856 * Destroy some amount of tokens1857 * @param signer keyring of signer1858 * @param collectionId ID of collection1859 * @param amount amount of tokens to be destroyed1860 * @param label 1861 * @example burnTokens(aliceKeyring, 10, 1000n);1862 * @returns ```true``` if extrinsic success, otherwise ```false``` 1863 */1864 async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {1865 return (await super.burnToken(signer, collectionId, 0, label, amount)).success;1866 }18671868 /**1869 * Burn some tokens on behalf of the owner.1870 * @param signer keyring of signer1871 * @param collectionId ID of collection1872 * @param fromAddressObj address on behalf of which tokens will be burnt1873 * @param amount amount of tokens to be burnt1874 * @param label 1875 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1876 * @returns ```true``` if extrinsic success, otherwise ```false``` 1877 */1878 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {1879 return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);1880 }18811882 /**1883 * Get total collection supply1884 * @param collectionId 1885 * @returns 1886 */1887 async getTotalPieces(collectionId: number): Promise<bigint> {1888 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();1889 }18901891 /**1892 * Set, change, or remove approved address to transfer tokens.1893 * 1894 * @param signer keyring of signer1895 * @param collectionId ID of collection1896 * @param toAddressObj address to be approved1897 * @param amount amount of tokens to be approved1898 * @param label 1899 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)1900 * @returns ```true``` if extrinsic success, otherwise ```false``` 1901 */1902 async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {1903 return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);1904 }19051906 /**1907 * Get amount of fungible tokens approved to transfer1908 * @param collectionId ID of collection1909 * @param fromAddressObj owner of tokens1910 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner1911 * @returns number of tokens approved for the transfer1912 */1913 async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {1914 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);1915 }1916}191719181919class ChainGroup extends HelperGroup {1920 /**1921 * Get system properties of a chain1922 * @example getChainProperties();1923 * @returns ss58Format, token decimals, and token symbol1924 */1925 getChainProperties(): IChainProperties {1926 const properties = (this.helper.api as any).registry.getChainProperties().toJSON();1927 return {1928 ss58Format: properties.ss58Format.toJSON(),1929 tokenDecimals: properties.tokenDecimals.toJSON(),1930 tokenSymbol: properties.tokenSymbol.toJSON(),1931 };1932 }19331934 /**1935 * Get chain header1936 * @example getLatestBlockNumber();1937 * @returns the number of the last block1938 */1939 async getLatestBlockNumber(): Promise<number> {1940 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();1941 }19421943 /**1944 * Get block hash by block number1945 * @param blockNumber number of block1946 * @example getBlockHashByNumber(12345);1947 * @returns hash of a block1948 */1949 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {1950 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();1951 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;1952 return blockHash;1953 }19541955 /**1956 * Get account nonce1957 * @param address substrate address1958 * @example getNonce("5GrwvaEF5zXb26Fz...");1959 * @returns number, account's nonce1960 */1961 async getNonce(address: TSubstrateAccount): Promise<number> {1962 return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber();1963 }1964}196519661967class BalanceGroup extends HelperGroup {1968 /**1969 * Representation of the native token in the smallest unit1970 * @example getOneTokenNominal()1971 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.1972 */1973 getOneTokenNominal(): bigint {1974 const chainProperties = this.helper.chain.getChainProperties();1975 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);1976 }19771978 /**1979 * Get substrate address balance1980 * @param address substrate address1981 * @example getSubstrate("5GrwvaEF5zXb26Fz...")1982 * @returns amount of tokens on address1983 */1984 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {1985 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();1986 }19871988 /**1989 * Get ethereum address balance1990 * @param address ethereum address1991 * @example getEthereum("0x9F0583DbB855d...")1992 * @returns amount of tokens on address1993 */1994 async getEthereum(address: TEthereumAccount): Promise<bigint> {1995 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();1996 }19971998 /**1999 * Transfer tokens to substrate address2000 * @param signer keyring of signer2001 * @param address substrate address of a recepient2002 * @param amount amount of tokens to be transfered2003 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2004 * @returns ```true``` if extrinsic success, otherwise ```false```2005 */2006 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2007 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}`);20082009 let transfer = {from: null, to: null, amount: 0n} as any;2010 result.result.events.forEach(({event: {data, method, section}}) => {2011 if ((section === 'balances') && (method === 'Transfer')) {2012 transfer = {2013 from: this.helper.address.normalizeSubstrate(data[0]),2014 to: this.helper.address.normalizeSubstrate(data[1]),2015 amount: BigInt(data[2]),2016 };2017 }2018 });2019 let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;2020 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;2021 isSuccess = isSuccess && BigInt(amount) === transfer.amount;2022 return isSuccess;2023 }2024}202520262027class AddressGroup extends HelperGroup {2028 /**2029 * Normalizes the address to the specified ss58 format, by default ```42```.2030 * @param address substrate address2031 * @param ss58Format format for address conversion, by default ```42```2032 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2033 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2034 */2035 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2036 return this.helper.util.normalizeSubstrateAddress(address, ss58Format);2037 }20382039 /**2040 * Get address in the connected chain format2041 * @param address substrate address2042 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2043 * @returns address in chain format2044 */2045 async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise<TSubstrateAccount> {2046 const info = this.helper.chain.getChainProperties();2047 return encodeAddress(decodeAddress(address), info.ss58Format);2048 }20492050 /**2051 * Get substrate mirror of an ethereum address2052 * @param ethAddress ethereum address2053 * @param toChainFormat false for normalized account2054 * @example ethToSubstrate('0x9F0583DbB855d...')2055 * @returns substrate mirror of a provided ethereum address2056 */2057 async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise<TSubstrateAccount> {2058 if(!toChainFormat) return evmToAddress(ethAddress);2059 const info = this.helper.chain.getChainProperties();2060 return evmToAddress(ethAddress, info.ss58Format);2061 }20622063 /**2064 * Get ethereum mirror of a substrate address2065 * @param subAddress substrate account2066 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2067 * @returns ethereum mirror of a provided substrate address2068 */2069 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2070 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join(''));2071 }2072}207320742075export class UniqueHelper extends ChainHelperBase {2076 chain: ChainGroup;2077 balance: BalanceGroup;2078 address: AddressGroup;2079 collection: CollectionGroup;2080 nft: NFTGroup;2081 rft: RFTGroup;2082 ft: FTGroup;20832084 constructor(logger?: ILogger) {2085 super(logger);2086 this.chain = new ChainGroup(this);2087 this.balance = new BalanceGroup(this);2088 this.address = new AddressGroup(this);2089 this.collection = new CollectionGroup(this);2090 this.nft = new NFTGroup(this);2091 this.rft = new RFTGroup(this);2092 this.ft = new FTGroup(this);2093 } 2094}209520962097class UniqueCollectionBase {2098 helper: UniqueHelper;2099 collectionId: number;21002101 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2102 this.collectionId = collectionId;2103 this.helper = uniqueHelper;2104 }21052106 async getData() {2107 return await this.helper.collection.getData(this.collectionId);2108 }21092110 async getLastTokenId() {2111 return await this.helper.collection.getLastTokenId(this.collectionId);2112 }21132114 async isTokenExists(tokenId: number) {2115 return await this.helper.collection.isTokenExists(this.collectionId, tokenId);2116 }21172118 async getAdmins() {2119 return await this.helper.collection.getAdmins(this.collectionId);2120 }21212122 async getEffectiveLimits() {2123 return await this.helper.collection.getEffectiveLimits(this.collectionId);2124 }21252126 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) {2127 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label);2128 }21292130 async confirmSponsorship(signer: TSigner, label?: string) {2131 return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label);2132 }21332134 async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) {2135 return await this.helper.collection.setLimits(signer, this.collectionId, limits, label);2136 }21372138 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) {2139 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label);2140 }21412142 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2143 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label);2144 }21452146 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2147 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label);2148 }21492150 async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2151 return await this.helper.collection.setProperties(signer, this.collectionId, properties, label);2152 }21532154 async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2155 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label);2156 }21572158 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2159 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2160 }21612162 async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) {2163 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label);2164 }21652166 async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) {2167 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label);2168 }21692170 async disableNesting(signer: TSigner, label?: string) {2171 return await this.helper.collection.disableNesting(signer, this.collectionId, label);2172 }21732174 async burn(signer: TSigner, label?: string) {2175 return await this.helper.collection.burn(signer, this.collectionId, label);2176 }2177}217821792180class UniqueNFTCollection extends UniqueCollectionBase {2181 getTokenObject(tokenId: number) {2182 return new UniqueNFTToken(tokenId, this);2183 }21842185 async getTokensByAddress(addressObj: ICrossAccountId) {2186 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);2187 }21882189 async getToken(tokenId: number, blockHashAt?: string) {2190 return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt);2191 }21922193 async getTokenOwner(tokenId: number, blockHashAt?: string) {2194 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);2195 }21962197 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {2198 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);2199 }22002201 async getTokenChildren(tokenId: number, blockHashAt?: string) {2202 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2203 }22042205 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2206 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2207 }22082209 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2210 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);2211 }22122213 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {2214 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label);2215 }22162217 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {2218 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);2219 }22202221 async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) {2222 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label);2223 }22242225 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) {2226 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label);2227 }22282229 async burnToken(signer: TSigner, tokenId: number, label?: string) {2230 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label);2231 }22322233 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2234 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2235 }22362237 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2238 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2239 }22402241 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2242 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2243 }22442245 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) {2246 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label);2247 }22482249 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2250 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label);2251 }2252}225322542255class UniqueRFTCollection extends UniqueCollectionBase {2256 getTokenObject(tokenId: number) {2257 return new UniqueRFTToken(tokenId, this);2258 }22592260 async getTokensByAddress(addressObj: ICrossAccountId) {2261 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2262 }22632264 async getTop10TokenOwners(tokenId: number) {2265 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);2266 }22672268 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {2269 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);2270 }22712272 async getTokenTotalPieces(tokenId: number) {2273 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2274 }22752276 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) {2277 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2278 }22792280 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2281 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);2282 }22832284 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2285 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount);2286 }22872288 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2289 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);2290 }22912292 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) {2293 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label);2294 }22952296 async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) {2297 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label);2298 }22992300 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) {2301 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label);2302 }23032304 async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) {2305 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount);2306 }23072308 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2309 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2310 }23112312 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2313 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2314 }23152316 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2317 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2318 }2319}232023212322class UniqueFTCollection extends UniqueCollectionBase {2323 async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) {2324 return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label);2325 }23262327 async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) {2328 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label);2329 }23302331 async getBalance(addressObj: ICrossAccountId) {2332 return await this.helper.ft.getBalance(this.collectionId, addressObj);2333 }23342335 async getTop10Owners() {2336 return await this.helper.ft.getTop10Owners(this.collectionId);2337 }23382339 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) {2340 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);2341 }23422343 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {2344 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);2345 }23462347 async burnTokens(signer: TSigner, amount: bigint, label?: string) {2348 return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label);2349 }23502351 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) {2352 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label);2353 }23542355 async getTotalPieces() {2356 return await this.helper.ft.getTotalPieces(this.collectionId);2357 }23582359 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2360 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label);2361 }23622363 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2364 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);2365 }2366}236723682369class UniqueTokenBase implements IToken {2370 collection: UniqueNFTCollection | UniqueRFTCollection;2371 collectionId: number;2372 tokenId: number;23732374 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {2375 this.collection = collection;2376 this.collectionId = collection.collectionId;2377 this.tokenId = tokenId;2378 }23792380 async getNextSponsored(addressObj: ICrossAccountId) {2381 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2382 }23832384 async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2385 return await this.collection.setTokenProperties(signer, this.tokenId, properties, label);2386 }23872388 async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2389 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label);2390 }2391}239223932394class UniqueNFTToken extends UniqueTokenBase {2395 collection: UniqueNFTCollection;23962397 constructor(tokenId: number, collection: UniqueNFTCollection) {2398 super(tokenId, collection);2399 this.collection = collection;2400 }24012402 async getData(blockHashAt?: string) {2403 return await this.collection.getToken(this.tokenId, blockHashAt);2404 }24052406 async getOwner(blockHashAt?: string) {2407 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);2408 }24092410 async getTopmostOwner(blockHashAt?: string) {2411 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);2412 }24132414 async getChildren(blockHashAt?: string) {2415 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);2416 }24172418 async nest(signer: TSigner, toTokenObj: IToken, label?: string) {2419 return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label);2420 }24212422 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2423 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label);2424 }24252426 async transfer(signer: TSigner, addressObj: ICrossAccountId) {2427 return await this.collection.transferToken(signer, this.tokenId, addressObj);2428 }24292430 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2431 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);2432 }24332434 async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) {2435 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label);2436 }24372438 async isApproved(toAddressObj: ICrossAccountId) {2439 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);2440 }24412442 async burn(signer: TSigner, label?: string) {2443 return await this.collection.burnToken(signer, this.tokenId, label);2444 }2445}24462447class UniqueRFTToken extends UniqueTokenBase {2448 collection: UniqueRFTCollection;24492450 constructor(tokenId: number, collection: UniqueRFTCollection) {2451 super(tokenId, collection);2452 this.collection = collection;2453 }24542455 async getTop10Owners() {2456 return await this.collection.getTop10TokenOwners(this.tokenId);2457 }24582459 async getBalance(addressObj: ICrossAccountId) {2460 return await this.collection.getTokenBalance(this.tokenId, addressObj);2461 }24622463 async getTotalPieces() {2464 return await this.collection.getTokenTotalPieces(this.tokenId);2465 }24662467 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) {2468 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);2469 }24702471 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2472 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);2473 }24742475 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2476 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label);2477 }24782479 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {2480 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);2481 }24822483 async repartition(signer: TSigner, amount: bigint, label?: string) {2484 return await this.collection.repartitionToken(signer, this.tokenId, amount, label);2485 }24862487 async burn(signer: TSigner, amount=100n, label?: string) {2488 return await this.collection.burnToken(signer, this.tokenId, amount, label);2489 }2490}