difftreelog
Merge pull request #479 from UniqueNetwork/feature/playgrounds
in: master
Feature/playgrounds
5 files changed
tests/src/fungible.test.tsdiffbeforeafterboth--- a/tests/src/fungible.test.ts
+++ b/tests/src/fungible.test.ts
@@ -14,25 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-import {default as usingApi} from './substrate/substrate-api';
import {IKeyringPair} from '@polkadot/types/types';
-import {
- getBalance,
- createMultipleItemsExpectSuccess,
- isTokenExists,
- getLastTokenId,
- getAllowance,
- approve,
- transferFrom,
- createCollection,
- transfer,
- burnItem,
- normalizeAccountId,
- CrossAccountId,
- createFungibleItemExpectSuccess,
- U128_MAX,
- burnFromExpectSuccess,
-} from './util/helpers';
+import {U128_MAX} from './util/helpers';
+
+import {usingPlaygrounds} from './util/playgrounds';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
@@ -44,141 +29,137 @@
describe('integration test: Fungible functionality:', () => {
before(async () => {
- await usingApi(async (api, privateKeyWrapper) => {
- alice = privateKeyWrapper('//Alice');
- bob = privateKeyWrapper('//Bob');
+ await usingPlaygrounds(async (helper, privateKey) => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
});
});
it('Create fungible collection and token', async () => {
- await usingApi(async api => {
- const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
- expect(createCollectionResult.success).to.be.true;
- const collectionId = createCollectionResult.collectionId;
- const defaultTokenId = await getLastTokenId(api, collectionId);
- const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
- const aliceBalance = await getBalance(api, collectionId, alice, aliceTokenId);
- const itemCountAfter = await getLastTokenId(api, collectionId);
-
- // What to expect
- // tslint:disable-next-line:no-unused-expression
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'trest'});
+ const defaultTokenId = await collection.getLastTokenId();
+ expect(defaultTokenId).to.be.equal(0);
+
+ await collection.mint(alice, {Substrate: alice.address}, U128_MAX);
+ const aliceBalance = await collection.getBalance({Substrate: alice.address});
+ const itemCountAfter = await collection.getLastTokenId();
+
expect(itemCountAfter).to.be.equal(defaultTokenId);
expect(aliceBalance).to.be.equal(U128_MAX);
});
});
it('RPC method tokenOnewrs for fungible collection and token', async () => {
- await usingApi(async (api, privateKeyWrapper) => {
+ await usingPlaygrounds(async (helper, privateKey) => {
const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
- const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
+ const facelessCrowd = Array(7).fill(0).map((_, i) => ({Substrate: privateKey(`//Alice+${i}`).address}));
+
+ const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+ await collection.mint(alice, {Substrate: alice.address}, U128_MAX);
+
+ await collection.transfer(alice, {Substrate: bob.address}, 1000n);
+ await collection.transfer(alice, ethAcc, 900n);
- const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
- const collectionId = createCollectionResult.collectionId;
- const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
-
- await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
- await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
-
for (let i = 0; i < 7; i++) {
- await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 1);
+ await collection.transfer(alice, facelessCrowd[i], 1n);
}
-
- const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
- const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
- const aliceID = normalizeAccountId(alice);
- const bobId = normalizeAccountId(bob);
+ const owners = await collection.getTop10Owners();
+
// What to expect
- // tslint:disable-next-line:no-unused-expression
- expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
- expect(owners.length == 10).to.be.true;
+ expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]);
+ expect(owners.length).to.be.equal(10);
- const eleven = privateKeyWrapper('11');
- expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
- expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+ const eleven = privateKey('//ALice+11');
+ expect(await collection.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true;
+ expect((await collection.getTop10Owners()).length).to.be.equal(10);
});
});
it('Transfer token', async () => {
- await usingApi(async api => {
+ await usingPlaygrounds(async helper => {
const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
- const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
- const tokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address);
+ const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ await collection.mint(alice, {Substrate: alice.address}, 500n);
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
- expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
- expect(await transfer(api, collectionId, tokenId, alice, ethAcc, 140n)).to.be.true;
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(500n);
+ expect(await collection.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
+ expect(await collection.transfer(alice, ethAcc, 140n)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(300n);
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
- expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(140n);
- await expect(transfer(api, collectionId, tokenId, alice, bob, 350n)).to.eventually.be.rejected;
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(300n);
+ expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(60n);
+ expect(await collection.getBalance(ethAcc)).to.be.equal(140n);
+
+ await expect(collection.transfer(alice, {Substrate: bob.address}, 350n)).to.eventually.be.rejected;
});
});
it('Tokens multiple creation', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
-
- const args = [
- {Fungible: {Value: 500n}},
- {Fungible: {Value: 400n}},
- {Fungible: {Value: 300n}},
- ];
-
- await createMultipleItemsExpectSuccess(alice, collectionId, args);
- expect(await getBalance(api, collectionId, alice, 0)).to.be.equal(1200n);
- });
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+ await collection.mintWithOneOwner(alice, {Substrate: alice.address}, [
+ {value: 500n},
+ {value: 400n},
+ {value: 300n},
+ ]);
+
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1200n);
+ });
});
it('Burn some tokens ', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
- const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
- expect(await burnItem(api, alice, collectionId, tokenId, 499n)).to.be.true;
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n);
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ await collection.mint(alice, {Substrate: alice.address}, 500n);
+
+ expect(await collection.isTokenExists(0)).to.be.true;
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(500n);
+ expect(await collection.burnTokens(alice, 499n)).to.be.true;
+ expect(await collection.isTokenExists(0)).to.be.true;
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);
});
});
it('Burn all tokens ', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
- const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
- expect(await burnItem(api, alice, collectionId, tokenId, 500n)).to.be.true;
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
- expect((await api.rpc.unique.totalPieces(collectionId, tokenId)).value.toBigInt()).to.be.equal(0n);
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ await collection.mint(alice, {Substrate: alice.address}, 500n);
+
+ expect(await collection.isTokenExists(0)).to.be.true;
+ expect(await collection.burnTokens(alice, 500n)).to.be.true;
+ expect(await collection.isTokenExists(0)).to.be.true;
+
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(0n);
+ expect(await collection.getTotalPieces()).to.be.equal(0n);
});
});
it('Set allowance for token', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+ await collection.mint(alice, {Substrate: alice.address}, 100n);
+
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(100n);
- const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 100n}, alice.address));
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+ expect(await collection.approveTokens(alice, {Substrate: bob.address}, 60n)).to.be.true;
+ expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(60n);
+ expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);
+
+ expect(await collection.transferFrom(bob, {Substrate: alice.address}, {Substrate: bob.address}, 20n)).to.be.true;
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(80n);
+ expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(20n);
+ expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(40n);
- expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
- expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n);
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(0n);
-
- expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob, 20n)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n);
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n);
- expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n);
-
- await burnFromExpectSuccess(bob, alice, collectionId, tokenId, 10n);
-
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(70n);
- expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(30n);
- expect(await transferFrom(api, collectionId, tokenId, bob, alice, ethAcc, 10n)).to.be.true;
- expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(10n);
+ await collection.burnTokensFrom(bob, {Substrate: alice.address}, 10n);
+
+ expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(70n);
+ expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(30n);
+ expect(await collection.transferFrom(bob, {Substrate: alice.address}, ethAcc, 10n)).to.be.true;
+ expect(await collection.getBalance(ethAcc)).to.be.equal(10n);
});
});
});
tests/src/refungible.test.tsdiffbeforeafterboth--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -14,28 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-import {default as usingApi, submitTransactionAsync} from './substrate/substrate-api';
import {IKeyringPair} from '@polkadot/types/types';
+
+import { usingPlaygrounds } from './util/playgrounds';
import {
- createCollectionExpectSuccess,
- getBalance,
- createMultipleItemsExpectSuccess,
- isTokenExists,
- getLastTokenId,
- getAllowance,
- approve,
- transferFrom,
- createCollection,
- createRefungibleToken,
- transfer,
- burnItem,
- repartitionRFT,
- createCollectionWithPropsExpectSuccess,
- getDetailedCollectionInfo,
- normalizeAccountId,
- CrossAccountId,
- getCreateItemsResult,
- getDestroyItemsResult,
getModuleNames,
Pallets,
requirePallets,
@@ -49,246 +31,240 @@
let alice: IKeyringPair;
let bob: IKeyringPair;
-
-
describe('integration test: Refungible functionality:', async () => {
before(async function() {
await requirePallets(this, [Pallets.ReFungible]);
- await usingApi(async (api, privateKeyWrapper) => {
- alice = privateKeyWrapper('//Alice');
- bob = privateKeyWrapper('//Bob');
- if (!getModuleNames(api).includes(Pallets.ReFungible)) this.skip();
+ await usingPlaygrounds(async (helper, privateKey) => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ if (!getModuleNames(helper.api!).includes(Pallets.ReFungible)) this.skip();
});
-
});
it('Create refungible collection and token', async () => {
- await usingApi(async api => {
- const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
- expect(createCollectionResult.success).to.be.true;
- const collectionId = createCollectionResult.collectionId;
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+ const itemCountBefore = await collection.getLastTokenId();
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
- const itemCountBefore = await getLastTokenId(api, collectionId);
- const result = await createRefungibleToken(api, alice, collectionId, 100n);
-
- const itemCountAfter = await getLastTokenId(api, collectionId);
+ const itemCountAfter = await collection.getLastTokenId();
// What to expect
- // tslint:disable-next-line:no-unused-expression
- expect(result.success).to.be.true;
+ expect(token?.tokenId).to.be.gte(itemCountBefore);
expect(itemCountAfter).to.be.equal(itemCountBefore + 1);
- expect(collectionId).to.be.equal(result.collectionId);
- expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString());
+ expect(itemCountAfter.toString()).to.be.equal(token?.tokenId.toString());
});
});
it('RPC method tokenOnewrs for refungible collection and token', async () => {
- await usingApi(async (api, privateKeyWrapper) => {
+ await usingPlaygrounds(async (helper, privateKey) => {
const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
- const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
-
- const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
- const collectionId = createCollectionResult.collectionId;
-
- const result = await createRefungibleToken(api, alice, collectionId, 10_000n);
- const aliceTokenId = result.itemId;
-
+ const facelessCrowd = Array(7).fill(0).map((_, i) => ({Substrate: privateKey(`//Alice+${i}`).address}));
+
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 10_000n);
+
+ await token.transfer(alice, {Substrate: bob.address}, 1000n);
+ await token.transfer(alice, ethAcc, 900n);
- await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
- await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
-
for (let i = 0; i < 7; i++) {
- await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 50*(i+1));
+ await token.transfer(alice, facelessCrowd[i], 50n * BigInt(i + 1));
}
-
- const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
- const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
-
- const aliceID = normalizeAccountId(alice);
- const bobId = normalizeAccountId(bob);
-
+
+ const owners = await token.getTop10Owners();
+
// What to expect
- // tslint:disable-next-line:no-unused-expression
- expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
+ expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]);
expect(owners.length).to.be.equal(10);
- const eleven = privateKeyWrapper('11');
- expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
- expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+ const eleven = privateKey('//ALice+11');
+ expect(await token.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true;
+ expect((await token.getTop10Owners()).length).to.be.equal(10);
});
});
it('Transfer token pieces', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
- const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
- expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
-
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(40n);
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
- await expect(transfer(api, collectionId, tokenId, alice, bob, 41n)).to.eventually.be.rejected;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+ expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
+
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n);
+
+ await expect(token.transfer(alice, {Substrate: bob.address}, 41n)).to.eventually.be.rejected;
});
});
it('Create multiple tokens', async () => {
- const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
- const args = [
- {ReFungible: {pieces: 1}},
- {ReFungible: {pieces: 2}},
- {ReFungible: {pieces: 100}},
- ];
- await createMultipleItemsExpectSuccess(alice, collectionId, args);
-
- await usingApi(async api => {
- const tokenId = await getLastTokenId(api, collectionId);
- expect(tokenId).to.be.equal(3);
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ // TODO: fix mintMultipleTokens
+ // await collection.mintMultipleTokens(alice, [
+ // {owner: {Substrate: alice.address}, pieces: 1n},
+ // {owner: {Substrate: alice.address}, pieces: 2n},
+ // {owner: {Substrate: alice.address}, pieces: 100n},
+ // ]);
+ await helper.rft.mintMultipleTokensWithOneOwner(alice, collection.collectionId, {Substrate: alice.address}, [
+ {pieces: 1n},
+ {pieces: 2n},
+ {pieces: 100n},
+ ]);
+ const lastTokenId = await collection.getLastTokenId();
+ expect(lastTokenId).to.be.equal(3);
+ expect(await collection.getTokenBalance(lastTokenId, {Substrate: alice.address})).to.be.equal(100n);
});
});
it('Burn some pieces', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
- const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
- expect(await burnItem(api, alice, collectionId, tokenId, 99n)).to.be.true;
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n);
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+ expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+ expect((await token.burn(alice, 99n)).success).to.be.true;
+ expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(1n);
});
});
it('Burn all pieces', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
- const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
- expect(await burnItem(api, alice, collectionId, tokenId, 100n)).to.be.true;
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.false;
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+
+ expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+
+ expect((await token.burn(alice, 100n)).success).to.be.true;
+ expect(await collection.isTokenExists(token.tokenId)).to.be.false;
});
});
it('Burn some pieces for multiple users', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
- const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+
+ expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+ expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
+
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n);
+
+ expect((await token.burn(alice, 40n)).success).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
- expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
+ expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n);
+
+ expect((await token.burn(bob, 59n)).success).to.be.true;
-
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(40n);
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
- expect(await burnItem(api, alice, collectionId, tokenId, 40n)).to.be.true;
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(1n);
+ expect(await collection.isTokenExists(token.tokenId)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
- expect(await burnItem(api, bob, collectionId, tokenId, 59n)).to.be.true;
+ expect((await token.burn(bob, 1n)).success).to.be.true;
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(1n);
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
- expect(await burnItem(api, bob, collectionId, tokenId, 1n)).to.be.true;
-
- expect(await isTokenExists(api, collectionId, tokenId)).to.be.false;
+ expect(await collection.isTokenExists(token.tokenId)).to.be.false;
});
});
it('Set allowance for token', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
- const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+ expect(await token.approve(alice, {Substrate: bob.address}, 60n)).to.be.true;
+ expect(await token.getApprovedPieces({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(60n);
- expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
- expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n);
-
- expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob, 20n)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n);
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n);
- expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n);
+ expect(await token.transferFrom(bob, {Substrate: alice.address}, {Substrate: bob.address}, 20n)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(80n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(20n);
+ expect(await token.getApprovedPieces({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(40n);
});
});
it('Repartition', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
- const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
- expect(await repartitionRFT(api, collectionId, alice, tokenId, 200n)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(200n);
-
- expect(await transfer(api, collectionId, tokenId, alice, bob, 110n)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(90n);
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(110n);
-
- await expect(repartitionRFT(api, collectionId, alice, tokenId, 80n)).to.eventually.be.rejected;
+ expect(await token.repartition(alice, 200n)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(200n);
+ expect(await token.getTotalPieces()).to.be.equal(200n);
+
+ expect(await token.transfer(alice, {Substrate: bob.address}, 110n)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(90n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(110n);
+
+ await expect(token.repartition(alice, 80n)).to.eventually.be.rejected;
+
+ expect(await token.transfer(alice, {Substrate: bob.address}, 90n)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(200n);
- expect(await transfer(api, collectionId, tokenId, alice, bob, 90n)).to.be.true;
- expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
- expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(200n);
+ expect(await token.repartition(bob, 150n)).to.be.true;
+ await expect(token.transfer(bob, {Substrate: alice.address}, 160n)).to.eventually.be.rejected;
- expect(await repartitionRFT(api, collectionId, bob, tokenId, 150n)).to.be.true;
- await expect(transfer(api, collectionId, tokenId, bob, alice, 160n)).to.eventually.be.rejected;
});
});
it('Repartition with increased amount', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
- const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
-
- const tx = api.tx.unique.repartition(collectionId, tokenId, 200n);
- const events = await submitTransactionAsync(alice, tx);
- const substrateEvents = getCreateItemsResult(events);
- expect(substrateEvents).to.include.deep.members([
- {
- success: true,
- collectionId,
- itemId: tokenId,
- recipient: {Substrate: alice.address},
- amount: 100,
- },
- ]);
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+ await token.repartition(alice, 200n);
+ const chainEvents = helper.chainLog.slice(-1)[0].events.map((x: any) => x.event);
+ expect(chainEvents).to.include.deep.members([{
+ method: 'ItemCreated',
+ section: 'common',
+ index: '0x4202',
+ data: [
+ collection.collectionId.toString(),
+ token.tokenId.toString(),
+ {Substrate: alice.address},
+ '100',
+ ],
+ }]);
});
});
it('Repartition with decreased amount', async () => {
- await usingApi(async api => {
- const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
- const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
-
- const tx = api.tx.unique.repartition(collectionId, tokenId, 50n);
- const events = await submitTransactionAsync(alice, tx);
- const substrateEvents = getDestroyItemsResult(events);
- expect(substrateEvents).to.include.deep.members([
- {
- success: true,
- collectionId,
- itemId: tokenId,
- owner: {Substrate: alice.address},
- amount: 50,
- },
- ]);
+ await usingPlaygrounds(async helper => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+ await token.repartition(alice, 50n);
+ const chainEvents = helper.chainLog.slice(-1)[0].events.map((x: any) => x.event);
+ expect(chainEvents).to.include.deep.members([{
+ method: 'ItemDestroyed',
+ section: 'common',
+ index: '0x4203',
+ data: [
+ collection.collectionId.toString(),
+ token.tokenId.toString(),
+ {Substrate: alice.address},
+ '50',
+ ],
+ }]);
});
});
- it('Сreate new collection with properties', async () => {
- await usingApi(async api => {
+ it('Create new collection with properties', async () => {
+ await usingPlaygrounds(async helper => {
const properties = [{key: 'key1', value: 'val1'}];
- const propertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}];
- const collectionId = await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'ReFungible'},
- properties: properties,
- propPerm: propertyPermissions,
- });
- const collection = (await getDetailedCollectionInfo(api, collectionId))!;
- expect(collection.properties.toHuman()).to.be.deep.equal(properties);
- expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);
+ const tokenPropertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}];
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test', properties, tokenPropertyPermissions});
+ const info = await collection.getData();
+ expect(info?.raw.properties).to.be.deep.equal(properties);
+ expect(info?.raw.tokenPropertyPermissions).to.be.deep.equal(tokenPropertyPermissions);
});
});
});
tests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -16,123 +16,112 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
-import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api';
-import {createCollectionExpectSuccess, destroyCollectionExpectSuccess, getAdminList, normalizeAccountId, queryCollectionExpectSuccess} from './util/helpers';
+import {usingPlaygrounds} from './util/playgrounds';
chai.use(chaiAsPromised);
const expect = chai.expect;
describe('Integration Test removeCollectionAdmin(collection_id, account_id):', () => {
it('Remove collection admin.', async () => {
- await usingApi(async (api, privateKeyWrapper) => {
- const collectionId = await createCollectionExpectSuccess();
- const alice = privateKeyWrapper('//Alice');
- const bob = privateKeyWrapper('//Bob');
- const collection = await queryCollectionExpectSuccess(api, collectionId);
- expect(collection.owner.toString()).to.be.deep.eq(alice.address);
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+ const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const collectionInfo = await collection.getData();
+ expect(collectionInfo?.raw.owner.toString()).to.be.deep.eq(alice.address);
// first - add collection admin Bob
- const addAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
- await submitTransactionAsync(alice, addAdminTx);
+ await collection.addAdmin(alice, {Substrate: bob.address});
- const adminListAfterAddAdmin = await getAdminList(api, collectionId);
- expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
+ const adminListAfterAddAdmin = await collection.getAdmins();
+ expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)});
// then remove bob from admins of collection
- const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
- await submitTransactionAsync(alice, removeAdminTx);
+ await collection.removeAdmin(alice, {Substrate: bob.address});
- const adminListAfterRemoveAdmin = await getAdminList(api, collectionId);
- expect(adminListAfterRemoveAdmin).not.to.be.deep.contains(normalizeAccountId(bob.address));
+ const adminListAfterRemoveAdmin = await collection.getAdmins();
+ expect(adminListAfterRemoveAdmin).not.to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)});
});
});
it('Remove admin from collection that has no admins', async () => {
- await usingApi(async (api, privateKeyWrapper) => {
- const alice = privateKeyWrapper('//Alice');
- const collectionId = await createCollectionExpectSuccess();
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const alice = privateKey('//Alice');
+ const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const adminListBeforeAddAdmin = await getAdminList(api, collectionId);
+ const adminListBeforeAddAdmin = await collection.getAdmins();
expect(adminListBeforeAddAdmin).to.have.lengthOf(0);
- const tx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(alice.address));
- await submitTransactionAsync(alice, tx);
+ // await expect(collection.removeAdmin(alice, {Substrate: alice.address})).to.be.rejectedWith('Unable to remove collection admin');
+ await collection.removeAdmin(alice, {Substrate: alice.address});
});
});
});
describe('Negative Integration Test removeCollectionAdmin(collection_id, account_id):', () => {
it('Can\'t remove collection admin from not existing collection', async () => {
- await usingApi(async (api, privateKeyWrapper) => {
+ await usingPlaygrounds(async (helper, privateKey) => {
// tslint:disable-next-line: no-bitwise
const collectionId = (1 << 32) - 1;
- const alice = privateKeyWrapper('//Alice');
- const bob = privateKeyWrapper('//Bob');
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
- const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
- await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;
+ await expect(helper.collection.removeAdmin(alice, collectionId, {Substrate: bob.address})).to.be.rejected;
// Verifying that nothing bad happened (network is live, new collections can be created, etc.)
- await createCollectionExpectSuccess();
+ await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
});
});
it('Can\'t remove collection admin from deleted collection', async () => {
- await usingApi(async (api, privateKeyWrapper) => {
- // tslint:disable-next-line: no-bitwise
- const collectionId = await createCollectionExpectSuccess();
- const alice = privateKeyWrapper('//Alice');
- const bob = privateKeyWrapper('//Bob');
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+ const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- await destroyCollectionExpectSuccess(collectionId);
+ expect(await collection.burn(alice)).to.be.true;
- const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
- await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;
+ await expect(helper.collection.removeAdmin(alice, collection.collectionId, {Substrate: bob.address})).to.be.rejected;
// Verifying that nothing bad happened (network is live, new collections can be created, etc.)
- await createCollectionExpectSuccess();
+ await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
});
});
it('Regular user can\'t remove collection admin', async () => {
- await usingApi(async (api, privateKeyWrapper) => {
- const collectionId = await createCollectionExpectSuccess();
- const alice = privateKeyWrapper('//Alice');
- const bob = privateKeyWrapper('//Bob');
- const charlie = privateKeyWrapper('//Charlie');
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+ const charlie = privateKey('//Charlie');
+ const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const addAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
- await submitTransactionAsync(alice, addAdminTx);
+ await collection.addAdmin(alice, {Substrate: bob.address});
- const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
- await expect(submitTransactionExpectFailAsync(charlie, changeOwnerTx)).to.be.rejected;
+ await expect(collection.removeAdmin(charlie, {Substrate: bob.address})).to.be.rejected;
// Verifying that nothing bad happened (network is live, new collections can be created, etc.)
- await createCollectionExpectSuccess();
+ await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
});
});
it('Admin can\'t remove collection admin.', async () => {
- await usingApi(async (api, privateKeyWrapper) => {
- const collectionId = await createCollectionExpectSuccess();
- const alice = privateKeyWrapper('//Alice');
- const bob = privateKeyWrapper('//Bob');
- const charlie = privateKeyWrapper('//Charlie');
-
- const addBobAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
- await submitTransactionAsync(alice, addBobAdminTx);
- const addCharlieAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(charlie.address));
- await submitTransactionAsync(alice, addCharlieAdminTx);
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+ const charlie = privateKey('//Charlie');
+ const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+ await collection.addAdmin(alice, {Substrate: bob.address});
+ await collection.addAdmin(alice, {Substrate: charlie.address});
- const adminListAfterAddAdmin = await getAdminList(api, collectionId);
- expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
- expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(charlie.address));
+ const adminListAfterAddAdmin = await collection.getAdmins();
+ expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)});
+ expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(charlie.address)});
- const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
- await expect(submitTransactionExpectFailAsync(charlie, removeAdminTx)).to.be.rejected;
+ await expect(collection.removeAdmin(charlie, {Substrate: bob.address})).to.be.rejected;
- const adminListAfterRemoveAdmin = await getAdminList(api, collectionId);
- expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
- expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(charlie.address));
+ const adminListAfterRemoveAdmin = await collection.getAdmins();
+ expect(adminListAfterRemoveAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)});
+ expect(adminListAfterRemoveAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(charlie.address)});
});
});
});
tests/src/util/playgrounds/index.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/util/playgrounds/index.ts
@@ -0,0 +1,93 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {UniqueHelper} from './unique';
+import config from '../../config';
+import '../../interfaces/augment-api-events';
+import * as defs from '../../interfaces/definitions';
+import {ApiPromise, WsProvider} from '@polkadot/api';
+
+
+class SilentLogger {
+ log(msg: any, level: any): void {}
+ level = {
+ ERROR: 'ERROR' as const,
+ WARNING: 'WARNING' as const,
+ INFO: 'INFO' as const,
+ };
+}
+
+
+class DevUniqueHelper extends UniqueHelper {
+ async connect(wsEndpoint: string, listeners?: any): Promise<void> {
+ const wsProvider = new WsProvider(wsEndpoint);
+ this.api = new ApiPromise({
+ provider: wsProvider,
+ signedExtensions: {
+ ContractHelpers: {
+ extrinsic: {},
+ payload: {},
+ },
+ FakeTransactionFinalizer: {
+ extrinsic: {},
+ payload: {},
+ },
+ },
+ rpc: {
+ unique: defs.unique.rpc,
+ rmrk: defs.rmrk.rpc,
+ eth: {
+ feeHistory: {
+ description: 'Dummy',
+ params: [],
+ type: 'u8',
+ },
+ maxPriorityFeePerGas: {
+ description: 'Dummy',
+ params: [],
+ type: 'u8',
+ },
+ },
+ },
+ });
+ await this.api.isReadyOrError;
+ this.network = await UniqueHelper.detectNetwork(this.api);
+ }
+}
+
+export const usingPlaygrounds = async (code: (helper: UniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
+ // TODO: Remove, this is temporary: Filter unneeded API output
+ // (Jaco promised it will be removed in the next version)
+ const consoleErr = console.error;
+ const consoleLog = console.log;
+ const consoleWarn = console.warn;
+
+ const outFn = (printer: any) => (...args: any[]) => {
+ for (const arg of args) {
+ if (typeof arg !== 'string')
+ continue;
+ if (arg.includes('1000:: Normal connection closure' || arg === 'Normal connection closure'))
+ return;
+ }
+ printer(...args);
+ };
+
+ console.error = outFn(consoleErr.bind(console));
+ console.log = outFn(consoleLog.bind(console));
+ console.warn = outFn(consoleWarn.bind(console));
+ const helper = new DevUniqueHelper(new SilentLogger());
+
+ try {
+ await helper.connect(config.substrateUrl);
+ const ss58Format = helper.chain.getChainProperties().ss58Format;
+ const privateKey = (seed: string) => helper.util.fromSeed(seed, ss58Format);
+ await code(helper, privateKey);
+ }
+ finally {
+ await helper.disconnect();
+ console.error = consoleErr;
+ console.log = consoleLog;
+ console.warn = consoleWarn;
+ }
+};
\ No newline at end of file
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034/* eslint-disable @typescript-eslint/no-var-requires */5/* eslint-disable function-call-argument-newline */6/* eslint-disable no-prototype-builtins */78import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';9import {ApiInterfaceEvents} from '@polkadot/api/types';10import {IKeyringPair} from '@polkadot/types/types';11import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';121314const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {15 const address = {} as ICrossAccountId;16 if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate;17 if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum;18 return address;19};202122const nesting = {23 toChecksumAddress(address: string): string {24 if (typeof address === 'undefined') return '';2526 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);2728 address = address.toLowerCase().replace(/^0x/i,'');29 const addressHash = keccakAsHex(address).replace(/^0x/i,'');30 const checksumAddress = ['0x'];3132 for (let i = 0; i < address.length; i++) {33 // If ith character is 8 to f then make it uppercase34 if (parseInt(addressHash[i], 16) > 7) {35 checksumAddress.push(address[i].toUpperCase());36 } else {37 checksumAddress.push(address[i]);38 }39 }40 return checksumAddress.join('');41 },42 tokenIdToAddress(collectionId: number, tokenId: number) {43 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);44 },45};464748interface IChainEvent {49 data: any;50 method: string;51 section: string;52}5354interface ITransactionResult {55 status: 'Fail' | 'Success';56 result: {57 events: {58 event: IChainEvent59 }[];60 },61 moduleError?: string;62}6364interface ILogger {65 log: (msg: any, level?: string) => void;66 level: {67 ERROR: 'ERROR';68 WARNING: 'WARNING';69 INFO: 'INFO';70 [key: string]: string;71 }72}7374interface IUniqueHelperLog {75 executedAt: number;76 executionTime: number;77 type: 'extrinsic' | 'rpc';78 status: 'Fail' | 'Success';79 call: string;80 params: any[];81 moduleError?: string;82 events?: any;83}8485interface IApiListeners {86 connected?: (...args: any[]) => any;87 disconnected?: (...args: any[]) => any;88 error?: (...args: any[]) => any;89 ready?: (...args: any[]) => any; 90 decorated?: (...args: any[]) => any;91}9293interface ICrossAccountId {94 Substrate?: TSubstrateAccount;95 Ethereum?: TEthereumAccount;96}9798interface ICrossAccountIdLower {99 substrate?: TSubstrateAccount;100 ethereum?: TEthereumAccount;101}102103interface ICollectionLimits {104 accountTokenOwnershipLimit?: number | null;105 sponsoredDataSize?: number | null;106 sponsoredDataRateLimit?: {blocks: number} | {sponsoringDisabled: null} | null;107 tokenLimit?: number | null;108 sponsorTransferTimeout?: number | null;109 sponsorApproveTimeout?: number | null;110 ownerCanTransfer?: boolean | null;111 ownerCanDestroy?: boolean | null;112 transfersEnabled?: boolean | null;113}114115interface INestingPermissions {116 tokenOwner?: boolean;117 collectionAdmin?: boolean;118 restricted?: number[] | null;119}120121interface ICollectionPermissions {122 access?: 'Normal' | 'AllowList';123 mintMode?: boolean;124 nesting?: INestingPermissions;125}126127interface IProperty {128 key: string;129 value: string;130}131132interface ITokenPropertyPermission {133 key: string;134 permission: {135 mutable: boolean;136 tokenOwner: boolean;137 collectionAdmin: boolean;138 }139}140141interface IToken {142 collectionId: number;143 tokenId: number;144}145146interface ICollectionCreationOptions {147 name: string | number[];148 description: string | number[];149 tokenPrefix: string | number[];150 mode?: {151 nft?: null;152 refungible?: null;153 fungible?: number;154 }155 permissions?: ICollectionPermissions;156 properties?: IProperty[];157 tokenPropertyPermissions?: ITokenPropertyPermission[];158 limits?: ICollectionLimits;159 pendingSponsor?: TSubstrateAccount;160}161162interface IChainProperties {163 ss58Format: number;164 tokenDecimals: number[];165 tokenSymbol: string[]166}167168type TSubstrateAccount = string;169type TEthereumAccount = string;170type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';171type TUniqueNetworks = 'opal' | 'quartz' | 'unique';172type TSigner = IKeyringPair; // | 'string'173174class UniqueUtil {175 static transactionStatus = {176 NOT_READY: 'NotReady',177 FAIL: 'Fail',178 SUCCESS: 'Success',179 };180181 static chainLogType = {182 EXTRINSIC: 'extrinsic',183 RPC: 'rpc',184 };185186 static getNestingTokenAddress(collectionId: number, tokenId: number) {187 return nesting.tokenIdToAddress(collectionId, tokenId);188 }189190 static getDefaultLogger(): ILogger {191 return {192 log(msg: any, level = 'INFO') {193 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));194 },195 level: {196 ERROR: 'ERROR',197 WARNING: 'WARNING',198 INFO: 'INFO',199 },200 };201 }202203 static vec2str(arr: string[] | number[]) {204 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');205 }206207 static str2vec(string: string) {208 if (typeof string !== 'string') return string;209 return Array.from(string).map(x => x.charCodeAt(0));210 }211212 static fromSeed(seed: string, ss58Format = 42) {213 const keyring = new Keyring({type: 'sr25519', ss58Format});214 return keyring.addFromUri(seed);215 }216217 static normalizeSubstrateAddress(address: string, ss58Format = 42) {218 return encodeAddress(decodeAddress(address), ss58Format);219 }220221 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') {222 if (creationResult.status !== this.transactionStatus.SUCCESS) {223 throw Error(`Unable to create collection for ${label}`);224 }225226 let collectionId = null;227 creationResult.result.events.forEach(({event: {data, method, section}}) => {228 if ((section === 'common') && (method === 'CollectionCreated')) {229 collectionId = parseInt(data[0].toString(), 10);230 }231 });232233 if (collectionId === null) {234 throw Error(`No CollectionCreated event for ${label}`);235 }236237 return collectionId;238 }239240 static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') {241 if (creationResult.status !== this.transactionStatus.SUCCESS) {242 throw Error(`Unable to create tokens for ${label}`);243 }244 let success = false;245 const tokens = [] as any;246 creationResult.result.events.forEach(({event: {data, method, section}}) => {247 if (method === 'ExtrinsicSuccess') {248 success = true;249 } else if ((section === 'common') && (method === 'ItemCreated')) {250 tokens.push({251 collectionId: parseInt(data[0].toString(), 10),252 tokenId: parseInt(data[1].toString(), 10),253 owner: data[2].toJSON(),254 });255 }256 });257 return {success, tokens};258 }259260 static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') {261 if (burnResult.status !== this.transactionStatus.SUCCESS) {262 throw Error(`Unable to burn tokens for ${label}`);263 }264 let success = false;265 const tokens = [] as any;266 burnResult.result.events.forEach(({event: {data, method, section}}) => {267 if (method === 'ExtrinsicSuccess') {268 success = true;269 } else if ((section === 'common') && (method === 'ItemDestroyed')) {270 tokens.push({271 collectionId: parseInt(data[0].toString(), 10),272 tokenId: parseInt(data[1].toString(), 10),273 owner: data[2].toJSON(),274 });275 }276 });277 return {success, tokens};278 }279280 static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) {281 let eventId = null;282 events.forEach(({event: {data, method, section}}) => {283 if ((section === expectedSection) && (method === expectedMethod)) {284 eventId = parseInt(data[0].toString(), 10);285 }286 });287288 if (eventId === null) {289 throw Error(`No ${expectedMethod} event for ${label}`);290 }291 return eventId === collectionId;292 }293294 static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {295 const normalizeAddress = (address: string | ICrossAccountId) => {296 if(typeof address === 'string') return address;297 const obj = {} as any;298 Object.keys(address).forEach(k => {299 obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];300 });301 if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)};302 if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()};303 return address;304 };305 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;306 events.forEach(({event: {data, method, section}}) => {307 if ((section === 'common') && (method === 'Transfer')) {308 const hData = (data as any).toJSON();309 transfer = {310 collectionId: hData[0],311 tokenId: hData[1],312 from: normalizeAddress(hData[2]),313 to: normalizeAddress(hData[3]),314 amount: BigInt(hData[4]),315 };316 }317 });318 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;319 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);320 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);321 isSuccess = isSuccess && amount === transfer.amount;322 return isSuccess;323 }324}325326327class ChainHelperBase {328 transactionStatus = UniqueUtil.transactionStatus;329 chainLogType = UniqueUtil.chainLogType;330 util: typeof UniqueUtil;331 logger: ILogger;332 api: ApiPromise | null;333 forcedNetwork: TUniqueNetworks | null;334 network: TUniqueNetworks | null;335 chainLog: IUniqueHelperLog[];336337 constructor(logger?: ILogger) {338 this.util = UniqueUtil;339 if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();340 this.logger = logger;341 this.api = null;342 this.forcedNetwork = null;343 this.network = null;344 this.chainLog = [];345 }346347 clearChainLog(): void {348 this.chainLog = [];349 }350351 forceNetwork(value: TUniqueNetworks): void {352 this.forcedNetwork = value;353 }354355 async connect(wsEndpoint: string, listeners?: IApiListeners) {356 if (this.api !== null) throw Error('Already connected');357 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);358 this.api = api;359 this.network = network;360 }361362 async disconnect() {363 if (this.api === null) return;364 await this.api.disconnect();365 this.api = null;366 this.network = null;367 }368369 static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {370 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;371 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;372 return 'opal';373 }374375 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {376 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});377 await api.isReady;378379 const network = await this.detectNetwork(api);380381 await api.disconnect();382383 return network;384 }385386 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{ 387 api: ApiPromise; 388 network: TUniqueNetworks; 389 }> {390 if(typeof network === 'undefined' || network === null) network = 'opal';391 const supportedRPC = {392 opal: {393 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,394 },395 quartz: {396 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,397 },398 unique: {399 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,400 },401 };402 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);403 const rpc = supportedRPC[network];404405 // TODO: investigate how to replace rpc in runtime406 // api._rpcCore.addUserInterfaces(rpc);407408 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});409410 await api.isReadyOrError;411412 if (typeof listeners === 'undefined') listeners = {};413 for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {414 if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;415 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);416 }417418 return {api, network};419 }420421 getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) {422 const {events, status} = data;423 if (status.isReady) {424 return this.transactionStatus.NOT_READY;425 }426 if (status.isBroadcast) {427 return this.transactionStatus.NOT_READY;428 }429 if (status.isInBlock || status.isFinalized) {430 const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed');431 if (errors.length > 0) {432 return this.transactionStatus.FAIL;433 }434 if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {435 return this.transactionStatus.SUCCESS;436 }437 }438439 return this.transactionStatus.FAIL;440 }441442 signTransaction(sender: TSigner, transaction: any, label = 'transaction', options = null) {443 const sign = (callback: any) => {444 if(options !== null) return transaction.signAndSend(sender, options, callback);445 return transaction.signAndSend(sender, callback);446 };447 return new Promise(async (resolve, reject) => {448 try {449 const unsub = await sign((result: any) => {450 const status = this.getTransactionStatus(result);451452 if (status === this.transactionStatus.SUCCESS) {453 this.logger.log(`${label} successful`);454 unsub();455 resolve({result, status});456 } else if (status === this.transactionStatus.FAIL) {457 let moduleError = null;458459 if (result.hasOwnProperty('dispatchError')) {460 const dispatchError = result['dispatchError'];461462 if (dispatchError && dispatchError.isModule) {463 const modErr = dispatchError.asModule;464 const errorMeta = dispatchError.registry.findMetaError(modErr);465466 moduleError = `${errorMeta.section}.${errorMeta.name}`;467 }468 else {469 this.logger.log(result, this.logger.level.ERROR);470 }471 }472473 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);474 unsub();475 reject({status, moduleError, result});476 }477 });478 } catch (e) {479 this.logger.log(e, this.logger.level.ERROR);480 reject(e);481 }482 });483 }484485 constructApiCall(apiCall: string, params: any[]) {486 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);487 let call = this.api as any;488 for(const part of apiCall.slice(4).split('.')) {489 call = call[part];490 }491 return call(...params);492 }493494 async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') {495 if(this.api === null) throw Error('API not initialized');496 if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);497498 const startTime = (new Date()).getTime();499 let result: ITransactionResult;500 let events = [];501 try {502 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult;503 events = result.result.events.map((x: any) => x.toHuman());504 }505 catch(e) {506 if(!(e as object).hasOwnProperty('status')) throw e;507 result = e as ITransactionResult;508 }509510 const endTime = (new Date()).getTime();511512 const log = {513 executedAt: endTime,514 executionTime: endTime - startTime,515 type: this.chainLogType.EXTRINSIC,516 status: result.status,517 call: extrinsic,518 params,519 } as IUniqueHelperLog;520521 if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError;522 if(events.length > 0) log.events = events;523524 this.chainLog.push(log);525526 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage);527 return result;528 }529530 async callRpc(rpc: string, params?: any[]) {531 if(typeof params === 'undefined') params = [];532 if(this.api === null) throw Error('API not initialized');533 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);534535 const startTime = (new Date()).getTime();536 let result;537 let error = null;538 const log = {539 type: this.chainLogType.RPC,540 call: rpc,541 params,542 } as IUniqueHelperLog;543544 try {545 result = await this.constructApiCall(rpc, params);546 }547 catch(e) {548 error = e;549 }550551 const endTime = (new Date()).getTime();552553 log.executedAt = endTime;554 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';555 log.executionTime = endTime - startTime;556557 this.chainLog.push(log);558559 if(error !== null) throw error;560561 return result;562 }563564 getSignerAddress(signer: IKeyringPair | string): string {565 if(typeof signer === 'string') return signer;566 return signer.address;567 }568}569570571class HelperGroup {572 helper: UniqueHelper;573574 constructor(uniqueHelper: UniqueHelper) {575 this.helper = uniqueHelper;576 }577}578579580class CollectionGroup extends HelperGroup {581 /**582 * Get number of blocks when sponsored transaction is available.583 *584 * @param collectionId ID of collection585 * @param tokenId ID of token586 * @param addressObj address for which the sponsorship is checked587 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});588 * @returns number of blocks or null if sponsorship hasn't been set589 */590 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {591 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();592 }593594 /**595 * Get the number of created collections.596 * 597 * @returns number of created collections598 */599 async getTotalCount(): Promise<number> {600 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();601 }602603 /**604 * Get information about the collection with additional data, including the number of tokens it contains, its administrators, the normalized address of the collection's owner, and decoded name and description.605 * 606 * @param collectionId ID of collection607 * @example await getData(2)608 * @returns collection information object609 */610 async getData(collectionId: number): Promise<{611 id: number;612 name: string;613 description: string;614 tokensCount: number;615 admins: ICrossAccountId[];616 normalizedOwner: TSubstrateAccount;617 raw: any618 } | null> {619 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);620 const humanCollection = collection.toHuman(), collectionData = {621 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],622 raw: humanCollection,623 } as any, jsonCollection = collection.toJSON();624 if (humanCollection === null) return null;625 collectionData.raw.limits = jsonCollection.limits;626 collectionData.raw.permissions = jsonCollection.permissions;627 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);628 for (const key of ['name', 'description']) {629 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);630 }631632 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0;633 collectionData.admins = await this.getAdmins(collectionId);634635 return collectionData;636 }637638 /**639 * Get the normalized addresses of the collection's administrators.640 * 641 * @param collectionId ID of collection642 * @example await getAdmins(1)643 * @returns array of administrators644 */645 async getAdmins(collectionId: number): Promise<ICrossAccountId[]> {646 const normalized = [];647 for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) {648 if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)});649 else normalized.push(admin);650 }651 return normalized;652 }653654 /**655 * Get the effective limits of the collection instead of null for default values656 * 657 * @param collectionId ID of collection658 * @example await getEffectiveLimits(2)659 * @returns object of collection limits660 */661 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {662 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();663 }664665 /**666 * Burns the collection if the signer has sufficient permissions and collection is empty.667 * 668 * @param signer keyring of signer669 * @param collectionId ID of collection670 * @param label extra label for log671 * @example await helper.collection.burn(aliceKeyring, 3);672 * @returns ```true``` if extrinsic success, otherwise ```false```673 */674 async burn(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {675 if(typeof label === 'undefined') label = `collection #${collectionId}`;676 const result = await this.helper.executeExtrinsic(677 signer,678 'api.tx.unique.destroyCollection', [collectionId],679 true, `Unable to burn collection for ${label}`,680 );681682 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label);683 }684685 /**686 * Sets the sponsor for the collection (Requires the Substrate address).687 * 688 * @param signer keyring of signer689 * @param collectionId ID of collection690 * @param sponsorAddress Sponsor substrate address691 * @param label extra label for log692 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")693 * @returns ```true``` if extrinsic success, otherwise ```false```694 */695 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise<boolean> {696 if(typeof label === 'undefined') label = `collection #${collectionId}`;697 const result = await this.helper.executeExtrinsic(698 signer,699 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],700 true, `Unable to set collection sponsor for ${label}`,701 );702703 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label);704 }705706 /**707 * Confirms consent to sponsor the collection on behalf of the signer.708 * 709 * @param signer keyring of signer710 * @param collectionId ID of collection711 * @param label extra label for log712 * @example confirmSponsorship(aliceKeyring, 10)713 * @returns ```true``` if extrinsic success, otherwise ```false```714 */715 async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {716 if(typeof label === 'undefined') label = `collection #${collectionId}`;717 const result = await this.helper.executeExtrinsic(718 signer,719 'api.tx.unique.confirmSponsorship', [collectionId],720 true, `Unable to confirm collection sponsorship for ${label}`,721 );722723 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label);724 }725726 /**727 * Sets the limits of the collection. At least one limit must be specified for a correct call.728 * 729 * @param signer keyring of signer730 * @param collectionId ID of collection731 * @param limits collection limits object732 * @param label extra label for log733 * @example734 * await setLimits(735 * aliceKeyring,736 * 10,737 * {738 * sponsorTransferTimeout: 0,739 * ownerCanDestroy: false740 * }741 * )742 * @returns ```true``` if extrinsic success, otherwise ```false```743 */744 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise<boolean> {745 if(typeof label === 'undefined') label = `collection #${collectionId}`;746 const result = await this.helper.executeExtrinsic(747 signer,748 'api.tx.unique.setCollectionLimits', [collectionId, limits],749 true, `Unable to set collection limits for ${label}`,750 );751752 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label);753 }754755 /**756 * Changes the owner of the collection to the new Substrate address.757 * 758 * @param signer keyring of signer759 * @param collectionId ID of collection760 * @param ownerAddress substrate address of new owner761 * @param label extra label for log762 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")763 * @returns ```true``` if extrinsic success, otherwise ```false```764 */765 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise<boolean> {766 if(typeof label === 'undefined') label = `collection #${collectionId}`;767 const result = await this.helper.executeExtrinsic(768 signer,769 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],770 true, `Unable to change collection owner for ${label}`,771 );772773 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label);774 }775776 /**777 * Adds a collection administrator. 778 * 779 * @param signer keyring of signer780 * @param collectionId ID of collection781 * @param adminAddressObj Administrator address (substrate or ethereum)782 * @param label extra label for log783 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})784 * @returns ```true``` if extrinsic success, otherwise ```false```785 */786 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {787 if(typeof label === 'undefined') label = `collection #${collectionId}`;788 const result = await this.helper.executeExtrinsic(789 signer,790 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],791 true, `Unable to add collection admin for ${label}`,792 );793794 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label);795 }796797 /**798 * Removes a collection administrator.799 * 800 * @param signer keyring of signer801 * @param collectionId ID of collection802 * @param adminAddressObj Administrator address (substrate or ethereum)803 * @param label extra label for log804 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})805 * @returns ```true``` if extrinsic success, otherwise ```false```806 */807 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {808 if(typeof label === 'undefined') label = `collection #${collectionId}`;809 const result = await this.helper.executeExtrinsic(810 signer,811 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],812 true, `Unable to remove collection admin for ${label}`,813 );814815 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label);816 }817818 /**819 * Sets onchain permissions for selected collection.820 * 821 * @param signer keyring of signer822 * @param collectionId ID of collection823 * @param permissions collection permissions object824 * @param label extra label for log825 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});826 * @returns ```true``` if extrinsic success, otherwise ```false```827 */828 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise<boolean> {829 if(typeof label === 'undefined') label = `collection #${collectionId}`;830 const result = await this.helper.executeExtrinsic(831 signer,832 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],833 true, `Unable to set collection permissions for ${label}`,834 );835836 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label);837 }838839 /**840 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.841 * 842 * @param signer keyring of signer843 * @param collectionId ID of collection844 * @param permissions nesting permissions object845 * @param label extra label for log846 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});847 * @returns ```true``` if extrinsic success, otherwise ```false```848 */849 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise<boolean> {850 return await this.setPermissions(signer, collectionId, {nesting: permissions}, label);851 }852853 /**854 * Disables nesting for selected collection.855 * 856 * @param signer keyring of signer857 * @param collectionId ID of collection858 * @param label extra label for log859 * @example disableNesting(aliceKeyring, 10);860 * @returns ```true``` if extrinsic success, otherwise ```false```861 */862 async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {863 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label);864 }865866 /**867 * Sets onchain properties to the collection.868 * 869 * @param signer keyring of signer870 * @param collectionId ID of collection871 * @param properties array of property objects872 * @param label extra label for log873 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);874 * @returns ```true``` if extrinsic success, otherwise ```false```875 */876 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {877 if(typeof label === 'undefined') label = `collection #${collectionId}`;878 const result = await this.helper.executeExtrinsic(879 signer,880 'api.tx.unique.setCollectionProperties', [collectionId, properties],881 true, `Unable to set collection properties for ${label}`,882 );883884 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);885 }886887 /**888 * Deletes onchain properties from the collection.889 * 890 * @param signer keyring of signer891 * @param collectionId ID of collection892 * @param propertyKeys array of property keys to delete893 * @param label894 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);895 * @returns ```true``` if extrinsic success, otherwise ```false```896 */897 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {898 if(typeof label === 'undefined') label = `collection #${collectionId}`;899 const result = await this.helper.executeExtrinsic(900 signer,901 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],902 true, `Unable to delete collection properties for ${label}`,903 );904905 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);906 }907908 /**909 * Changes the owner of the token.910 * 911 * @param signer keyring of signer912 * @param collectionId ID of collection913 * @param tokenId ID of token914 * @param addressObj address of a new owner915 * @param amount amount of tokens to be transfered. For NFT must be set to 1n916 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})917 * @returns true if the token success, otherwise false918 */919 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {920 const result = await this.helper.executeExtrinsic(921 signer,922 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],923 true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,924 );925926 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);927 }928929 /**930 * 931 * Change ownership of a token(s) on behalf of the owner. 932 * 933 * @param signer keyring of signer934 * @param collectionId ID of collection935 * @param tokenId ID of token936 * @param fromAddressObj address on behalf of which the token will be sent937 * @param toAddressObj new token owner938 * @param amount amount of tokens to be transfered. For NFT must be set to 1n939 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})940 * @returns true if the token success, otherwise false941 */942 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {943 const result = await this.helper.executeExtrinsic(944 signer,945 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],946 true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,947 );948 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);949 }950951 /**952 * 953 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.954 * 955 * @param signer keyring of signer956 * @param collectionId ID of collection957 * @param tokenId ID of token958 * @param label 959 * @param amount amount of tokens to be burned. For NFT must be set to 1n960 * @example burnToken(aliceKeyring, 10, 5);961 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```962 */963 async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{964 success: boolean,965 token: number | null966 }> {967 if(typeof label === 'undefined') label = `collection #${collectionId}`;968 const burnResult = await this.helper.executeExtrinsic(969 signer,970 'api.tx.unique.burnItem', [collectionId, tokenId, amount],971 true, `Unable to burn token for ${label}`,972 );973 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);974 if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');975 return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};976 }977978 /**979 * Destroys a concrete instance of NFT on behalf of the owner980 * 981 * @param signer keyring of signer982 * @param collectionId ID of collection983 * @param fromAddressObj address on behalf of which the token will be burnt984 * @param tokenId ID of token985 * @param label 986 * @param amount amount of tokens to be burned. For NFT must be set to 1n987 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})988 * @returns ```true``` if extrinsic success, otherwise ```false```989 */990 async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {991 if(typeof label === 'undefined') label = `collection #${collectionId}`;992 const burnResult = await this.helper.executeExtrinsic(993 signer,994 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],995 true, `Unable to burn token from for ${label}`,996 );997 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);998 return burnedTokens.success && burnedTokens.tokens.length > 0;999 }10001001 /**1002 * Set, change, or remove approved address to transfer the ownership of the NFT.1003 * 1004 * @param signer keyring of signer1005 * @param collectionId ID of collection1006 * @param tokenId ID of token1007 * @param toAddressObj 1008 * @param label 1009 * @param amount amount of token to be approved. For NFT must be set to 1n1010 * @returns ```true``` if extrinsic success, otherwise ```false```1011 */1012 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {1013 if(typeof label === 'undefined') label = `collection #${collectionId}`;1014 const approveResult = await this.helper.executeExtrinsic(1015 signer, 1016 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1017 true, `Unable to approve token for ${label}`,1018 );10191020 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);1021 }10221023 /**1024 * Get the amount of token pieces approved to transfer1025 * @param collectionId ID of collection1026 * @param tokenId ID of token1027 * @param toAccountObj 1028 * @param fromAccountObj1029 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1030 * @returns number of approved to transfer pieces1031 */1032 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1033 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1034 }10351036 /**1037 * Get the last created token id1038 * @param collectionId ID of collection1039 * @example getLastTokenId(10);1040 * @returns id of the last created token1041 */1042 async getLastTokenId(collectionId: number): Promise<number> {1043 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1044 }10451046 /**1047 * Check if token exists1048 * @param collectionId ID of collection1049 * @param tokenId ID of token1050 * @example isTokenExists(10, 20);1051 * @returns true if the token exists, otherwise false1052 */1053 async isTokenExists(collectionId: number, tokenId: number): Promise<boolean> {1054 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1055 }1056}10571058class NFTnRFT extends CollectionGroup {1059 /**1060 * Get tokens owned by account1061 * 1062 * @param collectionId ID of collection1063 * @param addressObj tokens owner1064 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1065 * @returns array of token ids owned by account1066 */1067 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1068 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1069 }10701071 /**1072 * Get token data1073 * @param collectionId ID of collection1074 * @param tokenId ID of token1075 * @param blockHashAt 1076 * @param propertyKeys1077 * @example getToken(10, 5);1078 * @returns human readable token data 1079 */1080 async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{1081 properties: IProperty[];1082 owner: ICrossAccountId;1083 normalizedOwner: ICrossAccountId;1084 }| null> {1085 let tokenData;1086 if(typeof blockHashAt === 'undefined') {1087 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1088 }1089 else {1090 if(typeof propertyKeys === 'undefined') {1091 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1092 if(!collection) return null;1093 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1094 }1095 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1096 }1097 tokenData = tokenData.toHuman();1098 if (tokenData === null || tokenData.owner === null) return null;1099 const owner = {} as any;1100 for (const key of Object.keys(tokenData.owner)) {1101 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];1102 }1103 tokenData.normalizedOwner = crossAccountIdFromLower(owner);1104 return tokenData;1105 }11061107 /**1108 * Set permissions to change token properties1109 * @param signer keyring of signer1110 * @param collectionId ID of collection1111 * @param permissions permissions to change a property by the collection owner or admin1112 * @param label 1113 * @example setTokenPropertyPermissions(1114 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1115 * )1116 * @returns true if extrinsic success otherwise false1117 */1118 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {1119 if(typeof label === 'undefined') label = `collection #${collectionId}`;1120 const result = await this.helper.executeExtrinsic(1121 signer,1122 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1123 true, `Unable to set token property permissions for ${label}`,1124 );11251126 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);1127 }11281129 /**1130 * Set token properties1131 * @param signer keyring of signer1132 * @param collectionId ID of collection1133 * @param tokenId ID of token1134 * @param properties 1135 * @param label 1136 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1137 * @returns ```true``` if extrinsic success, otherwise ```false```1138 */1139 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {1140 if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1141 const result = await this.helper.executeExtrinsic(1142 signer,1143 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1144 true, `Unable to set token properties for ${label}`,1145 );11461147 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);1148 }11491150 /**1151 * Delete the provided properties of a token1152 * @param signer keyring of signer1153 * @param collectionId ID of collection1154 * @param tokenId ID of token1155 * @param propertyKeys property keys to be deleted 1156 * @param label 1157 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1158 * @returns ```true``` if extrinsic success, otherwise ```false```1159 */1160 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {1161 if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;1162 const result = await this.helper.executeExtrinsic(1163 signer,1164 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1165 true, `Unable to delete token properties for ${label}`,1166 );11671168 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);1169 }11701171 /**1172 * Mint new collection1173 * @param signer keyring of signer1174 * @param collectionOptions basic collection options and properties 1175 * @param mode NFT or RFT type of a collection1176 * @param errorLabel 1177 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1178 * @returns object of the created collection1179 */1180 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {1181 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1182 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1183 for (const key of ['name', 'description', 'tokenPrefix']) {1184 if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);1185 }1186 const creationResult = await this.helper.executeExtrinsic(1187 signer,1188 'api.tx.unique.createCollectionEx', [collectionOptions],1189 true, errorLabel,1190 );1191 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1192 }11931194 getCollectionObject(collectionId: number): any {1195 return null;1196 }11971198 getTokenObject(collectionId: number, tokenId: number): any {1199 return null;1200 }1201}120212031204class NFTGroup extends NFTnRFT {1205 /**1206 * Get collection object1207 * @param collectionId ID of collection1208 * @example getCollectionObject(2);1209 * @returns instance of UniqueNFTCollection1210 */1211 getCollectionObject(collectionId: number): UniqueNFTCollection {1212 return new UniqueNFTCollection(collectionId, this.helper);1213 }12141215 /**1216 * Get token object1217 * @param collectionId ID of collection1218 * @param tokenId ID of token1219 * @example getTokenObject(10, 5);1220 * @returns instance of UniqueNFTToken1221 */1222 getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {1223 return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));1224 }12251226 /**1227 * Get token's owner1228 * @param collectionId ID of collection1229 * @param tokenId ID of token1230 * @param blockHashAt 1231 * @example getTokenOwner(10, 5);1232 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1233 */1234 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {1235 let owner;1236 if (typeof blockHashAt === 'undefined') {1237 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1238 } else {1239 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1240 }1241 return crossAccountIdFromLower(owner.toJSON());1242 }12431244 /**1245 * Is token approved to transfer1246 * @param collectionId ID of collection1247 * @param tokenId ID of token1248 * @param toAccountObj address to be approved1249 * @returns ```true``` if extrinsic success, otherwise ```false```1250 */1251 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1252 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1253 }12541255 /**1256 * Changes the owner of the token.1257 * 1258 * @param signer keyring of signer1259 * @param collectionId ID of collection1260 * @param tokenId ID of token1261 * @param addressObj address of a new owner1262 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1263 * @returns ```true``` if extrinsic success, otherwise ```false```1264 */1265 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1266 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1267 }12681269 /**1270 * 1271 * Change ownership of a NFT on behalf of the owner. 1272 * 1273 * @param signer keyring of signer1274 * @param collectionId ID of collection1275 * @param tokenId ID of token1276 * @param fromAddressObj address on behalf of which the token will be sent1277 * @param toAddressObj new token owner1278 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1279 * @returns ```true``` if extrinsic success, otherwise ```false```1280 */1281 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1282 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1283 }12841285 /**1286 * Recursively find the address that owns the token1287 * @param collectionId ID of collection1288 * @param tokenId ID of token1289 * @param blockHashAt 1290 * @example getTokenTopmostOwner(10, 5);1291 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1292 */1293 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {1294 let owner;1295 if (typeof blockHashAt === 'undefined') {1296 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1297 } else {1298 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1299 }13001301 if (owner === null) return null;13021303 owner = owner.toHuman();13041305 return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;1306 }13071308 /**1309 * Get tokens nested in the provided token1310 * @param collectionId ID of collection1311 * @param tokenId ID of token1312 * @param blockHashAt 1313 * @example getTokenChildren(10, 5);1314 * @returns tokens whose depth of nesting is <= 5 1315 */1316 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1317 let children;1318 if(typeof blockHashAt === 'undefined') {1319 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1320 } else {1321 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1322 }13231324 return children.toJSON().map((x: any) => {1325 return {collectionId: x.collection, tokenId: x.token};1326 });1327 }13281329 /**1330 * Nest one token into another1331 * @param signer keyring of signer1332 * @param tokenObj token to be nested1333 * @param rootTokenObj token to be parent1334 * @param label 1335 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1336 * @returns ```true``` if extrinsic success, otherwise ```false```1337 */1338 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {1339 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1340 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1341 if(!result) {1342 throw Error(`Unable to nest token for ${label}`);1343 }1344 return result;1345 }13461347 /**1348 * Remove token from nested state1349 * @param signer keyring of signer1350 * @param tokenObj token to unnest1351 * @param rootTokenObj parent of a token1352 * @param toAddressObj address of a new token owner 1353 * @param label 1354 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1355 * @returns ```true``` if extrinsic success, otherwise ```false```1356 */1357 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {1358 const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};1359 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1360 if(!result) {1361 throw Error(`Unable to unnest token for ${label}`);1362 }1363 return result;1364 }13651366 /**1367 * Mint new collection1368 * @param signer keyring of signer1369 * @param collectionOptions Collection options1370 * @param label 1371 * @example 1372 * mintCollection(aliceKeyring, {1373 * name: 'New',1374 * description: 'New collection',1375 * tokenPrefix: 'NEW',1376 * })1377 * @returns object of the created collection1378 */1379 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {1380 return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;1381 }13821383 /**1384 * Mint new token1385 * @param signer keyring of signer1386 * @param data token data1387 * @param label 1388 * @returns created token object1389 */1390 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {1391 if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1392 const creationResult = await this.helper.executeExtrinsic(1393 signer,1394 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1395 nft: {1396 properties: data.properties,1397 },1398 }],1399 true, `Unable to mint NFT token for ${label}`,1400 );1401 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1402 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1403 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1404 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1405 }14061407 /**1408 * Mint multiple NFT tokens1409 * @param signer keyring of signer1410 * @param collectionId ID of collection1411 * @param tokens array of tokens with owner and properties1412 * @param label 1413 * @example 1414 * mintMultipleTokens(aliceKeyring, 10, [{1415 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1416 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1417 * },{1418 * owner: {Ethereum: "0x9F0583DbB855d..."},1419 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1420 * }]);1421 * @returns ```true``` if extrinsic success, otherwise ```false```1422 */1423 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1424 if(typeof label === 'undefined') label = `collection #${collectionId}`;1425 const creationResult = await this.helper.executeExtrinsic(1426 signer,1427 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1428 true, `Unable to mint NFT tokens for ${label}`,1429 );1430 const collection = this.getCollectionObject(collectionId);1431 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1432 }14331434 /**1435 * Mint multiple NFT tokens with one owner1436 * @param signer keyring of signer1437 * @param collectionId ID of collection1438 * @param owner tokens owner1439 * @param tokens array of tokens with owner and properties1440 * @param label 1441 * @example1442 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1443 * properties: [{1444 * key: "gender",1445 * value: "female",1446 * },{1447 * key: "age",1448 * value: "33",1449 * }],1450 * }]);1451 * @returns array of newly created tokens1452 */1453 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {1454 if(typeof label === 'undefined') label = `collection #${collectionId}`;1455 const rawTokens = [];1456 for (const token of tokens) {1457 const raw = {NFT: {properties: token.properties}};1458 rawTokens.push(raw);1459 }1460 const creationResult = await this.helper.executeExtrinsic(1461 signer,1462 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1463 true, `Unable to mint NFT tokens for ${label}`,1464 );1465 const collection = this.getCollectionObject(collectionId);1466 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1467 }14681469 /**1470 * Destroys a concrete instance of NFT.1471 * @param signer keyring of signer1472 * @param collectionId ID of collection1473 * @param tokenId ID of token1474 * @param label 1475 * @example burnToken(aliceKeyring, 10, 5);1476 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1477 */1478 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {1479 return await super.burnToken(signer, collectionId, tokenId, label, 1n);1480 }14811482 /**1483 * Set, change, or remove approved address to transfer the ownership of the NFT.1484 * 1485 * @param signer keyring of signer1486 * @param collectionId ID of collection1487 * @param tokenId ID of token1488 * @param toAddressObj address to approve1489 * @param label 1490 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1491 * @returns ```true``` if extrinsic success, otherwise ```false```1492 */1493 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {1494 return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, 1n);1495 }1496}149714981499class RFTGroup extends NFTnRFT {1500 /**1501 * Get collection object1502 * @param collectionId ID of collection1503 * @example getCollectionObject(2);1504 * @returns instance of UniqueRFTCollection1505 */1506 getCollectionObject(collectionId: number): UniqueRFTCollection {1507 return new UniqueRFTCollection(collectionId, this.helper);1508 }15091510 /**1511 * Get token object1512 * @param collectionId ID of collection1513 * @param tokenId ID of token1514 * @example getTokenObject(10, 5);1515 * @returns instance of UniqueNFTToken1516 */1517 getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {1518 return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));1519 }15201521 /**1522 * Get top 10 token owners with the largest number of pieces 1523 * @param collectionId ID of collection1524 * @param tokenId ID of token1525 * @example getTokenTop10Owners(10, 5);1526 * @returns array of top 10 owners1527 */1528 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {1529 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);1530 }15311532 /**1533 * Get number of pieces owned by address1534 * @param collectionId ID of collection1535 * @param tokenId ID of token1536 * @param addressObj address token owner1537 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1538 * @returns number of pieces ownerd by address1539 */1540 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1541 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1542 }15431544 /**1545 * Transfer pieces of token to another address1546 * @param signer keyring of signer1547 * @param collectionId ID of collection1548 * @param tokenId ID of token1549 * @param addressObj address of a new owner1550 * @param amount number of pieces to be transfered1551 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1552 * @returns ```true``` if extrinsic success, otherwise ```false```1553 */1554 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {1555 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1556 }15571558 /**1559 * Change ownership of some pieces of RFT on behalf of the owner. 1560 * @param signer keyring of signer1561 * @param collectionId ID of collection1562 * @param tokenId ID of token1563 * @param fromAddressObj address on behalf of which the token will be sent1564 * @param toAddressObj new token owner1565 * @param amount number of pieces to be transfered1566 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1567 * @returns ```true``` if extrinsic success, otherwise ```false```1568 */1569 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {1570 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1571 }15721573 /**1574 * Mint new collection1575 * @param signer keyring of signer1576 * @param collectionOptions Collection options1577 * @param label 1578 * @example1579 * mintCollection(aliceKeyring, {1580 * name: 'New',1581 * description: 'New collection',1582 * tokenPrefix: 'NEW',1583 * })1584 * @returns object of the created collection1585 */1586 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {1587 return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;1588 }15891590 /**1591 * Mint new token1592 * @param signer keyring of signer1593 * @param data token data1594 * @param label 1595 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1596 * @returns created token object1597 */1598 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {1599 if(typeof label === 'undefined') label = `collection #${data.collectionId}`;1600 const creationResult = await this.helper.executeExtrinsic(1601 signer,1602 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1603 refungible: {1604 pieces: data.pieces,1605 properties: data.properties,1606 },1607 }],1608 true, `Unable to mint RFT token for ${label}`,1609 );1610 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);1611 if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1612 if (createdTokens.tokens.length < 1) throw Error('No tokens minted');1613 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1614 }16151616 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1617 throw Error('Not implemented');1618 if(typeof label === 'undefined') label = `collection #${collectionId}`;1619 const creationResult = await this.helper.executeExtrinsic(1620 signer,1621 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1622 true, `Unable to mint RFT tokens for ${label}`,1623 );1624 const collection = this.getCollectionObject(collectionId);1625 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1626 }16271628 /**1629 * Mint multiple RFT tokens with one owner1630 * @param signer keyring of signer1631 * @param collectionId ID of collection1632 * @param owner tokens owner1633 * @param tokens array of tokens with properties and pieces1634 * @param label 1635 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);1636 * @returns array of newly created RFT tokens1637 */1638 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {1639 if(typeof label === 'undefined') label = `collection #${collectionId}`;1640 const rawTokens = [];1641 for (const token of tokens) {1642 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};1643 rawTokens.push(raw);1644 }1645 const creationResult = await this.helper.executeExtrinsic(1646 signer,1647 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1648 true, `Unable to mint RFT tokens for ${label}`,1649 );1650 const collection = this.getCollectionObject(collectionId);1651 return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1652 }16531654 /**1655 * Destroys a concrete instance of RFT.1656 * @param signer keyring of signer1657 * @param collectionId ID of collection1658 * @param tokenId ID of token1659 * @param label 1660 * @param amount number of pieces to be burnt1661 * @example burnToken(aliceKeyring, 10, 5);1662 * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```1663 */1664 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {1665 return await super.burnToken(signer, collectionId, tokenId, label, amount);1666 }16671668 /**1669 * Set, change, or remove approved address to transfer the ownership of the RFT.1670 * 1671 * @param signer keyring of signer1672 * @param collectionId ID of collection1673 * @param tokenId ID of token1674 * @param toAddressObj address to approve1675 * @param label 1676 * @param amount number of pieces to be approved1677 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);1678 * @returns true if the token success, otherwise false1679 */1680 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {1681 return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);1682 }16831684 /**1685 * Get total number of pieces1686 * @param collectionId ID of collection1687 * @param tokenId ID of token1688 * @example getTokenTotalPieces(10, 5);1689 * @returns number of pieces1690 */1691 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {1692 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();1693 }16941695 /**1696 * Change number of token pieces. Signer must be the owner of all token pieces.1697 * @param signer keyring of signer1698 * @param collectionId ID of collection1699 * @param tokenId ID of token1700 * @param amount new number of pieces1701 * @param label 1702 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);1703 * @returns true if the repartion was success, otherwise false1704 */1705 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {1706 if(typeof label === 'undefined') label = `collection #${collectionId}`;1707 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);1708 const repartitionResult = await this.helper.executeExtrinsic(1709 signer,1710 'api.tx.unique.repartition', [collectionId, tokenId, amount],1711 true, `Unable to repartition RFT token for ${label}`,1712 );1713 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);1714 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);1715 }1716}171717181719class FTGroup extends CollectionGroup {1720 /**1721 * Get collection object1722 * @param collectionId ID of collection1723 * @example getCollectionObject(2);1724 * @returns instance of UniqueFTCollection1725 */1726 getCollectionObject(collectionId: number): UniqueFTCollection {1727 return new UniqueFTCollection(collectionId, this.helper);1728 }17291730 /**1731 * Mint new fungible collection1732 * @param signer keyring of signer1733 * @param collectionOptions Collection options1734 * @param decimalPoints number of token decimals 1735 * @param errorLabel 1736 * @example1737 * mintCollection(aliceKeyring, {1738 * name: 'New',1739 * description: 'New collection',1740 * tokenPrefix: 'NEW',1741 * }, 18)1742 * @returns newly created fungible collection1743 */1744 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {1745 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1746 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');1747 collectionOptions.mode = {fungible: decimalPoints};1748 for (const key of ['name', 'description', 'tokenPrefix']) {1749 if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);1750 }1751 const creationResult = await this.helper.executeExtrinsic(1752 signer,1753 'api.tx.unique.createCollectionEx', [collectionOptions],1754 true, errorLabel,1755 );1756 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));1757 }17581759 /**1760 * Mint tokens1761 * @param signer keyring of signer1762 * @param collectionId ID of collection1763 * @param owner address owner of new tokens1764 * @param amount amount of tokens to be meanted1765 * @param label 1766 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);1767 * @returns ```true``` if extrinsic success, otherwise ```false``` 1768 */1769 async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {1770 if(typeof label === 'undefined') label = `collection #${collectionId}`;1771 const creationResult = await this.helper.executeExtrinsic(1772 signer,1773 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {1774 fungible: {1775 value: amount,1776 },1777 }],1778 true, `Unable to mint fungible tokens for ${label}`,1779 );1780 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1781 }17821783 /**1784 * Mint multiple Fungible tokens with one owner1785 * @param signer keyring of signer1786 * @param collectionId ID of collection1787 * @param owner tokens owner1788 * @param tokens array of tokens with properties and pieces1789 * @param label 1790 * @returns ```true``` if extrinsic success, otherwise ```false``` 1791 */1792 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {1793 if(typeof label === 'undefined') label = `collection #${collectionId}`;1794 const rawTokens = [];1795 for (const token of tokens) {1796 const raw = {Fungible: {Value: token.value}};1797 rawTokens.push(raw);1798 }1799 const creationResult = await this.helper.executeExtrinsic(1800 signer,1801 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1802 true, `Unable to mint RFT tokens for ${label}`,1803 );1804 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);1805 }18061807 /**1808 * Get the top 10 owners with the largest balance for the Fungible collection 1809 * @param collectionId ID of collection1810 * @example getTop10Owners(10);1811 * @returns array of ```ICrossAccountId```1812 */1813 async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {1814 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);1815 }18161817 /**1818 * Get account balance1819 * @param collectionId ID of collection1820 * @param addressObj address of owner1821 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})1822 * @returns amount of fungible tokens owned by address1823 */1824 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {1825 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();1826 }18271828 /**1829 * Transfer tokens to address1830 * @param signer keyring of signer1831 * @param collectionId ID of collection1832 * @param toAddressObj address recepient1833 * @param amount amount of tokens to be sent1834 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1835 * @returns ```true``` if extrinsic success, otherwise ```false``` 1836 */1837 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {1838 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);1839 }18401841 /**1842 * Transfer some tokens on behalf of the owner.1843 * @param signer keyring of signer1844 * @param collectionId ID of collection1845 * @param fromAddressObj address on behalf of which tokens will be sent1846 * @param toAddressObj address where token to be sent1847 * @param amount number of tokens to be sent1848 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);1849 * @returns ```true``` if extrinsic success, otherwise ```false``` 1850 */1851 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {1852 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);1853 }18541855 /**1856 * Destroy some amount of tokens1857 * @param signer keyring of signer1858 * @param collectionId ID of collection1859 * @param amount amount of tokens to be destroyed1860 * @param label 1861 * @example burnTokens(aliceKeyring, 10, 1000n);1862 * @returns ```true``` if extrinsic success, otherwise ```false``` 1863 */1864 async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {1865 return (await super.burnToken(signer, collectionId, 0, label, amount)).success;1866 }18671868 /**1869 * Burn some tokens on behalf of the owner.1870 * @param signer keyring of signer1871 * @param collectionId ID of collection1872 * @param fromAddressObj address on behalf of which tokens will be burnt1873 * @param amount amount of tokens to be burnt1874 * @param label 1875 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);1876 * @returns ```true``` if extrinsic success, otherwise ```false``` 1877 */1878 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {1879 return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);1880 }18811882 /**1883 * Get total collection supply1884 * @param collectionId 1885 * @returns 1886 */1887 async getTotalPieces(collectionId: number): Promise<bigint> {1888 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();1889 }18901891 /**1892 * Set, change, or remove approved address to transfer tokens.1893 * 1894 * @param signer keyring of signer1895 * @param collectionId ID of collection1896 * @param toAddressObj address to be approved1897 * @param amount amount of tokens to be approved1898 * @param label 1899 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)1900 * @returns ```true``` if extrinsic success, otherwise ```false``` 1901 */1902 async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {1903 return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);1904 }19051906 /**1907 * Get amount of fungible tokens approved to transfer1908 * @param collectionId ID of collection1909 * @param fromAddressObj owner of tokens1910 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner1911 * @returns number of tokens approved for the transfer1912 */1913 async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {1914 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);1915 }1916}191719181919class ChainGroup extends HelperGroup {1920 /**1921 * Get system properties of a chain1922 * @example getChainProperties();1923 * @returns ss58Format, token decimals, and token symbol1924 */1925 getChainProperties(): IChainProperties {1926 const properties = (this.helper.api as any).registry.getChainProperties().toJSON();1927 return {1928 ss58Format: properties.ss58Format.toJSON(),1929 tokenDecimals: properties.tokenDecimals.toJSON(),1930 tokenSymbol: properties.tokenSymbol.toJSON(),1931 };1932 }19331934 /**1935 * Get chain header1936 * @example getLatestBlockNumber();1937 * @returns the number of the last block1938 */1939 async getLatestBlockNumber(): Promise<number> {1940 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();1941 }19421943 /**1944 * Get block hash by block number1945 * @param blockNumber number of block1946 * @example getBlockHashByNumber(12345);1947 * @returns hash of a block1948 */1949 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {1950 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();1951 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;1952 return blockHash;1953 }19541955 /**1956 * Get account nonce1957 * @param address substrate address1958 * @example getNonce("5GrwvaEF5zXb26Fz...");1959 * @returns number, account's nonce1960 */1961 async getNonce(address: TSubstrateAccount): Promise<number> {1962 return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber();1963 }1964}196519661967class BalanceGroup extends HelperGroup {1968 /**1969 * Representation of the native token in the smallest unit1970 * @example getOneTokenNominal()1971 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.1972 */1973 getOneTokenNominal(): bigint {1974 const chainProperties = this.helper.chain.getChainProperties();1975 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);1976 }19771978 /**1979 * Get substrate address balance1980 * @param address substrate address1981 * @example getSubstrate("5GrwvaEF5zXb26Fz...")1982 * @returns amount of tokens on address1983 */1984 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {1985 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();1986 }19871988 /**1989 * Get ethereum address balance1990 * @param address ethereum address1991 * @example getEthereum("0x9F0583DbB855d...")1992 * @returns amount of tokens on address1993 */1994 async getEthereum(address: TEthereumAccount): Promise<bigint> {1995 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();1996 }19971998 /**1999 * Transfer tokens to substrate address2000 * @param signer keyring of signer2001 * @param address substrate address of a recepient2002 * @param amount amount of tokens to be transfered2003 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2004 * @returns ```true``` if extrinsic success, otherwise ```false```2005 */2006 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2007 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`);20082009 let transfer = {from: null, to: null, amount: 0n} as any;2010 result.result.events.forEach(({event: {data, method, section}}) => {2011 if ((section === 'balances') && (method === 'Transfer')) {2012 transfer = {2013 from: this.helper.address.normalizeSubstrate(data[0]),2014 to: this.helper.address.normalizeSubstrate(data[1]),2015 amount: BigInt(data[2]),2016 };2017 }2018 });2019 let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;2020 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;2021 isSuccess = isSuccess && BigInt(amount) === transfer.amount;2022 return isSuccess;2023 }2024}202520262027class AddressGroup extends HelperGroup {2028 /**2029 * Normalizes the address to the specified ss58 format, by default ```42```.2030 * @param address substrate address2031 * @param ss58Format format for address conversion, by default ```42```2032 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2033 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2034 */2035 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2036 return this.helper.util.normalizeSubstrateAddress(address, ss58Format);2037 }20382039 /**2040 * Get address in the connected chain format2041 * @param address substrate address2042 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2043 * @returns address in chain format2044 */2045 async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise<TSubstrateAccount> {2046 const info = this.helper.chain.getChainProperties();2047 return encodeAddress(decodeAddress(address), info.ss58Format);2048 }20492050 /**2051 * Get substrate mirror of an ethereum address2052 * @param ethAddress ethereum address2053 * @param toChainFormat false for normalized account2054 * @example ethToSubstrate('0x9F0583DbB855d...')2055 * @returns substrate mirror of a provided ethereum address2056 */2057 async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise<TSubstrateAccount> {2058 if(!toChainFormat) return evmToAddress(ethAddress);2059 const info = this.helper.chain.getChainProperties();2060 return evmToAddress(ethAddress, info.ss58Format);2061 }20622063 /**2064 * Get ethereum mirror of a substrate address2065 * @param subAddress substrate account2066 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2067 * @returns ethereum mirror of a provided substrate address2068 */2069 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2070 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join(''));2071 }2072}207320742075export class UniqueHelper extends ChainHelperBase {2076 chain: ChainGroup;2077 balance: BalanceGroup;2078 address: AddressGroup;2079 collection: CollectionGroup;2080 nft: NFTGroup;2081 rft: RFTGroup;2082 ft: FTGroup;20832084 constructor(logger?: ILogger) {2085 super(logger);2086 this.chain = new ChainGroup(this);2087 this.balance = new BalanceGroup(this);2088 this.address = new AddressGroup(this);2089 this.collection = new CollectionGroup(this);2090 this.nft = new NFTGroup(this);2091 this.rft = new RFTGroup(this);2092 this.ft = new FTGroup(this);2093 } 2094}209520962097class UniqueCollectionBase {2098 helper: UniqueHelper;2099 collectionId: number;21002101 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2102 this.collectionId = collectionId;2103 this.helper = uniqueHelper;2104 }21052106 async getData() {2107 return await this.helper.collection.getData(this.collectionId);2108 }21092110 async getLastTokenId() {2111 return await this.helper.collection.getLastTokenId(this.collectionId);2112 }21132114 async isTokenExists(tokenId: number) {2115 return await this.helper.collection.isTokenExists(this.collectionId, tokenId);2116 }21172118 async getAdmins() {2119 return await this.helper.collection.getAdmins(this.collectionId);2120 }21212122 async getEffectiveLimits() {2123 return await this.helper.collection.getEffectiveLimits(this.collectionId);2124 }21252126 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) {2127 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label);2128 }21292130 async confirmSponsorship(signer: TSigner, label?: string) {2131 return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label);2132 }21332134 async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) {2135 return await this.helper.collection.setLimits(signer, this.collectionId, limits, label);2136 }21372138 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) {2139 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label);2140 }21412142 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2143 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label);2144 }21452146 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {2147 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label);2148 }21492150 async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2151 return await this.helper.collection.setProperties(signer, this.collectionId, properties, label);2152 }21532154 async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2155 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label);2156 }21572158 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {2159 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);2160 }21612162 async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) {2163 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label);2164 }21652166 async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) {2167 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label);2168 }21692170 async disableNesting(signer: TSigner, label?: string) {2171 return await this.helper.collection.disableNesting(signer, this.collectionId, label);2172 }21732174 async burn(signer: TSigner, label?: string) {2175 return await this.helper.collection.burn(signer, this.collectionId, label);2176 }2177}217821792180class UniqueNFTCollection extends UniqueCollectionBase {2181 getTokenObject(tokenId: number) {2182 return new UniqueNFTToken(tokenId, this);2183 }21842185 async getTokensByAddress(addressObj: ICrossAccountId) {2186 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);2187 }21882189 async getToken(tokenId: number, blockHashAt?: string) {2190 return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt);2191 }21922193 async getTokenOwner(tokenId: number, blockHashAt?: string) {2194 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);2195 }21962197 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {2198 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);2199 }22002201 async getTokenChildren(tokenId: number, blockHashAt?: string) {2202 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);2203 }22042205 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {2206 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);2207 }22082209 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2210 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);2211 }22122213 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {2214 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label);2215 }22162217 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {2218 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);2219 }22202221 async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) {2222 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label);2223 }22242225 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) {2226 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label);2227 }22282229 async burnToken(signer: TSigner, tokenId: number, label?: string) {2230 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label);2231 }22322233 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2234 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2235 }22362237 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2238 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2239 }22402241 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2242 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2243 }22442245 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) {2246 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label);2247 }22482249 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2250 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label);2251 }2252}225322542255class UniqueRFTCollection extends UniqueCollectionBase {2256 getTokenObject(tokenId: number) {2257 return new UniqueRFTToken(tokenId, this);2258 }22592260 async getTokensByAddress(addressObj: ICrossAccountId) {2261 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);2262 }22632264 async getTop10TokenOwners(tokenId: number) {2265 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);2266 }22672268 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {2269 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);2270 }22712272 async getTokenTotalPieces(tokenId: number) {2273 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);2274 }22752276 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) {2277 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);2278 }22792280 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2281 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);2282 }22832284 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2285 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount);2286 }22872288 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2289 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);2290 }22912292 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) {2293 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label);2294 }22952296 async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) {2297 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label);2298 }22992300 async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) {2301 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label);2302 }23032304 async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) {2305 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount);2306 }23072308 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {2309 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);2310 }23112312 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {2313 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);2314 }23152316 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {2317 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);2318 }2319}232023212322class UniqueFTCollection extends UniqueCollectionBase {2323 async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) {2324 return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label);2325 }23262327 async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) {2328 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label);2329 }23302331 async getBalance(addressObj: ICrossAccountId) {2332 return await this.helper.ft.getBalance(this.collectionId, addressObj);2333 }23342335 async getTop10Owners() {2336 return await this.helper.ft.getTop10Owners(this.collectionId);2337 }23382339 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) {2340 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);2341 }23422343 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {2344 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);2345 }23462347 async burnTokens(signer: TSigner, amount: bigint, label?: string) {2348 return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label);2349 }23502351 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) {2352 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label);2353 }23542355 async getTotalPieces() {2356 return await this.helper.ft.getTotalPieces(this.collectionId);2357 }23582359 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2360 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label);2361 }23622363 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2364 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);2365 }2366}236723682369class UniqueTokenBase implements IToken {2370 collection: UniqueNFTCollection | UniqueRFTCollection;2371 collectionId: number;2372 tokenId: number;23732374 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {2375 this.collection = collection;2376 this.collectionId = collection.collectionId;2377 this.tokenId = tokenId;2378 }23792380 async getNextSponsored(addressObj: ICrossAccountId) {2381 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);2382 }23832384 async setProperties(signer: TSigner, properties: IProperty[], label?: string) {2385 return await this.collection.setTokenProperties(signer, this.tokenId, properties, label);2386 }23872388 async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {2389 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label);2390 }2391}239223932394class UniqueNFTToken extends UniqueTokenBase {2395 collection: UniqueNFTCollection;23962397 constructor(tokenId: number, collection: UniqueNFTCollection) {2398 super(tokenId, collection);2399 this.collection = collection;2400 }24012402 async getData(blockHashAt?: string) {2403 return await this.collection.getToken(this.tokenId, blockHashAt);2404 }24052406 async getOwner(blockHashAt?: string) {2407 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);2408 }24092410 async getTopmostOwner(blockHashAt?: string) {2411 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);2412 }24132414 async getChildren(blockHashAt?: string) {2415 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);2416 }24172418 async nest(signer: TSigner, toTokenObj: IToken, label?: string) {2419 return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label);2420 }24212422 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {2423 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label);2424 }24252426 async transfer(signer: TSigner, addressObj: ICrossAccountId) {2427 return await this.collection.transferToken(signer, this.tokenId, addressObj);2428 }24292430 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2431 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);2432 }24332434 async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) {2435 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label);2436 }24372438 async isApproved(toAddressObj: ICrossAccountId) {2439 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);2440 }24412442 async burn(signer: TSigner, label?: string) {2443 return await this.collection.burnToken(signer, this.tokenId, label);2444 }2445}24462447class UniqueRFTToken extends UniqueTokenBase {2448 collection: UniqueRFTCollection;24492450 constructor(tokenId: number, collection: UniqueRFTCollection) {2451 super(tokenId, collection);2452 this.collection = collection;2453 }24542455 async getTop10Owners() {2456 return await this.collection.getTop10TokenOwners(this.tokenId);2457 }24582459 async getBalance(addressObj: ICrossAccountId) {2460 return await this.collection.getTokenBalance(this.tokenId, addressObj);2461 }24622463 async getTotalPieces() {2464 return await this.collection.getTokenTotalPieces(this.tokenId);2465 }24662467 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) {2468 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);2469 }24702471 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {2472 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);2473 }24742475 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {2476 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label);2477 }24782479 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {2480 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);2481 }24822483 async repartition(signer: TSigner, amount: bigint, label?: string) {2484 return await this.collection.repartitionToken(signer, this.tokenId, amount, label);2485 }24862487 async burn(signer: TSigner, amount=100n, label?: string) {2488 return await this.collection.burnToken(signer, this.tokenId, amount, label);2489 }2490}