12345678import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {ApiInterfaceEvents} from '@polkadot/api/types';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';11import {IKeyringPair} from '@polkadot/types/types';12import {IApiListeners, IChainEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types';1314const 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 34 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};4647class UniqueUtil {48 static transactionStatus = {49 NOT_READY: 'NotReady',50 FAIL: 'Fail',51 SUCCESS: 'Success',52 };5354 static chainLogType = {55 EXTRINSIC: 'extrinsic',56 RPC: 'rpc',57 };5859 static getNestingTokenAddress(collectionId: number, tokenId: number) {60 return nesting.tokenIdToAddress(collectionId, tokenId);61 }6263 static getDefaultLogger(): ILogger {64 return {65 log(msg: any, level = 'INFO') {66 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));67 },68 level: {69 ERROR: 'ERROR',70 WARNING: 'WARNING',71 INFO: 'INFO',72 },73 };74 }7576 static vec2str(arr: string[] | number[]) {77 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');78 }7980 static str2vec(string: string) {81 if (typeof string !== 'string') return string;82 return Array.from(string).map(x => x.charCodeAt(0));83 }8485 static fromSeed(seed: string, ss58Format = 42) {86 const keyring = new Keyring({type: 'sr25519', ss58Format});87 return keyring.addFromUri(seed);88 }8990 static normalizeSubstrateAddress(address: string, ss58Format = 42) {91 return encodeAddress(decodeAddress(address), ss58Format);92 }9394 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') {95 if (creationResult.status !== this.transactionStatus.SUCCESS) {96 throw Error(`Unable to create collection for ${label}`);97 }9899 let collectionId = null;100 creationResult.result.events.forEach(({event: {data, method, section}}) => {101 if ((section === 'common') && (method === 'CollectionCreated')) {102 collectionId = parseInt(data[0].toString(), 10);103 }104 });105106 if (collectionId === null) {107 throw Error(`No CollectionCreated event for ${label}`);108 }109110 return collectionId;111 }112113 static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') {114 if (creationResult.status !== this.transactionStatus.SUCCESS) {115 throw Error(`Unable to create tokens for ${label}`);116 }117 let success = false;118 const tokens = [] as any;119 creationResult.result.events.forEach(({event: {data, method, section}}) => {120 if (method === 'ExtrinsicSuccess') {121 success = true;122 } else if ((section === 'common') && (method === 'ItemCreated')) {123 tokens.push({124 collectionId: parseInt(data[0].toString(), 10),125 tokenId: parseInt(data[1].toString(), 10),126 owner: data[2].toJSON(),127 });128 }129 });130 return {success, tokens};131 }132133 static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') {134 if (burnResult.status !== this.transactionStatus.SUCCESS) {135 throw Error(`Unable to burn tokens for ${label}`);136 }137 let success = false;138 const tokens = [] as any;139 burnResult.result.events.forEach(({event: {data, method, section}}) => {140 if (method === 'ExtrinsicSuccess') {141 success = true;142 } else if ((section === 'common') && (method === 'ItemDestroyed')) {143 tokens.push({144 collectionId: parseInt(data[0].toString(), 10),145 tokenId: parseInt(data[1].toString(), 10),146 owner: data[2].toJSON(),147 });148 }149 });150 return {success, tokens};151 }152153 static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) {154 let eventId = null;155 events.forEach(({event: {data, method, section}}) => {156 if ((section === expectedSection) && (method === expectedMethod)) {157 eventId = parseInt(data[0].toString(), 10);158 }159 });160161 if (eventId === null) {162 throw Error(`No ${expectedMethod} event for ${label}`);163 }164 return eventId === collectionId;165 }166167 static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {168 const normalizeAddress = (address: string | ICrossAccountId) => {169 if(typeof address === 'string') return address;170 const obj = {} as any;171 Object.keys(address).forEach(k => {172 obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];173 });174 if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)};175 if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()};176 return address;177 };178 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;179 events.forEach(({event: {data, method, section}}) => {180 if ((section === 'common') && (method === 'Transfer')) {181 const hData = (data as any).toJSON();182 transfer = {183 collectionId: hData[0],184 tokenId: hData[1],185 from: normalizeAddress(hData[2]),186 to: normalizeAddress(hData[3]),187 amount: BigInt(hData[4]),188 };189 }190 });191 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;192 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);193 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);194 isSuccess = isSuccess && amount === transfer.amount;195 return isSuccess;196 }197}198199200class ChainHelperBase {201 transactionStatus = UniqueUtil.transactionStatus;202 chainLogType = UniqueUtil.chainLogType;203 util: typeof UniqueUtil;204 logger: ILogger;205 api: ApiPromise | null;206 forcedNetwork: TUniqueNetworks | null;207 network: TUniqueNetworks | null;208 chainLog: IUniqueHelperLog[];209210 constructor(logger?: ILogger) {211 this.util = UniqueUtil;212 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();213 this.logger = logger;214 this.api = null;215 this.forcedNetwork = null;216 this.network = null;217 this.chainLog = [];218 }219220 clearChainLog(): void {221 this.chainLog = [];222 }223224 forceNetwork(value: TUniqueNetworks): void {225 this.forcedNetwork = value;226 }227228 async connect(wsEndpoint: string, listeners?: IApiListeners) {229 if (this.api !== null) throw Error('Already connected');230 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);231 this.api = api;232 this.network = network;233 }234235 async disconnect() {236 if (this.api === null) return;237 await this.api.disconnect();238 this.api = null;239 this.network = null;240 }241242 static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {243 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;244 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;245 return 'opal';246 }247248 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {249 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});250 await api.isReady;251252 const network = await this.detectNetwork(api);253254 await api.disconnect();255256 return network;257 }258259 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{260 api: ApiPromise;261 network: TUniqueNetworks;262 }> {263 if(typeof network === 'undefined' || network === null) network = 'opal';264 const supportedRPC = {265 opal: {266 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,267 },268 quartz: {269 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,270 },271 unique: {272 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,273 },274 };275 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);276 const rpc = supportedRPC[network];277278 279 280281 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});282283 await api.isReadyOrError;284285 if (typeof listeners === 'undefined') listeners = {};286 for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {287 if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;288 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);289 }290291 return {api, network};292 }293294 getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) {295 const {events, status} = data;296 if (status.isReady) {297 return this.transactionStatus.NOT_READY;298 }299 if (status.isBroadcast) {300 return this.transactionStatus.NOT_READY;301 }302 if (status.isInBlock || status.isFinalized) {303 const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed');304 if (errors.length > 0) {305 return this.transactionStatus.FAIL;306 }307 if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {308 return this.transactionStatus.SUCCESS;309 }310 }311312 return this.transactionStatus.FAIL;313 }314315 signTransaction(sender: TSigner, transaction: any, label = 'transaction', options: any = null) {316 const sign = (callback: any) => {317 if(options !== null) return transaction.signAndSend(sender, options, callback);318 return transaction.signAndSend(sender, callback);319 };320 return new Promise(async (resolve, reject) => {321 try {322 const unsub = await sign((result: any) => {323 const status = this.getTransactionStatus(result);324325 if (status === this.transactionStatus.SUCCESS) {326 this.logger.log(`${label} successful`);327 unsub();328 resolve({result, status});329 } else if (status === this.transactionStatus.FAIL) {330 let moduleError = null;331332 if (result.hasOwnProperty('dispatchError')) {333 const dispatchError = result['dispatchError'];334335 if (dispatchError && dispatchError.isModule) {336 const modErr = dispatchError.asModule;337 const errorMeta = dispatchError.registry.findMetaError(modErr);338339 moduleError = `${errorMeta.section}.${errorMeta.name}`;340 }341 else {342 this.logger.log(result, this.logger.level.ERROR);343 }344 }345346 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);347 unsub();348 reject({status, moduleError, result});349 }350 });351 } catch (e) {352 this.logger.log(e, this.logger.level.ERROR);353 reject(e);354 }355 });356 }357358 constructApiCall(apiCall: string, params: any[]) {359 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);360 let call = this.api as any;361 for(const part of apiCall.slice(4).split('.')) {362 call = call[part];363 }364 return call(...params);365 }366367 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') {368 if(this.api === null) throw Error('API not initialized');369 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);370371 const startTime = (new Date()).getTime();372 let result: ITransactionResult;373 let events = [];374 try {375 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult;376 events = result.result.events.map((x: any) => x.toHuman());377 }378 catch(e) {379 if(!(e as object).hasOwnProperty('status')) throw e;380 result = e as ITransactionResult;381 }382383 const endTime = (new Date()).getTime();384385 const log = {386 executedAt: endTime,387 executionTime: endTime - startTime,388 type: this.chainLogType.EXTRINSIC,389 status: result.status,390 call: extrinsic,391 params,392 } as IUniqueHelperLog;393394 if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError;395 if(events.length > 0) log.events = events;396397 this.chainLog.push(log);398399 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage);400 return result;401 }402403 async callRpc(rpc: string, params?: any[]) {404 if(typeof params === 'undefined') params = [];405 if(this.api === null) throw Error('API not initialized');406 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);407408 const startTime = (new Date()).getTime();409 let result;410 let error = null;411 const log = {412 type: this.chainLogType.RPC,413 call: rpc,414 params,415 } as IUniqueHelperLog;416417 try {418 result = await this.constructApiCall(rpc, params);419 }420 catch(e) {421 error = e;422 }423424 const endTime = (new Date()).getTime();425426 log.executedAt = endTime;427 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';428 log.executionTime = endTime - startTime;429430 this.chainLog.push(log);431432 if(error !== null) throw error;433434 return result;435 }436437 getSignerAddress(signer: IKeyringPair | string): string {438 if(typeof signer === 'string') return signer;439 return signer.address;440 }441}442443444class HelperGroup {445 helper: UniqueHelper;446447 constructor(uniqueHelper: UniqueHelper) {448 this.helper = uniqueHelper;449 }450}451452453class CollectionGroup extends HelperGroup {454 455456457458459460461462463 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {464 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();465 }466467 468469470471472 async getTotalCount(): Promise<number> {473 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();474 }475476 477478479480481482483 async getData(collectionId: number): Promise<{484 id: number;485 name: string;486 description: string;487 tokensCount: number;488 admins: ICrossAccountId[];489 normalizedOwner: TSubstrateAccount;490 raw: any491 } | null> {492 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);493 const humanCollection = collection.toHuman(), collectionData = {494 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],495 raw: humanCollection,496 } as any, jsonCollection = collection.toJSON();497 if (humanCollection === null) return null;498 collectionData.raw.limits = jsonCollection.limits;499 collectionData.raw.permissions = jsonCollection.permissions;500 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);501 for (const key of ['name', 'description']) {502 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);503 }504505 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0;506 collectionData.admins = await this.getAdmins(collectionId);507508 return collectionData;509 }510511 512513514515516517518 async getAdmins(collectionId: number): Promise<ICrossAccountId[]> {519 const normalized = [];520 for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) {521 if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)});522 else normalized.push(admin);523 }524 return normalized;525 }526527 528529530531532533 async getAllowList(collectionId: number): Promise<ICrossAccountId[]> {534 const normalized = [];535 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();536 for (const address of allowListed) {537 if (address.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(address.Substrate)});538 else normalized.push(address);539 }540 return normalized;541 }542543 544545546547548549550 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {551 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();552 }553554 555556557558559560561562563 async burn(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {564 if(typeof label === 'undefined') label = `collection #${collectionId}`;565 const result = await this.helper.executeExtrinsic(566 signer,567 'api.tx.unique.destroyCollection', [collectionId],568 true, `Unable to burn collection for ${label}`,569 );570571 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label);572 }573574 575576577578579580581582583584 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise<boolean> {585 if(typeof label === 'undefined') label = `collection #${collectionId}`;586 const result = await this.helper.executeExtrinsic(587 signer,588 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],589 true, `Unable to set collection sponsor for ${label}`,590 );591592 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label);593 }594595 596597598599600601602603604 async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {605 if(typeof label === 'undefined') label = `collection #${collectionId}`;606 const result = await this.helper.executeExtrinsic(607 signer,608 'api.tx.unique.confirmSponsorship', [collectionId],609 true, `Unable to confirm collection sponsorship for ${label}`,610 );611612 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label);613 }614615 616617618619620621622623624625626627628629630631632633 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise<boolean> {634 if(typeof label === 'undefined') label = `collection #${collectionId}`;635 const result = await this.helper.executeExtrinsic(636 signer,637 'api.tx.unique.setCollectionLimits', [collectionId, limits],638 true, `Unable to set collection limits for ${label}`,639 );640641 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label);642 }643644 645646647648649650651652653654 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise<boolean> {655 if(typeof label === 'undefined') label = `collection #${collectionId}`;656 const result = await this.helper.executeExtrinsic(657 signer,658 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],659 true, `Unable to change collection owner for ${label}`,660 );661662 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label);663 }664665 666667668669670671672673674675 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {676 if(typeof label === 'undefined') label = `collection #${collectionId}`;677 const result = await this.helper.executeExtrinsic(678 signer,679 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],680 true, `Unable to add collection admin for ${label}`,681 );682683 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label);684 }685686 687688689690691692693694 async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId, label?: string): Promise<boolean> {695 if(typeof label === 'undefined') label = `collection #${collectionId}`;696 const result = await this.helper.executeExtrinsic(697 signer,698 'api.tx.unique.addToAllowList', [collectionId, addressObj],699 true, `Unable to add address to allow list for ${label}`,700 );701702 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded');703 }704705 706707708709710711712713714715 async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId, 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.removeFromAllowList', [collectionId, addressObj],720 true, `Unable to remove address from allow list for ${label}`,721 );722723 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved', label);724 }725726 727728729730731732733734735736 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {737 if(typeof label === 'undefined') label = `collection #${collectionId}`;738 const result = await this.helper.executeExtrinsic(739 signer,740 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],741 true, `Unable to remove collection admin for ${label}`,742 );743744 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label);745 }746747 748749750751752753754755756757 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise<boolean> {758 if(typeof label === 'undefined') label = `collection #${collectionId}`;759 const result = await this.helper.executeExtrinsic(760 signer,761 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],762 true, `Unable to set collection permissions for ${label}`,763 );764765 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label);766 }767768 769770771772773774775776777778 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise<boolean> {779 return await this.setPermissions(signer, collectionId, {nesting: permissions}, label);780 }781782 783784785786787788789790791 async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {792 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label);793 }794795 796797798799800801802803804805 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {806 if(typeof label === 'undefined') label = `collection #${collectionId}`;807 const result = await this.helper.executeExtrinsic(808 signer,809 'api.tx.unique.setCollectionProperties', [collectionId, properties],810 true, `Unable to set collection properties for ${label}`,811 );812813 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);814 }815816 817818819820821822823824825826 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {827 if(typeof label === 'undefined') label = `collection #${collectionId}`;828 const result = await this.helper.executeExtrinsic(829 signer,830 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],831 true, `Unable to delete collection properties for ${label}`,832 );833834 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);835 }836837 838839840841842843844845846847848 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {849 const result = await this.helper.executeExtrinsic(850 signer,851 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],852 true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,853 );854855 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);856 }857858 859860861862863864865866867868869870871 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {872 const result = await this.helper.executeExtrinsic(873 signer,874 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],875 true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,876 );877 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);878 }879880 881882883884885886887888889890891892 async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{893 success: boolean,894 token: number | null895 }> {896 if(typeof label === 'undefined') label = `collection #${collectionId}`;897 const burnResult = await this.helper.executeExtrinsic(898 signer,899 'api.tx.unique.burnItem', [collectionId, tokenId, amount],900 true, `Unable to burn token for ${label}`,901 );902 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);903 if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');904 return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};905 }906907 908909910911912913914915916917918919 async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {920 if(typeof label === 'undefined') label = `collection #${collectionId}`;921 const burnResult = await this.helper.executeExtrinsic(922 signer,923 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],924 true, `Unable to burn token from for ${label}`,925 );926 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);927 return burnedTokens.success && burnedTokens.tokens.length > 0;928 }929930 931932933934935936937938939940941 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {942 if(typeof label === 'undefined') label = `collection #${collectionId}`;943 const approveResult = await this.helper.executeExtrinsic(944 signer,945 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],946 true, `Unable to approve token for ${label}`,947 );948949 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);950 }951952 953954955956957958959960961 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {962 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();963 }964965 966967968969970971 async getLastTokenId(collectionId: number): Promise<number> {972 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();973 }974975 976977978979980981982 async isTokenExists(collectionId: number, tokenId: number): Promise<boolean> {983 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();984 }985}986987class NFTnRFT extends CollectionGroup {988 989990991992993994995996 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {997 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();998 }9991000 100110021003100410051006100710081009 async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{1010 properties: IProperty[];1011 owner: ICrossAccountId;1012 normalizedOwner: ICrossAccountId;1013 }| null> {1014 let tokenData;1015 if(typeof blockHashAt === 'undefined') {1016 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1017 }1018 else {1019 if(typeof propertyKeys === 'undefined') {1020 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1021 if(!collection) return null;1022 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1023 }1024 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1025 }1026 tokenData = tokenData.toHuman();1027 if (tokenData === null || tokenData.owner === null) return null;1028 const owner = {} as any;1029 for (const key of Object.keys(tokenData.owner)) {1030 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];1031 }1032 tokenData.normalizedOwner = crossAccountIdFromLower(owner);1033 return tokenData;1034 }10351036 10371038103910401041104210431044104510461047 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {1048 if(typeof label === 'undefined') label = `collection #${collectionId}`;1049 const result = await this.helper.executeExtrinsic(1050 signer,1051 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1052 true, `Unable to set token property permissions for ${label}`,1053 );10541055 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);1056 }10571058 1059106010611062106310641065106610671068 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {1069 if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1070 const result = await this.helper.executeExtrinsic(1071 signer,1072 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1073 true, `Unable to set token properties for ${label}`,1074 );10751076 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);1077 }10781079 1080108110821083108410851086108710881089 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {1090 if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1091 const result = await this.helper.executeExtrinsic(1092 signer,1093 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1094 true, `Unable to delete token properties for ${label}`,1095 );10961097 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);1098 }10991100 110111021103110411051106110711081109 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {1110 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; 1111 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1112 for (const key of ['name', 'description', 'tokenPrefix']) {1113 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);1114 }1115 const creationResult = await this.helper.executeExtrinsic(1116 signer,1117 'api.tx.unique.createCollectionEx', [collectionOptions],1118 true, errorLabel,1119 );1120 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1121 }11221123 getCollectionObject(collectionId: number): any {1124 return null;1125 }11261127 getTokenObject(collectionId: number, tokenId: number): any {1128 return null;1129 }1130}113111321133class NFTGroup extends NFTnRFT {1134 113511361137113811391140 getCollectionObject(collectionId: number): UniqueNFTCollection {1141 return new UniqueNFTCollection(collectionId, this.helper);1142 }11431144 1145114611471148114911501151 getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {1152 return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));1153 }11541155 11561157115811591160116111621163 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {1164 let owner;1165 if (typeof blockHashAt === 'undefined') {1166 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1167 } else {1168 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1169 }1170 return crossAccountIdFromLower(owner.toJSON());1171 }11721173 1174117511761177117811791180 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1181 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1182 }11831184 1185118611871188118911901191119211931194 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1195 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1196 }11971198 119912001201120212031204120512061207120812091210 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1211 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1212 }12131214 12151216121712181219122012211222 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {1223 let owner;1224 if (typeof blockHashAt === 'undefined') {1225 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1226 } else {1227 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1228 }12291230 if (owner === null) return null;12311232 owner = owner.toHuman();12331234 return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;1235 }12361237 12381239124012411242124312441245 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1246 let children;1247 if(typeof blockHashAt === 'undefined') {1248 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1249 } else {1250 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1251 }12521253 return children.toJSON().map((x: any) => {1254 return {collectionId: x.collection, tokenId: x.token};1255 });1256 }12571258 125912601261126212631264126512661267 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {1268 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1269 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1270 if(!result) {1271 throw Error(`Unable to nest token for ${label}`);1272 }1273 return result;1274 }12751276 1277127812791280128112821283128412851286 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {1287 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1288 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1289 if(!result) {1290 throw Error(`Unable to unnest token for ${label}`);1291 }1292 return result;1293 }12941295 1296129712981299130013011302130313041305130613071308 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {1309 return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;1310 }13111312 1313131413151316131713181319 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {1320 if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1321 const creationResult = await this.helper.executeExtrinsic(1322 signer,1323 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1324 nft: {1325 properties: data.properties,1326 },1327 }],1328 true, `Unable to mint NFT token for ${label}`,1329 );1330 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1331 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1332 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1333 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1334 }13351336 1337133813391340134113421343134413451346134713481349135013511352 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1353 if(typeof label === 'undefined') label = `collection #${collectionId}`;1354 const creationResult = await this.helper.executeExtrinsic(1355 signer,1356 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1357 true, `Unable to mint NFT tokens for ${label}`,1358 );1359 const collection = this.getCollectionObject(collectionId);1360 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1361 }13621363 1364136513661367136813691370137113721373137413751376137713781379138013811382 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1383 if(typeof label === 'undefined') label = `collection #${collectionId}`;1384 const rawTokens = [];1385 for (const token of tokens) {1386 const raw = {NFT: {properties: token.properties}};1387 rawTokens.push(raw);1388 }1389 const creationResult = await this.helper.executeExtrinsic(1390 signer,1391 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1392 true, `Unable to mint NFT tokens for ${label}`,1393 );1394 const collection = this.getCollectionObject(collectionId);1395 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1396 }13971398 139914001401140214031404140514061407 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {1408 return await super.burnToken(signer, collectionId, tokenId, label, 1n);1409 }14101411 14121413141414151416141714181419142014211422 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {1423 return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1424 }1425}142614271428class RFTGroup extends NFTnRFT {1429 143014311432143314341435 getCollectionObject(collectionId: number): UniqueRFTCollection {1436 return new UniqueRFTCollection(collectionId, this.helper);1437 }14381439 1440144114421443144414451446 getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {1447 return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));1448 }14491450 1451145214531454145514561457 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {1458 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);1459 }14601461 14621463146414651466146714681469 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1470 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1471 }14721473 1474147514761477147814791480148114821483 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {1484 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1485 }14861487 14881489149014911492149314941495149614971498 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {1499 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1500 }15011502 1503150415051506150715081509151015111512151315141515 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {1516 return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;1517 }15181519 15201521152215231524152515261527 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {1528 if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1529 const creationResult = await this.helper.executeExtrinsic(1530 signer,1531 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1532 refungible: {1533 pieces: data.pieces,1534 properties: data.properties,1535 },1536 }],1537 true, `Unable to mint RFT token for ${label}`,1538 );1539 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1540 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1541 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1542 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1543 }15441545 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1546 throw Error('Not implemented');1547 if(typeof label === 'undefined') label = `collection #${collectionId}`;1548 const creationResult = await this.helper.executeExtrinsic(1549 signer,1550 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1551 true, `Unable to mint RFT tokens for ${label}`,1552 );1553 const collection = this.getCollectionObject(collectionId);1554 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1555 }15561557 1558155915601561156215631564156515661567 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1568 if(typeof label === 'undefined') label = `collection #${collectionId}`;1569 const rawTokens = [];1570 for (const token of tokens) {1571 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1572 rawTokens.push(raw);1573 }1574 const creationResult = await this.helper.executeExtrinsic(1575 signer,1576 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1577 true, `Unable to mint RFT tokens for ${label}`,1578 );1579 const collection = this.getCollectionObject(collectionId);1580 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1581 }15821583 1584158515861587158815891590159115921593 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {1594 return await super.burnToken(signer, collectionId, tokenId, label, amount);1595 }15961597 159815991600160116021603160416051606160716081609 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {1610 return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1611 }16121613 1614161516161617161816191620 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1621 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1622 }16231624 1625162616271628162916301631163216331634 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {1635 if(typeof label === 'undefined') label = `collection #${collectionId}`;1636 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1637 const repartitionResult = await this.helper.executeExtrinsic(1638 signer,1639 'api.tx.unique.repartition', [collectionId, tokenId, amount],1640 true, `Unable to repartition RFT token for ${label}`,1641 );1642 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);1643 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);1644 }1645}164616471648class FTGroup extends CollectionGroup {1649 165016511652165316541655 getCollectionObject(collectionId: number): UniqueFTCollection {1656 return new UniqueFTCollection(collectionId, this.helper);1657 }16581659 16601661166216631664166516661667166816691670167116721673 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {1674 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; 1675 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1676 collectionOptions.mode = {fungible: decimalPoints};1677 for (const key of ['name', 'description', 'tokenPrefix']) {1678 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);1679 }1680 const creationResult = await this.helper.executeExtrinsic(1681 signer,1682 'api.tx.unique.createCollectionEx', [collectionOptions],1683 true, errorLabel,1684 );1685 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1686 }16871688 1689169016911692169316941695169616971698 async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {1699 if(typeof label === 'undefined') label = `collection #${collectionId}`;1700 const creationResult = await this.helper.executeExtrinsic(1701 signer,1702 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1703 fungible: {1704 value: amount,1705 },1706 }],1707 true, `Unable to mint fungible tokens for ${label}`,1708 );1709 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1710 }17111712 171317141715171617171718171917201721 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {1722 if(typeof label === 'undefined') label = `collection #${collectionId}`;1723 const rawTokens = [];1724 for (const token of tokens) {1725 const raw = {Fungible: {Value: token.value}};1726 rawTokens.push(raw);1727 }1728 const creationResult = await this.helper.executeExtrinsic(1729 signer,1730 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1731 true, `Unable to mint RFT tokens for ${label}`,1732 );1733 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1734 }17351736 173717381739174017411742 async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {1743 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);1744 }17451746 1747174817491750175117521753 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1754 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1755 }17561757 175817591760176117621763176417651766 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {1767 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1768 }17691770 1771177217731774177517761777177817791780 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {1781 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1782 }17831784 178517861787178817891790179117921793 async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {1794 return (await super.burnToken(signer, collectionId, 0, label, amount)).success;1795 }17961797 1798179918001801180218031804180518061807 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {1808 return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);1809 }18101811 18121813181418151816 async getTotalPieces(collectionId: number): Promise<bigint> {1817 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();1818 }18191820 18211822182318241825182618271828182918301831 async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {1832 return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);1833 }18341835 1836183718381839184018411842 async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {1843 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);1844 }1845}184618471848class ChainGroup extends HelperGroup {1849 18501851185218531854 getChainProperties(): IChainProperties {1855 const properties = (this.helper.api as any).registry.getChainProperties().toJSON();1856 return {1857 ss58Format: properties.ss58Format.toJSON(),1858 tokenDecimals: properties.tokenDecimals.toJSON(),1859 tokenSymbol: properties.tokenSymbol.toJSON(),1860 };1861 }18621863 18641865186618671868 async getLatestBlockNumber(): Promise<number> {1869 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();1870 }18711872 187318741875187618771878 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {1879 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();1880 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;1881 return blockHash;1882 }18831884 188518861887188818891890 async getNonce(address: TSubstrateAccount): Promise<number> {1891 return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber();1892 }1893}189418951896class BalanceGroup extends HelperGroup {1897 18981899190019011902 getOneTokenNominal(): bigint {1903 const chainProperties = this.helper.chain.getChainProperties();1904 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);1905 }19061907 190819091910191119121913 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {1914 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();1915 }19161917 191819191920192119221923 async getEthereum(address: TEthereumAccount): Promise<bigint> {1924 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();1925 }19261927 19281929193019311932193319341935 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {1936 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}`);19371938 let transfer = {from: null, to: null, amount: 0n} as any;1939 result.result.events.forEach(({event: {data, method, section}}) => {1940 if ((section === 'balances') && (method === 'Transfer')) {1941 transfer = {1942 from: this.helper.address.normalizeSubstrate(data[0]),1943 to: this.helper.address.normalizeSubstrate(data[1]),1944 amount: BigInt(data[2]),1945 };1946 }1947 });1948 let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;1949 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;1950 isSuccess = isSuccess && BigInt(amount) === transfer.amount;1951 return isSuccess;1952 }1953}195419551956class AddressGroup extends HelperGroup {1957 1958195919601961196219631964 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {1965 return this.helper.util.normalizeSubstrateAddress(address, ss58Format);1966 }19671968 196919701971197219731974 async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise<TSubstrateAccount> {1975 const info = this.helper.chain.getChainProperties();1976 return encodeAddress(decodeAddress(address), info.ss58Format);1977 }19781979 1980198119821983198419851986 async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise<TSubstrateAccount> {1987 if(!toChainFormat) return evmToAddress(ethAddress);1988 const info = this.helper.chain.getChainProperties();1989 return evmToAddress(ethAddress, info.ss58Format);1990 }19911992 199319941995199619971998 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {1999 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join(''));2000 }2001}200220032004export class UniqueHelper extends ChainHelperBase {2005 chain: ChainGroup;2006 balance: BalanceGroup;2007 address: AddressGroup;2008 collection: CollectionGroup;2009 nft: NFTGroup;2010 rft: RFTGroup;2011 ft: FTGroup;20122013 constructor(logger?: ILogger) {2014 super(logger);2015 this.chain = new ChainGroup(this);2016 this.balance = new BalanceGroup(this);2017 this.address = new AddressGroup(this);2018 this.collection = new CollectionGroup(this);2019 this.nft = new NFTGroup(this);2020 this.rft = new RFTGroup(this);2021 this.ft = new FTGroup(this);2022 }2023}202420252026class UniqueCollectionBase {2027 helper: UniqueHelper;2028 collectionId: number;20292030 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2031 this.collectionId = collectionId;2032 this.helper = uniqueHelper;2033 }20342035 async getData() {2036 return await this.helper.collection.getData(this.collectionId);2037 }20382039 async getLastTokenId() {2040 return await this.helper.collection.getLastTokenId(this.collectionId);2041 }20422043 async isTokenExists(tokenId: number) {2044 return await this.helper.collection.isTokenExists(this.collectionId, tokenId);2045 }20462047 async getAdmins() {2048 return await this.helper.collection.getAdmins(this.collectionId);2049 }20502051 async getAllowList() {2052 return await this.helper.collection.getAllowList(this.collectionId);2053 }20542055 async getEffectiveLimits() {2056 return await this.helper.collection.getEffectiveLimits(this.collectionId);2057 }20582059 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) {2060 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label);2061 }20622063 async confirmSponsorship(signer: TSigner, label?: string) {2064 return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label);2065 }20662067 async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) {2068 return await this.helper.collection.setLimits(signer, this.collectionId, limits, label);2069 }20702071 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) {2072 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label);2073 }20742075 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2076 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label);2077 }20782079 async addToAllowList(signer: TSigner, addressObj: ICrossAccountId, label?: string) {2080 return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj, label);2081 }20822083 async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId, label?: string) {2084 return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj, label);2085 }20862087 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2088 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label);2089 }20902091 async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2092 return await this.helper.collection.setProperties(signer, this.collectionId, properties, label);2093 }20942095 async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2096 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label);2097 }20982099 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2100 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2101 }21022103 async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) {2104 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label);2105 }21062107 async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) {2108 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label);2109 }21102111 async disableNesting(signer: TSigner, label?: string) {2112 return await this.helper.collection.disableNesting(signer, this.collectionId, label);2113 }21142115 async burn(signer: TSigner, label?: string) {2116 return await this.helper.collection.burn(signer, this.collectionId, label);2117 }2118}211921202121class UniqueNFTCollection extends UniqueCollectionBase {2122 getTokenObject(tokenId: number) {2123 return new UniqueNFTToken(tokenId, this);2124 }21252126 async getTokensByAddress(addressObj: ICrossAccountId) {2127 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);2128 }21292130 async getToken(tokenId: number, blockHashAt?: string) {2131 return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt);2132 }21332134 async getTokenOwner(tokenId: number, blockHashAt?: string) {2135 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);2136 }21372138 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {2139 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);2140 }21412142 async getTokenChildren(tokenId: number, blockHashAt?: string) {2143 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2144 }21452146 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2147 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2148 }21492150 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2151 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);2152 }21532154 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {2155 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label);2156 }21572158 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {2159 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);2160 }21612162 async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) {2163 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label);2164 }21652166 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) {2167 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label);2168 }21692170 async burnToken(signer: TSigner, tokenId: number, label?: string) {2171 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label);2172 }21732174 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2175 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2176 }21772178 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2179 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2180 }21812182 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2183 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2184 }21852186 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) {2187 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label);2188 }21892190 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2191 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label);2192 }2193}219421952196class UniqueRFTCollection extends UniqueCollectionBase {2197 getTokenObject(tokenId: number) {2198 return new UniqueRFTToken(tokenId, this);2199 }22002201 async getTokensByAddress(addressObj: ICrossAccountId) {2202 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2203 }22042205 async getTop10TokenOwners(tokenId: number) {2206 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);2207 }22082209 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {2210 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);2211 }22122213 async getTokenTotalPieces(tokenId: number) {2214 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2215 }22162217 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) {2218 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2219 }22202221 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2222 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);2223 }22242225 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2226 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount);2227 }22282229 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2230 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);2231 }22322233 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) {2234 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label);2235 }22362237 async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) {2238 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label);2239 }22402241 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) {2242 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label);2243 }22442245 async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) {2246 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount);2247 }22482249 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2250 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2251 }22522253 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2254 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2255 }22562257 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2258 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2259 }2260}226122622263class UniqueFTCollection extends UniqueCollectionBase {2264 async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) {2265 return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label);2266 }22672268 async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) {2269 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label);2270 }22712272 async getBalance(addressObj: ICrossAccountId) {2273 return await this.helper.ft.getBalance(this.collectionId, addressObj);2274 }22752276 async getTop10Owners() {2277 return await this.helper.ft.getTop10Owners(this.collectionId);2278 }22792280 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) {2281 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);2282 }22832284 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {2285 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);2286 }22872288 async burnTokens(signer: TSigner, amount: bigint, label?: string) {2289 return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label);2290 }22912292 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) {2293 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label);2294 }22952296 async getTotalPieces() {2297 return await this.helper.ft.getTotalPieces(this.collectionId);2298 }22992300 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2301 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label);2302 }23032304 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2305 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);2306 }2307}230823092310class UniqueTokenBase implements IToken {2311 collection: UniqueNFTCollection | UniqueRFTCollection;2312 collectionId: number;2313 tokenId: number;23142315 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {2316 this.collection = collection;2317 this.collectionId = collection.collectionId;2318 this.tokenId = tokenId;2319 }23202321 async getNextSponsored(addressObj: ICrossAccountId) {2322 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2323 }23242325 async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2326 return await this.collection.setTokenProperties(signer, this.tokenId, properties, label);2327 }23282329 async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2330 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label);2331 }2332}233323342335class UniqueNFTToken extends UniqueTokenBase {2336 collection: UniqueNFTCollection;23372338 constructor(tokenId: number, collection: UniqueNFTCollection) {2339 super(tokenId, collection);2340 this.collection = collection;2341 }23422343 async getData(blockHashAt?: string) {2344 return await this.collection.getToken(this.tokenId, blockHashAt);2345 }23462347 async getOwner(blockHashAt?: string) {2348 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);2349 }23502351 async getTopmostOwner(blockHashAt?: string) {2352 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);2353 }23542355 async getChildren(blockHashAt?: string) {2356 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);2357 }23582359 async nest(signer: TSigner, toTokenObj: IToken, label?: string) {2360 return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label);2361 }23622363 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2364 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label);2365 }23662367 async transfer(signer: TSigner, addressObj: ICrossAccountId) {2368 return await this.collection.transferToken(signer, this.tokenId, addressObj);2369 }23702371 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2372 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);2373 }23742375 async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) {2376 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label);2377 }23782379 async isApproved(toAddressObj: ICrossAccountId) {2380 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);2381 }23822383 async burn(signer: TSigner, label?: string) {2384 return await this.collection.burnToken(signer, this.tokenId, label);2385 }2386}23872388class UniqueRFTToken extends UniqueTokenBase {2389 collection: UniqueRFTCollection;23902391 constructor(tokenId: number, collection: UniqueRFTCollection) {2392 super(tokenId, collection);2393 this.collection = collection;2394 }23952396 async getTop10Owners() {2397 return await this.collection.getTop10TokenOwners(this.tokenId);2398 }23992400 async getBalance(addressObj: ICrossAccountId) {2401 return await this.collection.getTokenBalance(this.tokenId, addressObj);2402 }24032404 async getTotalPieces() {2405 return await this.collection.getTokenTotalPieces(this.tokenId);2406 }24072408 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) {2409 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);2410 }24112412 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2413 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);2414 }24152416 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2417 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label);2418 }24192420 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {2421 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);2422 }24232424 async repartition(signer: TSigner, amount: bigint, label?: string) {2425 return await this.collection.repartitionToken(signer, this.tokenId, amount, label);2426 }24272428 async burn(signer: TSigner, amount=100n, label?: string) {2429 return await this.collection.burnToken(signer, this.tokenId, amount, label);2430 }2431}