--- a/tests/src/fungible.test.ts +++ b/tests/src/fungible.test.ts @@ -15,9 +15,7 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import {itSub, usingPlaygrounds, expect} from './util/playgrounds'; - -const U128_MAX = (1n << 128n) - 1n; +import {itSub, usingPlaygrounds, expect, U128_MAX} from './util/playgrounds'; describe('integration test: Fungible functionality:', () => { let alice: IKeyringPair; --- a/tests/src/nesting/graphs.test.ts +++ b/tests/src/nesting/graphs.test.ts @@ -16,7 +16,8 @@ import {IKeyringPair} from '@polkadot/types/types'; import {expect, itSub, usingPlaygrounds} from '../util/playgrounds'; -import {UniqueHelper, UniqueNFTToken} from '../util/playgrounds/unique'; +import {ITokenNonfungible} from '../util/playgrounds/types'; +import {UniqueHelper} from '../util/playgrounds/unique'; /** * ```dot @@ -25,7 +26,7 @@ * 8 -> 5 * ``` */ -async function buildComplexObjectGraph(helper: UniqueHelper, sender: IKeyringPair): Promise { +async function buildComplexObjectGraph(helper: UniqueHelper, sender: IKeyringPair): Promise { const collection = await helper.nft.mintCollection(sender, {permissions: {nesting: {tokenOwner: true}}}); const tokens = await collection.mintMultipleTokens(sender, Array(8).fill({owner: {Substrate: sender.address}})); --- a/tests/src/nesting/nest.test.ts +++ b/tests/src/nesting/nest.test.ts @@ -35,7 +35,7 @@ // Create an immediately nested token const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Create a token to be nested const newToken = await collection.mintToken(alice); @@ -43,14 +43,14 @@ // Nest await newToken.nest(alice, targetToken); expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Move bundle to different user await targetToken.transfer(alice, {Substrate: bob.address}); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Unnest await newToken.unnest(bob, targetToken, {Substrate: bob.address}); @@ -65,12 +65,12 @@ // Create a nested token const tokenC = await collection.mintToken(alice, tokenA.nestingAccount()); - expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAccount()); + expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAccountInLowerCase()); // Transfer the nested token to another token await expect(tokenC.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount())).to.be.fulfilled; expect(await tokenC.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); - expect(await tokenC.getOwner()).to.be.deep.equal(tokenB.nestingAccount()); + expect(await tokenC.getOwner()).to.be.deep.equal(tokenB.nestingAccountInLowerCase()); }); itSub('Checks token children', async ({helper}) => { @@ -150,13 +150,13 @@ // Create an immediately nested token const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount()); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Create a token to be nested and nest const newToken = await collection.mintToken(bob); await newToken.nest(bob, targetToken); expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); }); itSub('Admin (NFT): Admin and Token Owner can operate together', async ({helper}) => { @@ -167,13 +167,13 @@ // Create an immediately nested token by an administrator const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount()); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Create a token to be nested and nest const newToken = await collection.mintToken(alice, {Substrate: charlie.address}); await newToken.nest(charlie, targetToken); expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); }); itSub('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async ({helper}) => { @@ -187,13 +187,13 @@ // Create an immediately nested token const nestedToken = await collectionB.mintToken(bob, targetToken.nestingAccount()); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Create a token to be nested and nest const newToken = await collectionB.mintToken(bob); await newToken.nest(bob, targetToken); expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); }); // ---------- Non-Fungible ---------- @@ -207,13 +207,13 @@ // Create an immediately nested token const nestedToken = await collection.mintToken(charlie, targetToken.nestingAccount()); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Create a token to be nested and nest const newToken = await collection.mintToken(charlie); await newToken.nest(charlie, targetToken); expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); }); itSub('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => { @@ -233,13 +233,13 @@ // Create an immediately nested token const nestedToken = await collectionB.mintToken(charlie, targetToken.nestingAccount()); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Create a token to be nested and nest const newToken = await collectionB.mintToken(charlie); await newToken.nest(charlie, targetToken); expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); - expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); }); // ---------- Fungible ---------- @@ -424,7 +424,7 @@ expect(await targetToken.getChildren()).to.be.length(1); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); }); itSub('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async ({helper}) => { --- a/tests/src/nesting/properties.test.ts +++ b/tests/src/nesting/properties.test.ts @@ -14,19 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -/*import usingApi, {executeTransaction} from '../substrate/substrate-api'; -import { - addCollectionAdminExpectSuccess, - CollectionMode, - createCollectionExpectSuccess, - setCollectionPermissionsExpectSuccess, - createItemExpectSuccess, - getCreateCollectionResult, - transferExpectSuccess, -} from '../util/helpers';*/ import {IKeyringPair} from '@polkadot/types/types'; import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds'; -import {UniqueCollectionBase, UniqueHelper, UniqueNFTCollection, UniqueNFTToken, UniqueRFTCollection, UniqueRFTToken} from '../util/playgrounds/unique'; +import {ICollectionBase, ICollectionNFT, ITokenNonfungible, ICollectionRFT, ITokenRefungible} from '../util/playgrounds/types'; +import {UniqueHelper} from '../util/playgrounds/unique'; // ---------- COLLECTION PROPERTIES @@ -46,7 +37,7 @@ expect(await collection.getProperties()).to.be.empty; }); - async function testSetsPropertiesForCollection(collection: UniqueCollectionBase) { + async function testSetsPropertiesForCollection(collection: ICollectionBase) { // As owner await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled; @@ -70,7 +61,7 @@ await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice)); }); - async function testCheckValidNames(collection: UniqueCollectionBase) { + async function testCheckValidNames(collection: ICollectionBase) { // alpha symbols await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled; @@ -104,7 +95,7 @@ await testCheckValidNames(await helper.rft.mintCollection(alice)); }); - async function testChangesProperties(collection: UniqueCollectionBase) { + async function testChangesProperties(collection: ICollectionBase) { await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled; // Mutate the properties @@ -125,7 +116,7 @@ await testChangesProperties(await helper.rft.mintCollection(alice)); }); - async function testDeleteProperties(collection: UniqueCollectionBase) { + async function testDeleteProperties(collection: ICollectionBase) { await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled; await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled; @@ -156,7 +147,7 @@ }); }); - async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueCollectionBase) { + async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: ICollectionBase) { await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])) .to.be.rejectedWith(/common\.NoPermission/); @@ -171,7 +162,7 @@ await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice)); }); - async function testFailsSetPropertiesThatExeedLimits(collection: UniqueCollectionBase) { + async function testFailsSetPropertiesThatExeedLimits(collection: ICollectionBase) { const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber(); // Mute the general tx parsing error, too many bytes to process @@ -200,7 +191,7 @@ await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice)); }); - async function testFailsSetMorePropertiesThanAllowed(collection: UniqueCollectionBase) { + async function testFailsSetMorePropertiesThanAllowed(collection: ICollectionBase) { const propertiesToBeSet = []; for (let i = 0; i < 65; i++) { propertiesToBeSet.push({ @@ -223,7 +214,7 @@ await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice)); }); - async function testFailsSetPropertiesWithInvalidNames(collection: UniqueCollectionBase) { + async function testFailsSetPropertiesWithInvalidNames(collection: ICollectionBase) { const invalidProperties = [ [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}], [{key: 'Mr/Sandman', value: 'Bring me a gene'}], @@ -290,7 +281,7 @@ expect(propertyRights).to.be.empty; }); - async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { + async function testSetsAccessRightsToProperties(collection: ICollectionNFT | ICollectionRFT) { await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true}}])) .to.be.fulfilled; @@ -314,7 +305,7 @@ await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice)); }); - async function testChangesAccessRightsToProperty(collection: UniqueNFTCollection | UniqueRFTCollection) { + async function testChangesAccessRightsToProperty(collection: ICollectionNFT | ICollectionRFT) { await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}])) .to.be.fulfilled; @@ -347,7 +338,7 @@ }); }); - async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: UniqueNFTCollection | UniqueRFTCollection) { + async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: ICollectionNFT | ICollectionRFT) { await expect(collection.setTokenPropertyPermissions(bob, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}])) .to.be.rejectedWith(/common\.NoPermission/); @@ -363,7 +354,7 @@ await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice)); }); - async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { + async function testPreventFromAddingTooManyPossibleProperties(collection: ICollectionNFT | ICollectionRFT) { const constitution = []; for (let i = 0; i < 65; i++) { constitution.push({ @@ -387,7 +378,7 @@ await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice)); }); - async function testPreventAccessRightsModifiedIfConstant(collection: UniqueNFTCollection | UniqueRFTCollection) { + async function testPreventAccessRightsModifiedIfConstant(collection: ICollectionNFT | ICollectionRFT) { await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}])) .to.be.fulfilled; @@ -408,7 +399,7 @@ await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice)); }); - async function testPreventsAddingPropertiesWithInvalidNames(collection: UniqueNFTCollection | UniqueRFTCollection) { + async function testPreventsAddingPropertiesWithInvalidNames(collection: ICollectionNFT | ICollectionRFT) { const invalidProperties = [ [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}], [{key: 'G#4', permission: {tokenOwner: true}}], @@ -478,7 +469,7 @@ ]; }); - async function testReadsYetEmptyProperties(token: UniqueNFTToken | UniqueRFTToken) { + async function testReadsYetEmptyProperties(token: ITokenNonfungible | ITokenRefungible) { const properties = await token.getProperties(); expect(properties).to.be.empty; @@ -498,7 +489,7 @@ await testReadsYetEmptyProperties(token); }); - async function testAssignPropertiesAccordingToPermissions(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) { + async function testAssignPropertiesAccordingToPermissions(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) { await token.collection.addAdmin(alice, {Substrate: bob.address}); await token.transfer(alice, {Substrate: charlie.address}, pieces); @@ -544,7 +535,7 @@ await testAssignPropertiesAccordingToPermissions(token, 100n); }); - async function testChangesPropertiesAccordingPermission(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) { + async function testChangesPropertiesAccordingPermission(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) { await token.collection.addAdmin(alice, {Substrate: bob.address}); await token.transfer(alice, {Substrate: charlie.address}, pieces); @@ -597,7 +588,7 @@ await testChangesPropertiesAccordingPermission(token, 100n); }); - async function testDeletePropertiesAccordingPermission(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) { + async function testDeletePropertiesAccordingPermission(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) { await token.collection.addAdmin(alice, {Substrate: bob.address}); await token.transfer(alice, {Substrate: charlie.address}, pieces); @@ -808,7 +799,7 @@ return (await (mode == 'NFT' ? api.query.nonfungible : api.query.refungible).tokenProperties(collectionId, tokenId)).toJSON().consumedSpace; } - async function prepare(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint): Promise { + async function prepare(token: ITokenNonfungible | ITokenRefungible, pieces: bigint): Promise { await token.collection.addAdmin(alice, {Substrate: bob.address}); await token.transfer(alice, {Substrate: charlie.address}, pieces); @@ -832,7 +823,7 @@ return originalSpace; } - async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) { + async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) { const originalSpace = await prepare(token, pieces); let i = 0; @@ -867,7 +858,7 @@ await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n); }); - async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) { + async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) { const originalSpace = await prepare(token, pieces); let i = 0; @@ -902,7 +893,7 @@ await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n); }); - async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) { + async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) { const originalSpace = await prepare(token, pieces); await expect( @@ -938,7 +929,7 @@ await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n); }); - async function testForbidsAddingTooManyProperties(token: UniqueNFTToken | UniqueRFTToken, pieces: bigint) { + async function testForbidsAddingTooManyProperties(token: ITokenNonfungible | ITokenRefungible, pieces: bigint) { const originalSpace = await prepare(token, pieces); await expect( @@ -993,7 +984,7 @@ }); }); - async function prepare(helper: UniqueHelper): Promise { + async function prepare(helper: UniqueHelper): Promise { const collection = await helper.rft.mintCollection(alice); const token = await collection.mintToken(alice, 100n); --- 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.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); // Try to burn await expect(nestedToken.burnFrom(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.ApprovedValueTooLow/); - expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount()); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccountInLowerCase()); }); // todo another test for creating excessive depth matryoshka with Ethereum? --- a/tests/src/util/playgrounds/index.ts +++ b/tests/src/util/playgrounds/index.ts @@ -9,10 +9,16 @@ import '../../interfaces/augment-api-events'; import {DevUniqueHelper, SilentLogger, SilentConsole} from './unique.dev'; - chai.use(chaiAsPromised); export const expect = chai.expect; +export const U128_MAX = (1n << 128n) - 1n; + +const MICROUNIQUE = 1_000_000_000_000n; +const MILLIUNIQUE = 1_000n * MICROUNIQUE; +const CENTIUNIQUE = 10n * MILLIUNIQUE; +export const UNIQUE = 100n * CENTIUNIQUE; + export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise, url: string = config.substrateUrl) => { const silentConsole = new SilentConsole(); silentConsole.enable(); --- a/tests/src/util/playgrounds/types.ts +++ b/tests/src/util/playgrounds/types.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import {IKeyringPair} from '@polkadot/types/types'; +import {UniqueHelper} from './unique'; export interface IEvent { section: string; @@ -106,7 +107,7 @@ } } -export interface IToken { +export interface ITokenAddress { collectionId: number; tokenId: number; } @@ -167,3 +168,169 @@ export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated'; export type TUniqueNetworks = 'opal' | 'quartz' | 'unique'; export type TSigner = IKeyringPair; // | 'string' + +export interface ICollectionBase { + helper: UniqueHelper; + collectionId: number; + getData: () => Promise<{ + id: number; + name: string; + description: string; + tokensCount: number; + admins: ICrossAccountId[]; + normalizedOwner: TSubstrateAccount; + raw: any + } | null>; + getLastTokenId: () => Promise; + isTokenExists: (tokenId: number) => Promise; + getAdmins: () => Promise; + getAllowList: () => Promise; + getEffectiveLimits: () => Promise; + getProperties: (propertyKeys?: string[]) => Promise; + getTokenNextSponsored: (tokenId: number, addressObj: ICrossAccountId) => Promise; + setSponsor: (signer: TSigner, sponsorAddress: TSubstrateAccount) => Promise; + confirmSponsorship: (signer: TSigner) => Promise; + removeSponsor: (signer: TSigner) => Promise; + setLimits: (signer: TSigner, limits: ICollectionLimits) => Promise; + changeOwner: (signer: TSigner, ownerAddress: TSubstrateAccount) => Promise; + addAdmin: (signer: TSigner, adminAddressObj: ICrossAccountId) => Promise; + addToAllowList: (signer: TSigner, addressObj: ICrossAccountId) => Promise; + removeFromAllowList: (signer: TSigner, addressObj: ICrossAccountId) => Promise; + removeAdmin: (signer: TSigner, adminAddressObj: ICrossAccountId) => Promise; + setProperties: (signer: TSigner, properties: IProperty[]) => Promise; + deleteProperties: (signer: TSigner, propertyKeys: string[]) => Promise; + setPermissions: (signer: TSigner, permissions: ICollectionPermissions) => Promise; + enableNesting: (signer: TSigner, permissions: INestingPermissions) => Promise; + disableNesting: (signer: TSigner) => Promise; + burn: (signer: TSigner) => Promise; +} + +export interface ICollectionNFT extends ICollectionBase { + getTokenObject: (tokenId: number) => ITokenNonfungible; // todo:playgrounds + getTokensByAddress: (addressObj: ICrossAccountId) => Promise; + getToken: (tokenId: number, blockHashAt?: string) => Promise<{ + properties: IProperty[]; + owner: ICrossAccountId; + normalizedOwner: ICrossAccountId; + }| null>; + getTokenOwner: (tokenId: number, blockHashAt?: string) => Promise; + getTokenTopmostOwner: (tokenId: number, blockHashAt?: string) => Promise; + getTokenChildren: (tokenId: number, blockHashAt?: string) => Promise; // todo:playgrounds + getPropertyPermissions: (propertyKeys?: string[]) => Promise; + getTokenProperties: (tokenId: number, propertyKeys?: string[]) => Promise; + transferToken: (signer: TSigner, tokenId: number, addressObj: ICrossAccountId) => Promise; + transferTokenFrom: (signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) => Promise; + approveToken: (signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) => Promise; + isTokenApproved: (tokenId: number, toAddressObj: ICrossAccountId) => Promise; + mintToken: (signer: TSigner, owner: ICrossAccountId, properties?: IProperty[]) => Promise;// todo:playgrounds + mintMultipleTokens: (signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]) => Promise;// todo:playgrounds + burnToken: (signer: TSigner, tokenId: number) => Promise<{ + success: boolean, + token: number | null + }>; + burnTokenFrom: (signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) => Promise; + setTokenProperties: (signer: TSigner, tokenId: number, properties: IProperty[]) => Promise; + deleteTokenProperties: (signer: TSigner, tokenId: number, propertyKeys: string[]) => Promise; + setTokenPropertyPermissions: (signer: TSigner, permissions: ITokenPropertyPermission[]) => Promise; + nestToken: (signer: TSigner, tokenId: number, toTokenObj: ITokenAddress) => Promise; + unnestToken: (signer: TSigner, tokenId: number, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) => Promise; +} + +export interface ICollectionRFT extends ICollectionBase { + getTokenObject: (tokenId: number) => ITokenRefungible; + getToken: (tokenId: number, blockHashAt?: string) => Promise<{ + properties: IProperty[]; + owner: ICrossAccountId; + normalizedOwner: ICrossAccountId; + }| null>; + getTokensByAddress: (addressObj: ICrossAccountId) => Promise; + getTop10TokenOwners: (tokenId: number) => Promise; + getTokenBalance: (tokenId: number, addressObj: ICrossAccountId) => Promise; + getTokenTotalPieces: (tokenId: number) => Promise; + getTokenApprovedPieces: (tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) => Promise; + getPropertyPermissions: (propertyKeys?: string[] | null) => Promise; + getTokenProperties: (tokenId: number, propertyKeys?: string[]) => Promise; + transferToken: (signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount: bigint) => Promise; + transferTokenFrom: (signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) => Promise; + approveToken: (signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount: bigint) => Promise; + repartitionToken: (signer: TSigner, tokenId: number, amount: bigint) => Promise; + mintToken: (signer: TSigner, pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]) => Promise; + mintMultipleTokens: (signer: TSigner, tokens: {pieces: bigint, owner: ICrossAccountId, properties?: IProperty[]}[]) => Promise; // todo:playgrounds + burnToken: (signer: TSigner, tokenId: number, amount: bigint) => Promise<{ + success: boolean, + token: number | null + }>; + burnTokenFrom: (signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount: bigint) => Promise; + setTokenProperties: (signer: TSigner, tokenId: number, properties: IProperty[]) => Promise; + deleteTokenProperties: (signer: TSigner, tokenId: number, propertyKeys: string[]) => Promise; + setTokenPropertyPermissions: (signer: TSigner, permissions: ITokenPropertyPermission[]) => Promise; +} + +export interface ICollectionFT extends ICollectionBase { + getBalance: (addressObj: ICrossAccountId) => Promise; + getTotalPieces: () => Promise; + getApprovedTokens: (fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) => Promise; + getTop10Owners: () => Promise; + mint: (signer: TSigner, amount: bigint, owner: ICrossAccountId) => Promise; + mintWithOneOwner: (signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId) => Promise; + transfer: (signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) => Promise; + transferFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) => Promise; + burnTokens: (signer: TSigner, amount: bigint) => Promise; + burnTokensFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint) => Promise; + approveTokens: (signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) => Promise; +} + +export interface ITokenBase extends ITokenAddress { + collection: ICollectionNFT | ICollectionRFT; + getNextSponsored: (addressObj: ICrossAccountId) => Promise; + getProperties: (propertyKeys?: string[]) => Promise; + setProperties: (signer: TSigner, properties: IProperty[]) => Promise; + deleteProperties: (signer: TSigner, propertyKeys: string[]) => Promise; + nestingAccount: () => ICrossAccountId; + nestingAccountInLowerCase: () => ICrossAccountId; +} + +export interface ITokenNonfungible extends ITokenBase { + collection: ICollectionNFT; + getData: (blockHashAt?: string) => Promise<{ + properties: IProperty[]; + owner: ICrossAccountId; + normalizedOwner: ICrossAccountId; + }| null>; + getOwner: (blockHashAt?: string) => Promise; + getTopmostOwner: (blockHashAt?: string) => Promise; + getChildren: (blockHashAt?: string) => Promise; // todo:playgrounds + nest: (signer: TSigner, toTokenObj: ITokenAddress) => Promise; + unnest: (signer: TSigner, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) => Promise; + transfer: (signer: TSigner, addressObj: ICrossAccountId) => Promise; + transferFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) => Promise; + approve: (signer: TSigner, toAddressObj: ICrossAccountId) => Promise; + isApproved: (toAddressObj: ICrossAccountId) => Promise; + burn: (signer: TSigner) => Promise<{ + success: boolean, + token: number | null + }>; + burnFrom: (signer: TSigner, fromAddressObj: ICrossAccountId) => Promise; +} + +export interface ITokenRefungible extends ITokenBase { + collection: ICollectionRFT; + getData: (blockHashAt?: string) => Promise<{ + properties: IProperty[]; + owner: ICrossAccountId; + normalizedOwner: ICrossAccountId; + }| null>; + getTop10Owners: () => Promise; + getBalance: (addressObj: ICrossAccountId) => Promise; + getTotalPieces: () => Promise; + getApprovedPieces: (fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) => Promise; + transfer: (signer: TSigner, addressObj: ICrossAccountId, amount: bigint) => Promise; + transferFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) => Promise; + approve: (signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) => Promise; + repartition: (signer: TSigner, amount: bigint) => Promise; + burn: (signer: TSigner, amount: bigint) => Promise<{ + success: boolean, + token: number | null + }>; + burnFrom: (signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint) => Promise; +} --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -9,7 +9,7 @@ import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types'; import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto'; 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'; +import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISubstrateBalance, ITokenAddress, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks, ICollectionBase, ICollectionNFT, ICollectionRFT, ITokenBase, ITokenNonfungible, ITokenRefungible, ICollectionFT} from './types'; export const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => { const address = {} as ICrossAccountId; @@ -55,11 +55,15 @@ RPC: 'rpc', }; - static getTokenAccount(token: IToken) { + static getTokenAccount(token: ITokenAddress): ICrossAccountId { + return {Ethereum: this.getTokenAddress(token)}; + } + + static getTokenAccountInLowerCase(token: ITokenAddress): ICrossAccountId { return {Ethereum: this.getTokenAddress(token).toLowerCase()}; } - static getTokenAddress(token: IToken) { + static getTokenAddress(token: ITokenAddress): string { return nesting.tokenIdToAddress(token.collectionId, token.tokenId); } @@ -1348,7 +1352,7 @@ * @example getTokenChildren(10, 5); * @returns tokens whose depth of nesting is <= 5 */ - async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise { + async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise { let children; if(typeof blockHashAt === 'undefined') { children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]); @@ -1369,7 +1373,7 @@ * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}); * @returns ```true``` if extrinsic success, otherwise ```false``` */ - async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise { + async nestToken(signer: TSigner, tokenObj: ITokenAddress, rootTokenObj: ITokenAddress): Promise { const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj); const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress); if(!result) { @@ -1387,7 +1391,7 @@ * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."}); * @returns ```true``` if extrinsic success, otherwise ```false``` */ - async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise { + async unnestToken(signer: TSigner, tokenObj: ITokenAddress, rootTokenObj: ITokenAddress, toAddressObj: ICrossAccountId): Promise { const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj); const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj); if(!result) { @@ -1456,7 +1460,7 @@ true, ); const collection = this.getCollectionObject(collectionId); - return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId)); + return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId)); } /** @@ -1489,7 +1493,7 @@ true, ); const collection = this.getCollectionObject(collectionId); - return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId)); + return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId)); } /** @@ -1630,7 +1634,7 @@ true, // `Unable to mint RFT tokens for ${label}`, ); const collection = this.getCollectionObject(collectionId); - return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId)); + return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId)); } /** @@ -1654,7 +1658,7 @@ true, ); const collection = this.getCollectionObject(collectionId); - return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId)); + return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: ITokenAddress) => collection.getTokenObject(x.tokenId)); } /** @@ -2219,7 +2223,7 @@ } -export class UniqueCollectionBase { +class UniqueCollectionBase implements ICollectionBase { helper: UniqueHelper; collectionId: number; @@ -2256,6 +2260,10 @@ return await this.helper.collection.getProperties(this.collectionId, propertyKeys); } + async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) { + return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj); + } + async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) { return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress); } @@ -2300,10 +2308,6 @@ return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys); } - async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) { - return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj); - } - async setPermissions(signer: TSigner, permissions: ICollectionPermissions) { return await this.helper.collection.setPermissions(signer, this.collectionId, permissions); } @@ -2322,7 +2326,7 @@ } -export class UniqueNFTCollection extends UniqueCollectionBase { +class UniqueNFTCollection extends UniqueCollectionBase implements ICollectionNFT { getTokenObject(tokenId: number) { return new UniqueNFTToken(tokenId, this); } @@ -2399,17 +2403,17 @@ return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions); } - async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) { + async nestToken(signer: TSigner, tokenId: number, toTokenObj: ITokenAddress) { return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj); } - async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) { + async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) { return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj); } } -export class UniqueRFTCollection extends UniqueCollectionBase { +class UniqueRFTCollection extends UniqueCollectionBase implements ICollectionRFT { getTokenObject(tokenId: number) { return new UniqueRFTToken(tokenId, this); } @@ -2434,6 +2438,10 @@ return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId); } + async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj); + } + async getPropertyPermissions(propertyKeys: string[] | null = null) { return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys); } @@ -2452,10 +2460,6 @@ async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) { return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount); - } - - async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { - return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj); } async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) { @@ -2492,23 +2496,31 @@ } -export class UniqueFTCollection extends UniqueCollectionBase { - async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) { - return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner); +class UniqueFTCollection extends UniqueCollectionBase implements ICollectionFT { + async getBalance(addressObj: ICrossAccountId) { + return await this.helper.ft.getBalance(this.collectionId, addressObj); } - async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) { - return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner); + async getTotalPieces() { + return await this.helper.ft.getTotalPieces(this.collectionId); } - async getBalance(addressObj: ICrossAccountId) { - return await this.helper.ft.getBalance(this.collectionId, addressObj); + async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj); } async getTop10Owners() { return await this.helper.ft.getTop10Owners(this.collectionId); } + async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) { + return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner); + } + + async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) { + return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner); + } + async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) { return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount); } @@ -2523,23 +2535,15 @@ async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) { return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount); - } - - async getTotalPieces() { - return await this.helper.ft.getTotalPieces(this.collectionId); } async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) { return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount); } - - async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { - return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj); - } } -export class UniqueTokenBase implements IToken { +class UniqueTokenBase implements ITokenBase { collection: UniqueNFTCollection | UniqueRFTCollection; collectionId: number; tokenId: number; @@ -2569,10 +2573,14 @@ nestingAccount() { return this.collection.helper.util.getTokenAccount(this); } + + nestingAccountInLowerCase() { + return this.collection.helper.util.getTokenAccountInLowerCase(this); + } } -export class UniqueNFTToken extends UniqueTokenBase { +class UniqueNFTToken extends UniqueTokenBase implements ITokenNonfungible { collection: UniqueNFTCollection; constructor(tokenId: number, collection: UniqueNFTCollection) { @@ -2596,11 +2604,11 @@ return await this.collection.getTokenChildren(this.tokenId, blockHashAt); } - async nest(signer: TSigner, toTokenObj: IToken) { + async nest(signer: TSigner, toTokenObj: ITokenAddress) { return await this.collection.nestToken(signer, this.tokenId, toTokenObj); } - async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) { + async unnest(signer: TSigner, fromTokenObj: ITokenAddress, toAddressObj: ICrossAccountId) { return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj); } @@ -2629,7 +2637,7 @@ } } -export class UniqueRFTToken extends UniqueTokenBase { +class UniqueRFTToken extends UniqueTokenBase implements ITokenRefungible { collection: UniqueRFTCollection; constructor(tokenId: number, collection: UniqueRFTCollection) { @@ -2653,6 +2661,10 @@ return await this.collection.getTokenTotalPieces(this.tokenId); } + async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) { + return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj); + } + async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) { return await this.collection.transferToken(signer, this.tokenId, addressObj, amount); } @@ -2663,10 +2675,6 @@ async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) { return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount); - } - - async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) { - return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj); } async repartition(signer: TSigner, amount: bigint) {