git.delta.rocks / unique-network / refs/commits / adb854cc5dc6

difftreelog

tests(util): introduce CrossAccountId class to take on some util functionality

Fahrrader2022-09-28parent: #a9c2603.patch.diff
in: master

4 files changed

modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
35 // Create an immediately nested token35 // Create an immediately nested token
36 const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount());36 const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount());
37 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});37 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
38 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());38 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
39 39
40 // Create a token to be nested40 // Create a token to be nested
41 const newToken = await collection.mintToken(alice);41 const newToken = await collection.mintToken(alice);
4242
43 // Nest43 // Nest
44 await newToken.nest(alice, targetToken);44 await newToken.nest(alice, targetToken);
45 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});45 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
46 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());46 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
4747
48 // Move bundle to different user48 // Move bundle to different user
49 await targetToken.transfer(alice, {Substrate: bob.address});49 await targetToken.transfer(alice, {Substrate: bob.address});
50 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});50 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
51 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());51 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
52 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});52 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
53 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());53 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
5454
55 // Unnest55 // Unnest
56 await newToken.unnest(bob, targetToken, {Substrate: bob.address});56 await newToken.unnest(bob, targetToken, {Substrate: bob.address});
6565
66 // Create a nested token66 // Create a nested token
67 const tokenC = await collection.mintToken(alice, tokenA.nestingAccount());67 const tokenC = await collection.mintToken(alice, tokenA.nestingAccount());
68 expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAccountInLowerCase());68 expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
69 69
70 // Transfer the nested token to another token70 // Transfer the nested token to another token
71 await expect(tokenC.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount())).to.be.fulfilled;71 await expect(tokenC.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount())).to.be.fulfilled;
72 expect(await tokenC.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});72 expect(await tokenC.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
73 expect(await tokenC.getOwner()).to.be.deep.equal(tokenB.nestingAccountInLowerCase());73 expect(await tokenC.getOwner()).to.be.deep.equal(tokenB.nestingAccount().toLowerCase());
74 });74 });
7575
76 itSub('Checks token children', async ({helper}) => {76 itSub('Checks token children', async ({helper}) => {
150 // Create an immediately nested token150 // Create an immediately nested token
151 const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount());151 const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount());
152 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});152 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
153 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());153 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
154154
155 // Create a token to be nested and nest155 // Create a token to be nested and nest
156 const newToken = await collection.mintToken(bob);156 const newToken = await collection.mintToken(bob);
157 await newToken.nest(bob, targetToken);157 await newToken.nest(bob, targetToken);
158 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});158 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
159 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());159 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
160 });160 });
161161
162 itSub('Admin (NFT): Admin and Token Owner can operate together', async ({helper}) => {162 itSub('Admin (NFT): Admin and Token Owner can operate together', async ({helper}) => {
167 // Create an immediately nested token by an administrator167 // Create an immediately nested token by an administrator
168 const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount());168 const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount());
169 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});169 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
170 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());170 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
171171
172 // Create a token to be nested and nest172 // Create a token to be nested and nest
173 const newToken = await collection.mintToken(alice, {Substrate: charlie.address});173 const newToken = await collection.mintToken(alice, {Substrate: charlie.address});
174 await newToken.nest(charlie, targetToken);174 await newToken.nest(charlie, targetToken);
175 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});175 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
176 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());176 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
177 });177 });
178178
179 itSub('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async ({helper}) => {179 itSub('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async ({helper}) => {
187 // Create an immediately nested token187 // Create an immediately nested token
188 const nestedToken = await collectionB.mintToken(bob, targetToken.nestingAccount());188 const nestedToken = await collectionB.mintToken(bob, targetToken.nestingAccount());
189 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});189 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
190 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());190 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
191191
192 // Create a token to be nested and nest192 // Create a token to be nested and nest
193 const newToken = await collectionB.mintToken(bob);193 const newToken = await collectionB.mintToken(bob);
194 await newToken.nest(bob, targetToken);194 await newToken.nest(bob, targetToken);
195 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});195 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
196 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());196 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
197 });197 });
198198
199 // ---------- Non-Fungible ----------199 // ---------- Non-Fungible ----------
207 // Create an immediately nested token207 // Create an immediately nested token
208 const nestedToken = await collection.mintToken(charlie, targetToken.nestingAccount());208 const nestedToken = await collection.mintToken(charlie, targetToken.nestingAccount());
209 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});209 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
210 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());210 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
211211
212 // Create a token to be nested and nest212 // Create a token to be nested and nest
213 const newToken = await collection.mintToken(charlie);213 const newToken = await collection.mintToken(charlie);
214 await newToken.nest(charlie, targetToken);214 await newToken.nest(charlie, targetToken);
215 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});215 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
216 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());216 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
217 });217 });
218218
219 itSub('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {219 itSub('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {
233 // Create an immediately nested token233 // Create an immediately nested token
234 const nestedToken = await collectionB.mintToken(charlie, targetToken.nestingAccount());234 const nestedToken = await collectionB.mintToken(charlie, targetToken.nestingAccount());
235 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});235 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
236 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());236 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
237237
238 // Create a token to be nested and nest238 // Create a token to be nested and nest
239 const newToken = await collectionB.mintToken(charlie);239 const newToken = await collectionB.mintToken(charlie);
240 await newToken.nest(charlie, targetToken);240 await newToken.nest(charlie, targetToken);
241 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});241 expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address});
242 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());242 expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
243 });243 });
244244
245 // ---------- Fungible ----------245 // ---------- Fungible ----------
424424
425 expect(await targetToken.getChildren()).to.be.length(1);425 expect(await targetToken.getChildren()).to.be.length(1);
426 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});426 expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
427 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());427 expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
428 });428 });
429429
430 itSub('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async ({helper}) => {430 itSub('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async ({helper}) => {
modifiedtests/src/nesting/unnest.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -105,11 +105,11 @@
 
     // Try to unnest
     await expect(nestedToken.unnest(bob, targetToken, {Substrate: alice.address})).to.be.rejectedWith(/common\.ApprovedValueTooLow/);
-    expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
+    expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
 
     // Try to burn
     await expect(nestedToken.burnFrom(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.ApprovedValueTooLow/);
-    expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase());
+    expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
   });
 
   // todo another test for creating excessive depth matryoshka with Ethereum?
modifiedtests/src/rpc.test.tsdiffbeforeafterboth
--- a/tests/src/rpc.test.ts
+++ b/tests/src/rpc.test.ts
@@ -16,7 +16,7 @@
 
 import {IKeyringPair} from '@polkadot/types/types';
 import {usingPlaygrounds, itSub, expect} from './util/playgrounds';
-import {crossAccountIdFromLower} from './util/playgrounds/unique';
+import {CrossAccountId} from './util/playgrounds/unique';
 
 describe('integration test: RPC methods', () => {
   let donor: IKeyringPair;
@@ -55,7 +55,7 @@
     // Set-up over
 
     const owners = await helper.callRpc('api.rpc.unique.tokenOwners', [collection.collectionId, 0]);
-    const ids = (owners.toJSON() as any[]).map(crossAccountIdFromLower);
+    const ids = (owners.toJSON() as any[]).map(CrossAccountId.fromLowerCaseKeys);
 
     expect(ids).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]);
     expect(owners.length == 10).to.be.true;
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -11,12 +11,42 @@
 import {IKeyringPair} from '@polkadot/types/types';
 import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types';
 
-export const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {
-  const address = {} as ICrossAccountId;
-  if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate;
-  if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum;
-  return address;
-};
+export class CrossAccountId implements ICrossAccountId {
+  Substrate?: TSubstrateAccount;
+  Ethereum?: TEthereumAccount;
+
+  constructor(account: ICrossAccountId) {
+    if (account.Substrate) this.Substrate = account.Substrate;
+    if (account.Ethereum) this.Ethereum = account.Ethereum;
+  }
+
+  static fromKeyring(account: IKeyringPair) {
+    return new CrossAccountId({Substrate: account.address});
+  }
+
+  static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {
+    return new CrossAccountId({Substrate: address.substrate, Ethereum: address.ethereum});
+  }
+
+  static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {
+    return encodeAddress(decodeAddress(address), ss58Format);
+  }
+
+  static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {
+    return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});
+  }
+  
+  withNormalizedSubstrate(ss58Format = 42): CrossAccountId {
+    if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);
+    return this;
+  }
+  
+  toLowerCase(): CrossAccountId {
+    if (this.Substrate) this.Substrate = this.Substrate.toLowerCase();
+    if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();
+    return this;
+  }
+}
 
 const nesting = {
   toChecksumAddress(address: string): string {
@@ -55,12 +85,8 @@
     RPC: 'rpc',
   };
 
-  static getTokenAccount(token: IToken): ICrossAccountId {
-    return {Ethereum: this.getTokenAddress(token)};
-  }
-
-  static getTokenAccountInLowerCase(token: IToken): ICrossAccountId {
-    return {Ethereum: this.getTokenAddress(token).toLowerCase()};
+  static getTokenAccount(token: IToken): CrossAccountId {
+    return new CrossAccountId({Ethereum: this.getTokenAddress(token)});
   }
 
   static getTokenAddress(token: IToken): string {
@@ -92,10 +118,6 @@
   static fromSeed(seed: string, ss58Format = 42) {
     const keyring = new Keyring({type: 'sr25519', ss58Format});
     return keyring.addFromUri(seed);
-  }
-
-  static normalizeSubstrateAddress(address: string, ss58Format = 42) {
-    return encodeAddress(decodeAddress(address), ss58Format);
   }
 
   static extractCollectionIdFromCreationResult(creationResult: ITransactionResult) {
@@ -178,8 +200,8 @@
       Object.keys(address).forEach(k => {
         obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];
       });
-      if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)};
-      if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()};
+      if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);
+      if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();
       return address;
     };
     let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;
@@ -563,7 +585,7 @@
     name: string;
     description: string;
     tokensCount: number;
-    admins: ICrossAccountId[];
+    admins: CrossAccountId[];
     normalizedOwner: TSubstrateAccount;
     raw: any
   } | null> {
@@ -596,11 +618,11 @@
    * @example await getAdmins(1)
    * @returns array of administrators
    */
-  async getAdmins(collectionId: number, normalize = false): Promise<ICrossAccountId[]> {
+  async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {
     const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();
 
     return normalize
-      ? admins.map((address: any) => this.helper.address.normalizeCrossAccountIfSubstrate(address))
+      ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())
       : admins;
   }
 
@@ -611,10 +633,10 @@
    * @example await getAllowList(1)
    * @returns array of allow-listed addresses
    */
-  async getAllowList(collectionId: number, normalize = false): Promise<ICrossAccountId[]> {
+  async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {
     const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();
     return normalize
-      ? allowListed.map((address: any) => this.helper.address.normalizeCrossAccountIfSubstrate(address))
+      ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())
       : allowListed;
   }
 
@@ -1099,8 +1121,8 @@
    */
   async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{
     properties: IProperty[];
-    owner: ICrossAccountId;
-    normalizedOwner: ICrossAccountId;
+    owner: CrossAccountId;
+    normalizedOwner: CrossAccountId;
   }| null> {
     let tokenData;
     if(typeof blockHashAt === 'undefined') {
@@ -1118,9 +1140,9 @@
     if (tokenData === null || tokenData.owner === null) return null;
     const owner = {} as any;
     for (const key of Object.keys(tokenData.owner)) {
-      owner[key.toLocaleLowerCase()] = this.helper.address.normalizeCrossAccountIfSubstrate(tokenData.owner[key]);
+      owner[key.toLocaleLowerCase()] = new CrossAccountId(tokenData.owner[key]).withNormalizedSubstrate();
     }
-    tokenData.normalizedOwner = crossAccountIdFromLower(owner);
+    tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);
     return tokenData;
   }
 
@@ -1272,14 +1294,14 @@
    * @example getTokenOwner(10, 5);
    * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}
    */
-  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {
+  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {
     let owner;
     if (typeof blockHashAt === 'undefined') {
       owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);
     } else {
       owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);
     }
-    return crossAccountIdFromLower(owner.toJSON());
+    return CrossAccountId.fromLowerCaseKeys(owner.toJSON());
   }
 
   /**
@@ -1331,7 +1353,7 @@
    * @example getTokenTopmostOwner(10, 5);
    * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}
    */
-  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {
+  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {
     let owner;
     if (typeof blockHashAt === 'undefined') {
       owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);
@@ -1541,8 +1563,8 @@
    * @example getTokenTop10Owners(10, 5);
    * @returns array of top 10 owners
    */
-  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {
-    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);
+  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {
+    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);
   }
 
   /**
@@ -1825,8 +1847,8 @@
    * @example getTop10Owners(10);
    * @returns array of ```ICrossAccountId```
    */
-  async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {
-    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);
+  async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {
+    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);
   }
 
   /**
@@ -2045,9 +2067,9 @@
         };
       }
     });
-    let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;
-    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;
-    isSuccess = isSuccess && BigInt(amount) === transfer.amount;
+    const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from 
+      && this.helper.address.normalizeSubstrate(address) === transfer.to 
+      && BigInt(amount) === transfer.amount;
     return isSuccess;
   }
 }
@@ -2062,20 +2084,7 @@
    * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation
    */
   normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {
-    return this.helper.util.normalizeSubstrateAddress(address, ss58Format);
-  }
-
-  /**
-   * Normalizes the address of an account ONLY if it's Substrate to the specified ss58 format, by default ```42```.
-   * @param account account of either Substrate type or Ethereum, but only Substrate will be changed
-   * @param ss58Format format for address conversion, by default ```42```
-   * @example normalizeCrossAccountIfSubstrate({Substrate: "unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx"}) // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
-   * @returns untouched ethereum account or substrate account converted to normalized (i.e., starting with 5) or specified explicitly representation
-   */
-  normalizeCrossAccountIfSubstrate(account: ICrossAccountId, ss58Format = 42): ICrossAccountId  {
-    return account.Substrate
-      ? {Substrate: this.normalizeSubstrate(account.Substrate, ss58Format)}
-      : account;
+    return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);
   }
 
   /**
@@ -2572,10 +2581,6 @@
 
   nestingAccount() {
     return this.collection.helper.util.getTokenAccount(this);
-  }
-
-  nestingAccountInLowerCase() {
-    return this.collection.helper.util.getTokenAccountInLowerCase(this);
   }
 }