difftreelog
test add event asserts (#982)
in: master
* add events asserts * fix eslint * eslint fix - unused vars
7 files changed
tests/src/benchmarks/nesting/index.tsdiffbeforeafterboth--- a/tests/src/benchmarks/nesting/index.ts
+++ b/tests/src/benchmarks/nesting/index.ts
@@ -1,17 +1,11 @@
import {EthUniqueHelper, usingEthPlaygrounds} from '../../eth/util';
import {readFile} from 'fs/promises';
-import {
- ICrossAccountId,
-} from '../../util/playgrounds/types';
import {IKeyringPair} from '@polkadot/types/types';
-import {UniqueNFTCollection} from '../../util/playgrounds/unique';
import {Contract} from 'web3-eth-contract';
-import {createObjectCsvWriter} from 'csv-writer';
-import {convertToTokens, createCollectionForBenchmarks, PERMISSIONS, PROPERTIES} from '../utils/common';
+import {convertToTokens} from '../utils/common';
import {makeNames} from '../../util';
import {ContractImports} from '../../eth/util/playgrounds/types';
import {RMRKNestableMintable} from './ABIGEN';
-import {rm} from 'fs';
const {dirname} = makeNames(import.meta.url);
tests/src/eth/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -162,7 +162,7 @@
const malignant = await helper.eth.createAccountWithBalance(donor);
const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner);
- const {collectionId: collectionIdB, contract: contractB} = await createNestingCollection(helper, owner);
+ const {contract: contractB} = await createNestingCollection(helper, owner);
await contractA.methods.setCollectionNesting([true, false, [contractA.options.address, contractB.options.address]]).send({from: owner});
tests/src/fetchMetadata.tsdiffbeforeafterboth--- a/tests/src/fetchMetadata.ts
+++ b/tests/src/fetchMetadata.ts
@@ -1,16 +1,16 @@
-import { writeFile } from "fs/promises";
-import { join } from "path";
-import { exit } from "process";
-import { fileURLToPath } from "url";
+import {writeFile} from 'fs/promises';
+import {join} from 'path';
+import {exit} from 'process';
+import {fileURLToPath} from 'url';
const url = process.env.RPC_URL;
-if (!url) throw new Error('RPC_URL is not set');
+if(!url) throw new Error('RPC_URL is not set');
const srcDir = fileURLToPath(new URL('.', import.meta.url));
-for (let i = 0; i < 10; i++) {
+for(let i = 0; i < 10; i++) {
try {
- console.log(`Trying to fetch metadata, retry ${i + 1}/${10}`)
+ console.log(`Trying to fetch metadata, retry ${i + 1}/${10}`);
const response = await fetch(url, {
method: 'POST',
headers: {
tests/src/governance/technicalCommittee.test.tsdiffbeforeafterboth--- a/tests/src/governance/technicalCommittee.test.ts
+++ b/tests/src/governance/technicalCommittee.test.ts
@@ -36,8 +36,8 @@
});
});
- async function proposalFromAllCommittee(proposal: any) {
- return await usingPlaygrounds(async (helper) => {
+ function proposalFromAllCommittee(proposal: any) {
+ return usingPlaygrounds(async (helper) => {
expect((await helper.callRpc('api.query.technicalCommitteeMembership.members')).toJSON().length).to.be.equal(allTechCommitteeThreshold);
const proposeResult = await helper.technicalCommittee.collective.propose(
techcomms.andy,
@@ -54,7 +54,13 @@
await helper.technicalCommittee.collective.vote(techcomms.constantine, proposalHash, proposalIndex, true);
await helper.technicalCommittee.collective.vote(techcomms.greg, proposalHash, proposalIndex, true);
- return await helper.technicalCommittee.collective.close(techcomms.andy, proposalHash, proposalIndex);
+ const closeResult = await helper.technicalCommittee.collective.close(techcomms.andy, proposalHash, proposalIndex);
+ Event.TechnicalCommittee.Closed.expect(closeResult);
+ Event.TechnicalCommittee.Approved.expect(closeResult);
+ const {result} = Event.TechnicalCommittee.Executed.expect(closeResult);
+ expect(result).to.eq('Ok');
+
+ return closeResult;
});
}
@@ -64,16 +70,16 @@
await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);
- await expect(proposalFromAllCommittee(helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0)))
- .to.be.fulfilled;
+ const fastTrackProposal = await proposalFromAllCommittee(helper.democracy.fastTrackCall(preimageHash, democracyFastTrackVotingPeriod, 0));
+ Event.Democracy.Started.expect(fastTrackProposal);
});
itSub('TechComm can cancel Democracy proposals', async ({helper}) => {
const proposeResult = await helper.getSudo().democracy.propose(sudoer, dummyProposalCall(helper), 0n);
const proposalIndex = Event.Democracy.Proposed.expect(proposeResult).proposalIndex;
- await expect(proposalFromAllCommittee(helper.democracy.cancelProposalCall(proposalIndex)))
- .to.be.fulfilled;
+ const cancelProposal = await proposalFromAllCommittee(helper.democracy.cancelProposalCall(proposalIndex));
+ Event.Democracy.ProposalCanceled.expect(cancelProposal);
});
itSub('TechComm can cancel ongoing Democracy referendums', async ({helper}) => {
@@ -81,19 +87,19 @@
const startedEvent = await helper.wait.expectEvent(democracyLaunchPeriod, Event.Democracy.Started);
const referendumIndex = startedEvent.referendumIndex;
- await expect(proposalFromAllCommittee(helper.democracy.emergencyCancelCall(referendumIndex)))
- .to.be.fulfilled;
+ const emergencyCancelProposal = await proposalFromAllCommittee(helper.democracy.emergencyCancelCall(referendumIndex));
+ Event.Democracy.Cancelled.expect(emergencyCancelProposal);
});
-
itSub('TechComm member can veto Democracy proposals', async ({helper}) => {
const preimageHash = await helper.preimage.notePreimageFromCall(sudoer, dummyProposalCall(helper), true);
await helper.getSudo().democracy.externalProposeDefaultWithPreimage(sudoer, preimageHash);
- await expect(helper.technicalCommittee.collective.execute(
+ const vetoExternalCall = await helper.technicalCommittee.collective.execute(
techcomms.andy,
helper.democracy.vetoExternalCall(preimageHash),
- )).to.be.fulfilled;
+ );
+ Event.Democracy.Vetoed.expect(vetoExternalCall);
});
itSub('TechComm can cancel Fellowship referendums', async ({helper}) => {
@@ -108,7 +114,8 @@
defaultEnactmentMoment,
);
const referendumIndex = Event.FellowshipReferenda.Submitted.expect(submitResult).referendumIndex;
- await expect(proposalFromAllCommittee(helper.fellowship.referenda.cancelCall(referendumIndex))).to.be.fulfilled;
+ const cancelProposal = await proposalFromAllCommittee(helper.fellowship.referenda.cancelCall(referendumIndex));
+ Event.FellowshipReferenda.Cancelled.expect(cancelProposal);
});
itSub.skip('TechComm member can add a Fellowship member', async ({helper}) => {
tests/src/maintenance.seqtest.tsdiffbeforeafterboth--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -18,7 +18,6 @@
import {ApiPromise} from '@polkadot/api';
import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util';
import {itEth} from './eth/util';
-import {UniqueHelper} from './util/playgrounds/unique';
import {main as correctState} from './migrations/correctStateAfterMaintenance';
async function maintenanceEnabled(api: ApiPromise): Promise<boolean> {
tests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -174,6 +174,20 @@
static Passed = this.Method('Passed', data => ({
referendumIndex: eventJsonData<number>(data, 0),
}));
+
+ static ProposalCanceled = this.Method('ProposalCanceled', data => ({
+ propIndex: eventJsonData<number>(data, 0),
+ }));
+
+ static Cancelled = this.Method('Cancelled', data => ({
+ propIndex: eventJsonData<number>(data, 0),
+ }));
+
+ static Vetoed = this.Method('Vetoed', data => ({
+ who: eventHumanData(data, 0),
+ proposalHash: eventHumanData(data, 1),
+ until: eventJsonData<number>(data, 1),
+ }));
};
static Council = class extends EventSection('council') {
@@ -188,6 +202,9 @@
yes: eventJsonData<number>(data, 1),
no: eventJsonData<number>(data, 2),
}));
+ static Executed = this.Method('Executed', data => ({
+ proposalHash: eventHumanData(data, 0),
+ }));
};
static TechnicalCommittee = class extends EventSection('technicalCommittee') {
@@ -202,6 +219,13 @@
yes: eventJsonData<number>(data, 1),
no: eventJsonData<number>(data, 2),
}));
+ static Approved = this.Method('Approved', data => ({
+ proposalHash: eventHumanData(data, 0),
+ }));
+ static Executed = this.Method('Executed', data => ({
+ proposalHash: eventHumanData(data, 0),
+ result: eventHumanData(data, 1),
+ }));
};
static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {
@@ -210,6 +234,11 @@
trackId: eventJsonData<number>(data, 1),
proposal: eventJsonData(data, 2),
}));
+
+ static Cancelled = this.Method('Cancelled', data => ({
+ index: eventJsonData<number>(data, 0),
+ tally: eventJsonData(data, 1),
+ }));
};
static UniqueScheduler = schedulerSection('uniqueScheduler');
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 {SignerOptions} from '@polkadot/api/types/submittable';10import '../../interfaces/augment-api';11import {AugmentedSubmittables} from '@polkadot/api-base/types/submittable';12import {ApiInterfaceEvents} from '@polkadot/api/types';13import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a, blake2AsHex} from '@polkadot/util-crypto';14import {IKeyringPair} from '@polkadot/types/types';15import {hexToU8a} from '@polkadot/util/hex';16import {u8aConcat} from '@polkadot/util/u8a';17import {18 IApiListeners,19 IBlock,20 IEvent,21 IChainProperties,22 ICollectionCreationOptions,23 ICollectionLimits,24 ICollectionPermissions,25 ICrossAccountId,26 ICrossAccountIdLower,27 ILogger,28 INestingPermissions,29 IProperty,30 IStakingInfo,31 ISchedulerOptions,32 ISubstrateBalance,33 IToken,34 ITokenPropertyPermission,35 ITransactionResult,36 IUniqueHelperLog,37 TApiAllowedListeners,38 TEthereumAccount,39 TSigner,40 TSubstrateAccount,41 TNetworks,42 IForeignAssetMetadata,43 AcalaAssetMetadata,44 MoonbeamAssetInfo,45 DemocracyStandardAccountVote,46 IEthCrossAccountId,47 CollectionFlag,48 IPhasicEvent,49 DemocracySplitAccount,50} from './types';51import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';52import type {Vec} from '@polkadot/types-codec';53import {FrameSupportPreimagesBounded, FrameSupportScheduleDispatchTime, FrameSystemEventRecord, PalletBalancesIdAmount, PalletDemocracyConviction, PalletDemocracyVoteAccountVote} from '@polkadot/types/lookup';54import {arrayUnzip} from '@polkadot/util';55import {Event} from './unique.dev';5657export class CrossAccountId {58 Substrate!: TSubstrateAccount;59 Ethereum!: TEthereumAccount;6061 constructor(account: ICrossAccountId) {62 if('Substrate' in account) this.Substrate = account.Substrate;63 else this.Ethereum = account.Ethereum;64 }6566 static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {67 switch (domain) {68 case 'Substrate': return new CrossAccountId({Substrate: account.address});69 case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();70 }71 }7273 static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {74 if('substrate' in address) return new CrossAccountId({Substrate: address.substrate});75 else return new CrossAccountId({Ethereum: address.ethereum});76 }7778 static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {79 return encodeAddress(decodeAddress(address), ss58Format);80 }8182 static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {83 return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});84 }8586 withNormalizedSubstrate(ss58Format = 42): CrossAccountId {87 if(this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);88 return this;89 }9091 static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {92 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));93 }9495 toEthereum(): CrossAccountId {96 if(this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});97 return this;98 }99100 static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {101 return evmToAddress(address, ss58Format);102 }103104 toSubstrate(ss58Format?: number): CrossAccountId {105 if(this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});106 return this;107 }108109 toLowerCase(): CrossAccountId {110 if(this.Substrate) this.Substrate = this.Substrate.toLowerCase();111 if(this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();112 return this;113 }114}115116const nesting = {117 toChecksumAddress(address: string): string {118 if(typeof address === 'undefined') return '';119120 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);121122 address = address.toLowerCase().replace(/^0x/i, '');123 const addressHash = keccakAsHex(address).replace(/^0x/i, '');124 const checksumAddress = ['0x'];125126 for(let i = 0; i < address.length; i++) {127 // If ith character is 8 to f then make it uppercase128 if(parseInt(addressHash[i], 16) > 7) {129 checksumAddress.push(address[i].toUpperCase());130 } else {131 checksumAddress.push(address[i]);132 }133 }134 return checksumAddress.join('');135 },136 tokenIdToAddress(collectionId: number, tokenId: number) {137 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);138 },139};140141class UniqueUtil {142 static transactionStatus = {143 NOT_READY: 'NotReady',144 FAIL: 'Fail',145 SUCCESS: 'Success',146 };147148 static chainLogType = {149 EXTRINSIC: 'extrinsic',150 RPC: 'rpc',151 };152153 static getTokenAccount(token: IToken): CrossAccountId {154 return new CrossAccountId({Ethereum: this.getTokenAddress(token)});155 }156157 static getTokenAddress(token: IToken): string {158 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);159 }160161 static getDefaultLogger(): ILogger {162 return {163 log(msg: any, level = 'INFO') {164 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));165 },166 level: {167 ERROR: 'ERROR',168 WARNING: 'WARNING',169 INFO: 'INFO',170 },171 };172 }173174 static vec2str(arr: string[] | number[]) {175 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');176 }177178 static str2vec(string: string) {179 if(typeof string !== 'string') return string;180 return Array.from(string).map(x => x.charCodeAt(0));181 }182183 static fromSeed(seed: string, ss58Format = 42) {184 const keyring = new Keyring({type: 'sr25519', ss58Format});185 return keyring.addFromUri(seed);186 }187188 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {189 if(creationResult.status !== this.transactionStatus.SUCCESS) {190 throw Error('Unable to create collection!');191 }192193 let collectionId = null;194 creationResult.result.events.forEach(({event: {data, method, section}}) => {195 if((section === 'common') && (method === 'CollectionCreated')) {196 collectionId = parseInt(data[0].toString(), 10);197 }198 });199200 if(collectionId === null) {201 throw Error('No CollectionCreated event was found!');202 }203204 return collectionId;205 }206207 static extractTokensFromCreationResult(creationResult: ITransactionResult): {208 success: boolean,209 tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],210 } {211 if(creationResult.status !== this.transactionStatus.SUCCESS) {212 throw Error('Unable to create tokens!');213 }214 let success = false;215 const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];216 creationResult.result.events.forEach(({event: {data, method, section}}) => {217 if(method === 'ExtrinsicSuccess') {218 success = true;219 } else if((section === 'common') && (method === 'ItemCreated')) {220 tokens.push({221 collectionId: parseInt(data[0].toString(), 10),222 tokenId: parseInt(data[1].toString(), 10),223 owner: data[2].toHuman(),224 amount: data[3].toBigInt(),225 });226 }227 });228 return {success, tokens};229 }230231 static extractTokensFromBurnResult(burnResult: ITransactionResult): {232 success: boolean,233 tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],234 } {235 if(burnResult.status !== this.transactionStatus.SUCCESS) {236 throw Error('Unable to burn tokens!');237 }238 let success = false;239 const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];240 burnResult.result.events.forEach(({event: {data, method, section}}) => {241 if(method === 'ExtrinsicSuccess') {242 success = true;243 } else if((section === 'common') && (method === 'ItemDestroyed')) {244 tokens.push({245 collectionId: parseInt(data[0].toString(), 10),246 tokenId: parseInt(data[1].toString(), 10),247 owner: data[2].toHuman(),248 amount: data[3].toBigInt(),249 });250 }251 });252 return {success, tokens};253 }254255 static findCollectionInEvents(events: { event: IEvent }[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {256 let eventId = null;257 events.forEach(({event: {data, method, section}}) => {258 if((section === expectedSection) && (method === expectedMethod)) {259 eventId = parseInt(data[0].toString(), 10);260 }261 });262263 if(eventId === null) {264 throw Error(`No ${expectedMethod} event was found!`);265 }266 return eventId === collectionId;267 }268269 static isTokenTransferSuccess(events: { event: IEvent }[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {270 const normalizeAddress = (address: string | ICrossAccountId) => {271 if(typeof address === 'string') return address;272 const obj = {} as any;273 Object.keys(address).forEach(k => {274 obj[k.toLocaleLowerCase()] = (address as any)[k];275 });276 if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);277 if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();278 return address;279 };280 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;281 events.forEach(({event: {data, method, section}}) => {282 if((section === 'common') && (method === 'Transfer')) {283 const hData = (data as any).toJSON();284 transfer = {285 collectionId: hData[0],286 tokenId: hData[1],287 from: normalizeAddress(hData[2]),288 to: normalizeAddress(hData[3]),289 amount: BigInt(hData[4]),290 };291 }292 });293 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;294 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);295 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);296 isSuccess = isSuccess && amount === transfer.amount;297 return isSuccess;298 }299300 static bigIntToDecimals(number: bigint, decimals = 18) {301 const numberStr = number.toString();302 const dotPos = numberStr.length - decimals;303304 if(dotPos <= 0) {305 return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;306 } else {307 const intPart = numberStr.substring(0, dotPos);308 const fractPart = numberStr.substring(dotPos);309 return intPart + '.' + fractPart;310 }311 }312}313314class UniqueEventHelper {315 private static extractIndex(index: any): [number, number] | string {316 if(index.toRawType() === '[u8;2]') return [index[0], index[1]];317 return index.toJSON();318 }319320 private static extractSub(data: any, subTypes: any): { [key: string]: any } {321 let obj: any = {};322 let index = 0;323324 if(data.entries) {325 for(const [key, value] of data.entries()) {326 obj[key] = this.extractData(value, subTypes[index]);327 index++;328 }329 } else obj = data.toJSON();330331 return obj;332 }333334 private static toHuman(data: any) {335 return data && data.toHuman ? data.toHuman() : `${data}`;336 }337338 private static extractData(data: any, type: any): any {339 if(!type) return this.toHuman(data);340 if(['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();341 if(['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();342 if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);343 return this.toHuman(data);344 }345346 public static extractEvents(events: { event: any, phase: any }[]): IEvent[] {347 const parsedEvents: IEvent[] = [];348349 events.forEach((record) => {350 const {event, phase} = record;351 const types = event.typeDef;352353 const eventData: IEvent = {354 section: event.section.toString(),355 method: event.method.toString(),356 index: this.extractIndex(event.index),357 data: [],358 phase: phase.toJSON(),359 };360361 event.data.forEach((val: any, index: number) => {362 eventData.data.push(this.extractData(val, types[index]));363 });364365 parsedEvents.push(eventData);366 });367368 return parsedEvents;369 }370}371const InvalidTypeSymbol = Symbol('Invalid type');372// eslint-disable-next-line @typescript-eslint/no-unused-vars373export type Invalid<ErrorMessage> =374 | ((375 invalidType: typeof InvalidTypeSymbol,376 ..._: typeof InvalidTypeSymbol[]377 ) => typeof InvalidTypeSymbol)378 | null379 | undefined;380// Has slightly better error messages than Get381type Get2<T, P extends string, E> =382 P extends `${infer Key}.${infer Key2}` ? Key extends keyof T ? Key2 extends keyof T[Key] ? T[Key][Key2] : E : E : E;383type ForceFunction<T> = T extends (...args: any) => any ? T : (...args: any) => Invalid<'not a function'>;384385export class ChainHelperBase {386 helperBase: any;387388 transactionStatus = UniqueUtil.transactionStatus;389 chainLogType = UniqueUtil.chainLogType;390 util: typeof UniqueUtil;391 eventHelper: typeof UniqueEventHelper;392 logger: ILogger;393 api: ApiPromise | null;394 forcedNetwork: TNetworks | null;395 network: TNetworks | null;396 wsEndpoint: string | null;397 chainLog: IUniqueHelperLog[];398 children: ChainHelperBase[];399 address: AddressGroup;400 chain: ChainGroup;401402 constructor(logger?: ILogger, helperBase?: any) {403 this.helperBase = helperBase;404405 this.util = UniqueUtil;406 this.eventHelper = UniqueEventHelper;407 if(typeof logger == 'undefined') logger = this.util.getDefaultLogger();408 this.logger = logger;409 this.api = null;410 this.forcedNetwork = null;411 this.network = null;412 this.wsEndpoint = null;413 this.chainLog = [];414 this.children = [];415 this.address = new AddressGroup(this);416 this.chain = new ChainGroup(this);417 }418419 clone(helperCls: ChainHelperBaseConstructor, options: { [key: string]: any } = {}) {420 Object.setPrototypeOf(helperCls.prototype, this);421 const newHelper = new helperCls(this.logger, options);422423 newHelper.api = this.api;424 newHelper.network = this.network;425 newHelper.forceNetwork = this.forceNetwork;426427 this.children.push(newHelper);428429 return newHelper;430 }431432 getEndpoint(): string {433 if(this.wsEndpoint === null) throw Error('No connection was established');434 return this.wsEndpoint;435 }436437 getApi(): ApiPromise {438 if(this.api === null) throw Error('API not initialized');439 return this.api;440 }441442 async subscribeEvents(expectedEvents: { section: string, names: string[] }[]) {443 const collectedEvents: IEvent[] = [];444 const unsubscribe = await this.getApi().query.system.events((events: Vec<FrameSystemEventRecord>) => {445 const ievents = this.eventHelper.extractEvents(events);446 ievents.forEach((event) => {447 expectedEvents.forEach((e => {448 if(event.section === e.section && e.names.includes(event.method)) {449 collectedEvents.push(event);450 }451 }));452 });453 });454 return {unsubscribe: unsubscribe as any, collectedEvents};455 }456457 clearChainLog(): void {458 this.chainLog = [];459 }460461 forceNetwork(value: TNetworks): void {462 this.forcedNetwork = value;463 }464465 async connect(wsEndpoint: string, listeners?: IApiListeners) {466 if(this.api !== null) throw Error('Already connected');467 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);468 this.wsEndpoint = wsEndpoint;469 this.api = api;470 this.network = network;471 }472473 async disconnect() {474 for(const child of this.children) {475 child.clearApi();476 }477478 if(this.api === null) return;479 await this.api.disconnect();480 this.clearApi();481 }482483 clearApi() {484 this.api = null;485 this.network = null;486 }487488 static async detectNetwork(api: ApiPromise): Promise<TNetworks> {489 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;490 const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];491492 if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;493494 if(['quartz', 'unique', 'sapphire'].indexOf(spec.specName) > -1) return spec.specName;495 return 'opal';496 }497498 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {499 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});500 await api.isReady;501502 const network = await this.detectNetwork(api);503504 await api.disconnect();505506 return network;507 }508509 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{510 api: ApiPromise;511 network: TNetworks;512 }> {513 if(typeof network === 'undefined' || network === null) network = 'opal';514 const supportedRPC = {515 opal: {516 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,517 },518 quartz: {519 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,520 },521 unique: {522 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,523 },524 rococo: {},525 westend: {},526 moonbeam: {},527 moonriver: {},528 acala: {},529 karura: {},530 westmint: {},531 };532 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);533 const rpc = supportedRPC[network];534535 // TODO: investigate how to replace rpc in runtime536 // api._rpcCore.addUserInterfaces(rpc);537538 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});539540 await api.isReadyOrError;541542 if(typeof listeners === 'undefined') listeners = {};543 for(const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {544 if(!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;545 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);546 }547548 return {api, network};549 }550551 getTransactionStatus(data: { events: { event: IEvent }[], status: any }) {552 const {events, status} = data;553 if(status.isReady) {554 return this.transactionStatus.NOT_READY;555 }556 if(status.isBroadcast) {557 return this.transactionStatus.NOT_READY;558 }559 if(status.isInBlock || status.isFinalized) {560 const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');561 if(errors.length > 0) {562 return this.transactionStatus.FAIL;563 }564 if(events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {565 return this.transactionStatus.SUCCESS;566 }567 }568569 return this.transactionStatus.FAIL;570 }571572 signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {573 const sign = (callback: any) => {574 if(options !== null) return transaction.signAndSend(sender, options, callback);575 return transaction.signAndSend(sender, callback);576 };577 // eslint-disable-next-line no-async-promise-executor578 return new Promise(async (resolve, reject) => {579 try {580 const unsub = await sign((result: any) => {581 const status = this.getTransactionStatus(result);582583 if(status === this.transactionStatus.SUCCESS) {584 this.logger.log(`${label} successful`);585 unsub();586 resolve({result, status, blockHash: result.status.asInBlock.toHuman()});587 } else if(status === this.transactionStatus.FAIL) {588 let moduleError = null;589590 if(result.hasOwnProperty('dispatchError')) {591 const dispatchError = result['dispatchError'];592593 if(dispatchError) {594 if(dispatchError.isModule) {595 const modErr = dispatchError.asModule;596 const errorMeta = dispatchError.registry.findMetaError(modErr);597598 moduleError = `${errorMeta.section}.${errorMeta.name}`;599 } else if(dispatchError.isToken) {600 moduleError = `Token: ${dispatchError.asToken}`;601 } else {602 // May be [object Object] in case of unhandled non-unit enum603 moduleError = `Misc: ${dispatchError.toHuman()}`;604 }605 } else {606 this.logger.log(result, this.logger.level.ERROR);607 }608 }609610 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);611 unsub();612 reject({status, moduleError, result});613 }614 });615 } catch (e) {616 this.logger.log(e, this.logger.level.ERROR);617 reject(e);618 }619 });620 }621622 async signTransactionWithoutSending(signer: TSigner, tx: any) {623 const api = this.getApi();624 const signingInfo = await api.derive.tx.signingInfo(signer.address);625626 tx.sign(signer, {627 blockHash: api.genesisHash,628 genesisHash: api.genesisHash,629 runtimeVersion: api.runtimeVersion,630 nonce: signingInfo.nonce,631 });632633 return tx.toHex();634 }635636 async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {637 const api = this.getApi();638 const signingInfo = await api.derive.tx.signingInfo(signer.address);639640 // We need to sign the tx because641 // unsigned transactions does not have an inclusion fee642 tx.sign(signer, {643 blockHash: api.genesisHash,644 genesisHash: api.genesisHash,645 runtimeVersion: api.runtimeVersion,646 nonce: signingInfo.nonce,647 });648649 if(len === null) {650 return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;651 } else {652 return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;653 }654 }655656 constructApiCall(apiCall: string, params: any[]) {657 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);658 let call = this.getApi() as any;659 for(const part of apiCall.slice(4).split('.')) {660 call = call[part];661 if(!call) {662 const advice = part.includes('_') ? ' Looks like it needs to be converted to camel case.' : '';663 throw Error(`Function ${part} of api call ${apiCall} not found.${advice}`);664 }665 }666 return call(...params);667 }668669 encodeApiCall(apiCall: string, params: any[]) {670 return this.constructApiCall(apiCall, params).method.toHex();671 }672673 async executeExtrinsic<674 E extends string,675 V extends (676 ...args: any) => any = ForceFunction<677 Get2<678 AugmentedSubmittables<'promise'>,679 E, (...args: any) => Invalid<'not found'>680 >681 >682 >(683 sender: TSigner,684 extrinsic: `api.tx.${E}`,685 params: Parameters<V>,686 expectSuccess = true,687 options: Partial<SignerOptions> | null = null,/*, failureMessage='expected success'*/688 ): Promise<ITransactionResult> {689 if(this.api === null) throw Error('API not initialized');690691 const startTime = (new Date()).getTime();692 let result: ITransactionResult;693 let events: IEvent[] = [];694 try {695 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;696 events = this.eventHelper.extractEvents(result.result.events);697 const errorEvent = events.find((event) => event.method == 'ExecutedFailed' || event.method == 'CreatedFailed');698 if(errorEvent)699 throw Error(errorEvent.method + ': ' + extrinsic);700 }701 catch (e) {702 if(!(e as object).hasOwnProperty('status')) throw e;703 result = e as ITransactionResult;704 }705706 const endTime = (new Date()).getTime();707708 const log = {709 executedAt: endTime,710 executionTime: endTime - startTime,711 type: this.chainLogType.EXTRINSIC,712 status: result.status,713 call: extrinsic,714 signer: this.getSignerAddress(sender),715 params,716 } as IUniqueHelperLog;717718 let errorMessage = '';719720 if(result.status !== this.transactionStatus.SUCCESS) {721 if(result.moduleError) {722 errorMessage = typeof result.moduleError === 'string'723 ? result.moduleError724 : `${Object.keys(result.moduleError)[0]}: ${Object.values(result.moduleError)[0]}`;725 log.moduleError = errorMessage;726 }727 else if(result.result.dispatchError) log.dispatchError = result.result.dispatchError;728 }729 if(events.length > 0) log.events = events;730731 this.chainLog.push(log);732733 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {734 if(result.moduleError) throw Error(`${errorMessage}`);735 else if(result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));736 }737 return result as any;738 }739740 async callRpc741 // TODO: make it strongly typed, or use api.query/api.rpc directly742 // <743 // K extends 'rpc' | 'query',744 // E extends string,745 // V extends (...args: any) => any = ForceFunction<746 // Get2<747 // K extends 'rpc' ? DecoratedRpc<'promise', RpcInterface> : QueryableStorage<'promise'>,748 // E, (...args: any) => Invalid<'not found'>749 // >750 // >,751 // P = Parameters<V>,752 // >753 (rpc: string, params?: any[]): Promise<any> {754755 if(typeof params === 'undefined') params = [] as any;756 if(this.api === null) throw Error('API not initialized');757 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);758759 const startTime = (new Date()).getTime();760 let result;761 let error = null;762 const log = {763 type: this.chainLogType.RPC,764 call: rpc,765 params,766 } as any as IUniqueHelperLog;767768 try {769 result = await this.constructApiCall(rpc, params as any);770 }771 catch (e) {772 error = e;773 }774775 const endTime = (new Date()).getTime();776777 log.executedAt = endTime;778 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';779 log.executionTime = endTime - startTime;780781 this.chainLog.push(log);782783 if(error !== null) throw error;784785 return result;786 }787788 getSignerAddress(signer: IKeyringPair | string): string {789 if(typeof signer === 'string') return signer;790 return signer.address;791 }792793 fetchAllPalletNames(): string[] {794 if(this.api === null) throw Error('API not initialized');795 return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()).sort();796 }797798 fetchMissingPalletNames(requiredPallets: readonly string[]): string[] {799 const palletNames = this.fetchAllPalletNames();800 return requiredPallets.filter(p => !palletNames.includes(p));801 }802}803804805class HelperGroup<T extends ChainHelperBase> {806 helper: T;807808 constructor(uniqueHelper: T) {809 this.helper = uniqueHelper;810 }811}812813814class CollectionGroup extends HelperGroup<UniqueHelper> {815 /**816 * Get number of blocks when sponsored transaction is available.817 *818 * @param collectionId ID of collection819 * @param tokenId ID of token820 * @param addressObj address for which the sponsorship is checked821 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});822 * @returns number of blocks or null if sponsorship hasn't been set823 */824 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {825 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();826 }827828 /**829 * Get the number of created collections.830 *831 * @returns number of created collections832 */833 async getTotalCount(): Promise<number> {834 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();835 }836837 /**838 * Get information about the collection with additional data,839 * including the number of tokens it contains, its administrators,840 * the normalized address of the collection's owner, and decoded name and description.841 *842 * @param collectionId ID of collection843 * @example await getData(2)844 * @returns collection information object845 */846 async getData(collectionId: number): Promise<{847 id: number;848 name: string;849 description: string;850 tokensCount: number;851 admins: CrossAccountId[];852 normalizedOwner: TSubstrateAccount;853 raw: any854 } | null> {855 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);856 const humanCollection = collection.toHuman(), collectionData = {857 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],858 raw: humanCollection,859 } as any, jsonCollection = collection.toJSON();860 if(humanCollection === null) return null;861 collectionData.raw.limits = jsonCollection.limits;862 collectionData.raw.permissions = jsonCollection.permissions;863 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);864 for(const key of ['name', 'description']) {865 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);866 }867868 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))869 ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)870 : 0;871 collectionData.admins = await this.getAdmins(collectionId);872873 return collectionData;874 }875876 /**877 * Get the addresses of the collection's administrators, optionally normalized.878 *879 * @param collectionId ID of collection880 * @param normalize whether to normalize the addresses to the default ss58 format881 * @example await getAdmins(1)882 * @returns array of administrators883 */884 async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {885 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();886887 return normalize888 ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())889 : admins;890 }891892 /**893 * Get the addresses added to the collection allow-list, optionally normalized.894 * @param collectionId ID of collection895 * @param normalize whether to normalize the addresses to the default ss58 format896 * @example await getAllowList(1)897 * @returns array of allow-listed addresses898 */899 async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {900 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();901 return normalize902 ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())903 : allowListed;904 }905906 /**907 * Get the effective limits of the collection instead of null for default values908 *909 * @param collectionId ID of collection910 * @example await getEffectiveLimits(2)911 * @returns object of collection limits912 */913 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {914 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();915 }916917 /**918 * Burns the collection if the signer has sufficient permissions and collection is empty.919 *920 * @param signer keyring of signer921 * @param collectionId ID of collection922 * @example await helper.collection.burn(aliceKeyring, 3);923 * @returns ```true``` if extrinsic success, otherwise ```false```924 */925 async burn(signer: TSigner, collectionId: number): Promise<boolean> {926 const result = await this.helper.executeExtrinsic(927 signer,928 'api.tx.unique.destroyCollection', [collectionId],929 true,930 );931932 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');933 }934935 /**936 * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.937 *938 * @param signer keyring of signer939 * @param collectionId ID of collection940 * @param sponsorAddress Sponsor substrate address941 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")942 * @returns ```true``` if extrinsic success, otherwise ```false```943 */944 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {945 const result = await this.helper.executeExtrinsic(946 signer,947 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],948 true,949 );950951 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorSet');952 }953954 /**955 * Confirms consent to sponsor the collection on behalf of the signer.956 *957 * @param signer keyring of signer958 * @param collectionId ID of collection959 * @example confirmSponsorship(aliceKeyring, 10)960 * @returns ```true``` if extrinsic success, otherwise ```false```961 */962 async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {963 const result = await this.helper.executeExtrinsic(964 signer,965 'api.tx.unique.confirmSponsorship', [collectionId],966 true,967 );968969 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'SponsorshipConfirmed');970 }971972 /**973 * Removes the sponsor of a collection, regardless if it consented or not.974 *975 * @param signer keyring of signer976 * @param collectionId ID of collection977 * @example removeSponsor(aliceKeyring, 10)978 * @returns ```true``` if extrinsic success, otherwise ```false```979 */980 async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {981 const result = await this.helper.executeExtrinsic(982 signer,983 'api.tx.unique.removeCollectionSponsor', [collectionId],984 true,985 );986987 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorRemoved');988 }989990 /**991 * Sets the limits of the collection. At least one limit must be specified for a correct call.992 *993 * @param signer keyring of signer994 * @param collectionId ID of collection995 * @param limits collection limits object996 * @example997 * await setLimits(998 * aliceKeyring,999 * 10,1000 * {1001 * sponsorTransferTimeout: 0,1002 * ownerCanDestroy: false1003 * }1004 * )1005 * @returns ```true``` if extrinsic success, otherwise ```false```1006 */1007 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {1008 const result = await this.helper.executeExtrinsic(1009 signer,1010 'api.tx.unique.setCollectionLimits', [collectionId, limits],1011 true,1012 );10131014 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionLimitSet');1015 }10161017 /**1018 * Changes the owner of the collection to the new Substrate address.1019 *1020 * @param signer keyring of signer1021 * @param collectionId ID of collection1022 * @param ownerAddress substrate address of new owner1023 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")1024 * @returns ```true``` if extrinsic success, otherwise ```false```1025 */1026 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {1027 const result = await this.helper.executeExtrinsic(1028 signer,1029 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],1030 true,1031 );10321033 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionOwnerChanged');1034 }10351036 /**1037 * Adds a collection administrator.1038 *1039 * @param signer keyring of signer1040 * @param collectionId ID of collection1041 * @param adminAddressObj Administrator address (substrate or ethereum)1042 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1043 * @returns ```true``` if extrinsic success, otherwise ```false```1044 */1045 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1046 const result = await this.helper.executeExtrinsic(1047 signer,1048 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],1049 true,1050 );10511052 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminAdded');1053 }10541055 /**1056 * Removes a collection administrator.1057 *1058 * @param signer keyring of signer1059 * @param collectionId ID of collection1060 * @param adminAddressObj Administrator address (substrate or ethereum)1061 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1062 * @returns ```true``` if extrinsic success, otherwise ```false```1063 */1064 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1065 const result = await this.helper.executeExtrinsic(1066 signer,1067 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],1068 true,1069 );10701071 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminRemoved');1072 }10731074 /**1075 * Check if user is in allow list.1076 *1077 * @param collectionId ID of collection1078 * @param user Account to check1079 * @example await getAdmins(1)1080 * @returns is user in allow list1081 */1082 async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {1083 return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();1084 }10851086 /**1087 * Adds an address to allow list1088 * @param signer keyring of signer1089 * @param collectionId ID of collection1090 * @param addressObj address to add to the allow list1091 * @returns ```true``` if extrinsic success, otherwise ```false```1092 */1093 async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1094 const result = await this.helper.executeExtrinsic(1095 signer,1096 'api.tx.unique.addToAllowList', [collectionId, addressObj],1097 true,1098 );10991100 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressAdded');1101 }11021103 /**1104 * Removes an address from allow list1105 *1106 * @param signer keyring of signer1107 * @param collectionId ID of collection1108 * @param addressObj address to remove from the allow list1109 * @returns ```true``` if extrinsic success, otherwise ```false```1110 */1111 async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1112 const result = await this.helper.executeExtrinsic(1113 signer,1114 'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1115 true,1116 );11171118 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressRemoved');1119 }11201121 /**1122 * Sets onchain permissions for selected collection.1123 *1124 * @param signer keyring of signer1125 * @param collectionId ID of collection1126 * @param permissions collection permissions object1127 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1128 * @returns ```true``` if extrinsic success, otherwise ```false```1129 */1130 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1131 const result = await this.helper.executeExtrinsic(1132 signer,1133 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1134 true,1135 );11361137 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPermissionSet');1138 }11391140 /**1141 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1142 *1143 * @param signer keyring of signer1144 * @param collectionId ID of collection1145 * @param permissions nesting permissions object1146 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1147 * @returns ```true``` if extrinsic success, otherwise ```false```1148 */1149 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1150 return await this.setPermissions(signer, collectionId, {nesting: permissions});1151 }11521153 /**1154 * Disables nesting for selected collection.1155 *1156 * @param signer keyring of signer1157 * @param collectionId ID of collection1158 * @example disableNesting(aliceKeyring, 10);1159 * @returns ```true``` if extrinsic success, otherwise ```false```1160 */1161 async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1162 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1163 }11641165 /**1166 * Sets onchain properties to the collection.1167 *1168 * @param signer keyring of signer1169 * @param collectionId ID of collection1170 * @param properties array of property objects1171 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1172 * @returns ```true``` if extrinsic success, otherwise ```false```1173 */1174 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1175 const result = await this.helper.executeExtrinsic(1176 signer,1177 'api.tx.unique.setCollectionProperties', [collectionId, properties],1178 true,1179 );11801181 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1182 }11831184 /**1185 * Get collection properties.1186 *1187 * @param collectionId ID of collection1188 * @param propertyKeys optionally filter the returned properties to only these keys1189 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1190 * @returns array of key-value pairs1191 */1192 async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1193 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1194 }11951196 async getPropertiesConsumedSpace(collectionId: number): Promise<number> {1197 const api = this.helper.getApi();1198 const props = (await api.query.common.collectionProperties(collectionId)).toJSON();11991200 return (props! as any).consumedSpace;1201 }12021203 async getCollectionOptions(collectionId: number) {1204 return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1205 }12061207 /**1208 * Deletes onchain properties from the collection.1209 *1210 * @param signer keyring of signer1211 * @param collectionId ID of collection1212 * @param propertyKeys array of property keys to delete1213 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1214 * @returns ```true``` if extrinsic success, otherwise ```false```1215 */1216 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1217 const result = await this.helper.executeExtrinsic(1218 signer,1219 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1220 true,1221 );12221223 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1224 }12251226 /**1227 * Changes the owner of the token.1228 *1229 * @param signer keyring of signer1230 * @param collectionId ID of collection1231 * @param tokenId ID of token1232 * @param addressObj address of a new owner1233 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1234 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1235 * @returns true if the token success, otherwise false1236 */1237 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1238 const result = await this.helper.executeExtrinsic(1239 signer,1240 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1241 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1242 );12431244 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1245 }12461247 /**1248 *1249 * Change ownership of a token(s) on behalf of the owner.1250 *1251 * @param signer keyring of signer1252 * @param collectionId ID of collection1253 * @param tokenId ID of token1254 * @param fromAddressObj address on behalf of which the token will be sent1255 * @param toAddressObj new token owner1256 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1257 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1258 * @returns true if the token success, otherwise false1259 */1260 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1261 const result = await this.helper.executeExtrinsic(1262 signer,1263 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1264 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1265 );1266 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1267 }12681269 /**1270 *1271 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1272 *1273 * @param signer keyring of signer1274 * @param collectionId ID of collection1275 * @param tokenId ID of token1276 * @param amount amount of tokens to be burned. For NFT must be set to 1n1277 * @example burnToken(aliceKeyring, 10, 5);1278 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1279 */1280 async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {1281 const burnResult = await this.helper.executeExtrinsic(1282 signer,1283 'api.tx.unique.burnItem', [collectionId, tokenId, amount],1284 true, // `Unable to burn token for ${label}`,1285 );1286 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1287 if(burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1288 return burnedTokens.success;1289 }12901291 /**1292 * Destroys a concrete instance of NFT on behalf of the owner1293 *1294 * @param signer keyring of signer1295 * @param collectionId ID of collection1296 * @param tokenId ID of token1297 * @param fromAddressObj address on behalf of which the token will be burnt1298 * @param amount amount of tokens to be burned. For NFT must be set to 1n1299 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1300 * @returns ```true``` if extrinsic success, otherwise ```false```1301 */1302 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1303 const burnResult = await this.helper.executeExtrinsic(1304 signer,1305 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1306 true, // `Unable to burn token from for ${label}`,1307 );1308 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1309 return burnedTokens.success && burnedTokens.tokens.length > 0;1310 }13111312 /**1313 * Set, change, or remove approved address to transfer the ownership of the NFT.1314 *1315 * @param signer keyring of signer1316 * @param collectionId ID of collection1317 * @param tokenId ID of token1318 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1319 * @param amount amount of token to be approved. For NFT must be set to 1n1320 * @returns ```true``` if extrinsic success, otherwise ```false```1321 */1322 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1323 const approveResult = await this.helper.executeExtrinsic(1324 signer,1325 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1326 true, // `Unable to approve token for ${label}`,1327 );13281329 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1330 }13311332 /**1333 * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1334 *1335 * @param signer keyring of signer1336 * @param collectionId ID of collection1337 * @param tokenId ID of token1338 * @param fromAddressObj Signer's Ethereum address containing her tokens1339 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1340 * @param amount amount of token to be approved. For NFT must be set to 1n1341 * @returns ```true``` if extrinsic success, otherwise ```false```1342 */1343 async approveTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {1344 const approveResult = await this.helper.executeExtrinsic(1345 signer,1346 'api.tx.unique.approveFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1347 true, // `Unable to approve token for ${label}`,1348 );13491350 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1351 }13521353 /**1354 * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1355 *1356 * @param signer keyring of signer1357 * @param collectionId ID of collection1358 * @param tokenId ID of token1359 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1360 * @param amount amount of token to be approved. For NFT must be set to 1n1361 * @returns ```true``` if extrinsic success, otherwise ```false```1362 */1363 async approveTokenFromEth(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1364 const ethMirror = CrossAccountId.fromKeyring(signer).toEthereum();1365 return await this.approveTokenFrom(signer, collectionId, tokenId, ethMirror, toAddressObj, amount);1366 }13671368 /**1369 * Get the amount of token pieces approved to transfer or burn. Normally 0.1370 *1371 * @param collectionId ID of collection1372 * @param tokenId ID of token1373 * @param toAccountObj address which is approved to use token pieces1374 * @param fromAccountObj address which may have allowed the use of its owned tokens1375 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1376 * @returns number of approved to transfer pieces1377 */1378 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1379 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1380 }13811382 /**1383 * Get the last created token ID in a collection1384 *1385 * @param collectionId ID of collection1386 * @example getLastTokenId(10);1387 * @returns id of the last created token1388 */1389 async getLastTokenId(collectionId: number): Promise<number> {1390 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1391 }13921393 /**1394 * Check if token exists1395 *1396 * @param collectionId ID of collection1397 * @param tokenId ID of token1398 * @example doesTokenExist(10, 20);1399 * @returns true if the token exists, otherwise false1400 */1401 async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1402 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1403 }1404}14051406class NFTnRFT extends CollectionGroup {1407 /**1408 * Get tokens owned by account1409 *1410 * @param collectionId ID of collection1411 * @param addressObj tokens owner1412 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1413 * @returns array of token ids owned by account1414 */1415 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1416 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1417 }14181419 /**1420 * Get token data1421 *1422 * @param collectionId ID of collection1423 * @param tokenId ID of token1424 * @param propertyKeys optionally filter the token properties to only these keys1425 * @param blockHashAt optionally query the data at some block with this hash1426 * @example getToken(10, 5);1427 * @returns human readable token data1428 */1429 async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1430 properties: IProperty[];1431 owner: CrossAccountId;1432 normalizedOwner: CrossAccountId;1433 } | null> {1434 let tokenData;1435 if(typeof blockHashAt === 'undefined') {1436 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1437 }1438 else {1439 if(propertyKeys.length == 0) {1440 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1441 if(!collection) return null;1442 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1443 }1444 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1445 }1446 tokenData = tokenData.toHuman();1447 if(tokenData === null || tokenData.owner === null) return null;1448 const owner = {} as any;1449 for(const key of Object.keys(tokenData.owner)) {1450 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1451 ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1452 : tokenData.owner[key];1453 }1454 tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1455 return tokenData;1456 }14571458 /**1459 * Get token's owner1460 * @param collectionId ID of collection1461 * @param tokenId ID of token1462 * @param blockHashAt optionally query the data at the block with this hash1463 * @example getTokenOwner(10, 5);1464 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1465 */1466 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1467 let owner;1468 if(typeof blockHashAt === 'undefined') {1469 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1470 } else {1471 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1472 }1473 return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1474 }14751476 /**1477 * Recursively find the address that owns the token1478 * @param collectionId ID of collection1479 * @param tokenId ID of token1480 * @param blockHashAt1481 * @example getTokenTopmostOwner(10, 5);1482 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1483 */1484 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1485 let owner;1486 if(typeof blockHashAt === 'undefined') {1487 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1488 } else {1489 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1490 }14911492 if(owner === null) return null;14931494 return owner.toHuman();1495 }14961497 /**1498 * Nest one token into another1499 * @param signer keyring of signer1500 * @param tokenObj token to be nested1501 * @param rootTokenObj token to be parent1502 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1503 * @returns ```true``` if extrinsic success, otherwise ```false```1504 */1505 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1506 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1507 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1508 if(!result) {1509 throw Error('Unable to nest token!');1510 }1511 return result;1512 }15131514 /**1515 * Remove token from nested state1516 * @param signer keyring of signer1517 * @param tokenObj token to unnest1518 * @param rootTokenObj parent of a token1519 * @param toAddressObj address of a new token owner1520 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1521 * @returns ```true``` if extrinsic success, otherwise ```false```1522 */1523 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1524 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1525 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1526 if(!result) {1527 throw Error('Unable to unnest token!');1528 }1529 return result;1530 }15311532 /**1533 * Set permissions to change token properties1534 *1535 * @param signer keyring of signer1536 * @param collectionId ID of collection1537 * @param permissions permissions to change a property by the collection admin or token owner1538 * @example setTokenPropertyPermissions(1539 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1540 * )1541 * @returns true if extrinsic success otherwise false1542 */1543 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1544 const result = await this.helper.executeExtrinsic(1545 signer,1546 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1547 true,1548 );15491550 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1551 }15521553 /**1554 * Get token property permissions.1555 *1556 * @param collectionId ID of collection1557 * @param propertyKeys optionally filter the returned property permissions to only these keys1558 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1559 * @returns array of key-permission pairs1560 */1561 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1562 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1563 }15641565 /**1566 * Set token properties1567 *1568 * @param signer keyring of signer1569 * @param collectionId ID of collection1570 * @param tokenId ID of token1571 * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1572 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1573 * @returns ```true``` if extrinsic success, otherwise ```false```1574 */1575 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1576 const result = await this.helper.executeExtrinsic(1577 signer,1578 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1579 true,1580 );15811582 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1583 }15841585 /**1586 * Get properties, metadata assigned to a token.1587 *1588 * @param collectionId ID of collection1589 * @param tokenId ID of token1590 * @param propertyKeys optionally filter the returned properties to only these keys1591 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1592 * @returns array of key-value pairs1593 */1594 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1595 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1596 }15971598 /**1599 * Delete the provided properties of a token1600 * @param signer keyring of signer1601 * @param collectionId ID of collection1602 * @param tokenId ID of token1603 * @param propertyKeys property keys to be deleted1604 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1605 * @returns ```true``` if extrinsic success, otherwise ```false```1606 */1607 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1608 const result = await this.helper.executeExtrinsic(1609 signer,1610 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1611 true,1612 );16131614 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1615 }16161617 /**1618 * Mint new collection1619 *1620 * @param signer keyring of signer1621 * @param collectionOptions basic collection options and properties1622 * @param mode NFT or RFT type of a collection1623 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1624 * @returns object of the created collection1625 */1626 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1627 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1628 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1629 for(const key of ['name', 'description', 'tokenPrefix']) {1630 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);1631 }16321633 let flags = 0;1634 // convert CollectionFlags to number and join them in one number1635 if(collectionOptions.flags) {1636 for(let i = 0; i < collectionOptions.flags.length; i++){1637 const flag = collectionOptions.flags[i];1638 flags = flags | flag;1639 }1640 }1641 collectionOptions.flags = [flags];16421643 const creationResult = await this.helper.executeExtrinsic(1644 signer,1645 'api.tx.unique.createCollectionEx', [collectionOptions],1646 true, // errorLabel,1647 );1648 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1649 }16501651 getCollectionObject(_collectionId: number): any {1652 return null;1653 }16541655 getTokenObject(_collectionId: number, _tokenId: number): any {1656 return null;1657 }16581659 /**1660 * Tells whether the given `owner` approves the `operator`.1661 * @param collectionId ID of collection1662 * @param owner owner address1663 * @param operator operator addrees1664 * @returns true if operator is enabled1665 */1666 async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1667 return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1668 }16691670 /** Sets or unsets the approval of a given operator.1671 * The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1672 * @param operator Operator1673 * @param approved Should operator status be granted or revoked?1674 * @returns ```true``` if extrinsic success, otherwise ```false```1675 */1676 async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1677 const result = await this.helper.executeExtrinsic(1678 signer,1679 'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1680 true,1681 );1682 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1683 }1684}168516861687class NFTGroup extends NFTnRFT {1688 /**1689 * Get collection object1690 * @param collectionId ID of collection1691 * @example getCollectionObject(2);1692 * @returns instance of UniqueNFTCollection1693 */1694 getCollectionObject(collectionId: number): UniqueNFTCollection {1695 return new UniqueNFTCollection(collectionId, this.helper);1696 }16971698 /**1699 * Get token object1700 * @param collectionId ID of collection1701 * @param tokenId ID of token1702 * @example getTokenObject(10, 5);1703 * @returns instance of UniqueNFTToken1704 */1705 getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1706 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1707 }17081709 /**1710 * Is token approved to transfer1711 * @param collectionId ID of collection1712 * @param tokenId ID of token1713 * @param toAccountObj address to be approved1714 * @returns ```true``` if extrinsic success, otherwise ```false```1715 */1716 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1717 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1718 }17191720 /**1721 * Changes the owner of the token.1722 *1723 * @param signer keyring of signer1724 * @param collectionId ID of collection1725 * @param tokenId ID of token1726 * @param addressObj address of a new owner1727 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1728 * @returns ```true``` if extrinsic success, otherwise ```false```1729 */1730 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1731 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1732 }17331734 /**1735 *1736 * Change ownership of a NFT on behalf of the owner.1737 *1738 * @param signer keyring of signer1739 * @param collectionId ID of collection1740 * @param tokenId ID of token1741 * @param fromAddressObj address on behalf of which the token will be sent1742 * @param toAddressObj new token owner1743 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1744 * @returns ```true``` if extrinsic success, otherwise ```false```1745 */1746 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1747 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1748 }17491750 /**1751 * Get tokens nested in the provided token1752 * @param collectionId ID of collection1753 * @param tokenId ID of token1754 * @param blockHashAt optionally query the data at the block with this hash1755 * @example getTokenChildren(10, 5);1756 * @returns tokens whose depth of nesting is <= 51757 */1758 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1759 let children;1760 if(typeof blockHashAt === 'undefined') {1761 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1762 } else {1763 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1764 }17651766 return children.toJSON().map((x: any) => ({collectionId: x.collection, tokenId: x.token}));1767 }17681769 /**1770 * Mint new collection1771 * @param signer keyring of signer1772 * @param collectionOptions Collection options1773 * @example1774 * mintCollection(aliceKeyring, {1775 * name: 'New',1776 * description: 'New collection',1777 * tokenPrefix: 'NEW',1778 * })1779 * @returns object of the created collection1780 */1781 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1782 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1783 }17841785 /**1786 * Mint new token1787 * @param signer keyring of signer1788 * @param data token data1789 * @returns created token object1790 */1791 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1792 const creationResult = await this.helper.executeExtrinsic(1793 signer,1794 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1795 NFT: {1796 properties: data.properties,1797 },1798 }],1799 true,1800 );1801 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1802 if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1803 if(createdTokens.tokens.length < 1) throw Error('No tokens minted');1804 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1805 }18061807 /**1808 * Mint multiple NFT tokens1809 * @param signer keyring of signer1810 * @param collectionId ID of collection1811 * @param tokens array of tokens with owner and properties1812 * @example1813 * mintMultipleTokens(aliceKeyring, 10, [{1814 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1815 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1816 * },{1817 * owner: {Ethereum: "0x9F0583DbB855d..."},1818 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1819 * }]);1820 * @returns ```true``` if extrinsic success, otherwise ```false```1821 */1822 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1823 const creationResult = await this.helper.executeExtrinsic(1824 signer,1825 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1826 true,1827 );1828 const collection = this.getCollectionObject(collectionId);1829 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1830 }18311832 /**1833 * Mint multiple NFT tokens with one owner1834 * @param signer keyring of signer1835 * @param collectionId ID of collection1836 * @param owner tokens owner1837 * @param tokens array of tokens with owner and properties1838 * @example1839 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1840 * properties: [{1841 * key: "gender",1842 * value: "female",1843 * },{1844 * key: "age",1845 * value: "33",1846 * }],1847 * }]);1848 * @returns array of newly created tokens1849 */1850 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1851 const rawTokens = [];1852 for(const token of tokens) {1853 const raw = {NFT: {properties: token.properties}};1854 rawTokens.push(raw);1855 }1856 const creationResult = await this.helper.executeExtrinsic(1857 signer,1858 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1859 true,1860 );1861 const collection = this.getCollectionObject(collectionId);1862 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1863 }18641865 /**1866 * Set, change, or remove approved address to transfer the ownership of the NFT.1867 *1868 * @param signer keyring of signer1869 * @param collectionId ID of collection1870 * @param tokenId ID of token1871 * @param toAddressObj address to approve1872 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1873 * @returns ```true``` if extrinsic success, otherwise ```false```1874 */1875 approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1876 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1877 }1878}187918801881class RFTGroup extends NFTnRFT {1882 /**1883 * Get collection object1884 * @param collectionId ID of collection1885 * @example getCollectionObject(2);1886 * @returns instance of UniqueRFTCollection1887 */1888 getCollectionObject(collectionId: number): UniqueRFTCollection {1889 return new UniqueRFTCollection(collectionId, this.helper);1890 }18911892 /**1893 * Get token object1894 * @param collectionId ID of collection1895 * @param tokenId ID of token1896 * @example getTokenObject(10, 5);1897 * @returns instance of UniqueNFTToken1898 */1899 getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1900 return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1901 }19021903 /**1904 * Get top 10 token owners with the largest number of pieces1905 * @param collectionId ID of collection1906 * @param tokenId ID of token1907 * @example getTokenTop10Owners(10, 5);1908 * @returns array of top 10 owners1909 */1910 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1911 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1912 }19131914 /**1915 * Get number of pieces owned by address1916 * @param collectionId ID of collection1917 * @param tokenId ID of token1918 * @param addressObj address token owner1919 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1920 * @returns number of pieces ownerd by address1921 */1922 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1923 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1924 }19251926 /**1927 * Transfer pieces of token to another address1928 * @param signer keyring of signer1929 * @param collectionId ID of collection1930 * @param tokenId ID of token1931 * @param addressObj address of a new owner1932 * @param amount number of pieces to be transfered1933 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1934 * @returns ```true``` if extrinsic success, otherwise ```false```1935 */1936 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1937 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1938 }19391940 /**1941 * Change ownership of some pieces of RFT on behalf of the owner.1942 * @param signer keyring of signer1943 * @param collectionId ID of collection1944 * @param tokenId ID of token1945 * @param fromAddressObj address on behalf of which the token will be sent1946 * @param toAddressObj new token owner1947 * @param amount number of pieces to be transfered1948 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1949 * @returns ```true``` if extrinsic success, otherwise ```false```1950 */1951 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1952 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1953 }19541955 /**1956 * Mint new collection1957 * @param signer keyring of signer1958 * @param collectionOptions Collection options1959 * @example1960 * mintCollection(aliceKeyring, {1961 * name: 'New',1962 * description: 'New collection',1963 * tokenPrefix: 'NEW',1964 * })1965 * @returns object of the created collection1966 */1967 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1968 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1969 }19701971 /**1972 * Mint new token1973 * @param signer keyring of signer1974 * @param data token data1975 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1976 * @returns created token object1977 */1978 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1979 const creationResult = await this.helper.executeExtrinsic(1980 signer,1981 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1982 ReFungible: {1983 pieces: data.pieces,1984 properties: data.properties,1985 },1986 }],1987 true,1988 );1989 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1990 if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1991 if(createdTokens.tokens.length < 1) throw Error('No tokens minted');1992 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1993 }19941995 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {1996 throw Error('Not implemented');1997 const creationResult = await this.helper.executeExtrinsic(1998 signer,1999 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],2000 true, // `Unable to mint RFT tokens for ${label}`,2001 );2002 const collection = this.getCollectionObject(collectionId);2003 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2004 }20052006 /**2007 * Mint multiple RFT tokens with one owner2008 * @param signer keyring of signer2009 * @param collectionId ID of collection2010 * @param owner tokens owner2011 * @param tokens array of tokens with properties and pieces2012 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);2013 * @returns array of newly created RFT tokens2014 */2015 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {2016 const rawTokens = [];2017 for(const token of tokens) {2018 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};2019 rawTokens.push(raw);2020 }2021 const creationResult = await this.helper.executeExtrinsic(2022 signer,2023 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2024 true,2025 );2026 const collection = this.getCollectionObject(collectionId);2027 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2028 }20292030 /**2031 * Destroys a concrete instance of RFT.2032 * @param signer keyring of signer2033 * @param collectionId ID of collection2034 * @param tokenId ID of token2035 * @param amount number of pieces to be burnt2036 * @example burnToken(aliceKeyring, 10, 5);2037 * @returns ```true``` if the extrinsic is successful, otherwise ```false```2038 */2039 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {2040 return await super.burnToken(signer, collectionId, tokenId, amount);2041 }20422043 /**2044 * Destroys a concrete instance of RFT on behalf of the owner.2045 * @param signer keyring of signer2046 * @param collectionId ID of collection2047 * @param tokenId ID of token2048 * @param fromAddressObj address on behalf of which the token will be burnt2049 * @param amount number of pieces to be burnt2050 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)2051 * @returns ```true``` if extrinsic success, otherwise ```false```2052 */2053 async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2054 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);2055 }20562057 /**2058 * Set, change, or remove approved address to transfer the ownership of the RFT.2059 *2060 * @param signer keyring of signer2061 * @param collectionId ID of collection2062 * @param tokenId ID of token2063 * @param toAddressObj address to approve2064 * @param amount number of pieces to be approved2065 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);2066 * @returns true if the token success, otherwise false2067 */2068 approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {2069 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);2070 }20712072 /**2073 * Get total number of pieces2074 * @param collectionId ID of collection2075 * @param tokenId ID of token2076 * @example getTokenTotalPieces(10, 5);2077 * @returns number of pieces2078 */2079 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {2080 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();2081 }20822083 /**2084 * Change number of token pieces. Signer must be the owner of all token pieces.2085 * @param signer keyring of signer2086 * @param collectionId ID of collection2087 * @param tokenId ID of token2088 * @param amount new number of pieces2089 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);2090 * @returns true if the repartion was success, otherwise false2091 */2092 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {2093 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);2094 const repartitionResult = await this.helper.executeExtrinsic(2095 signer,2096 'api.tx.unique.repartition', [collectionId, tokenId, amount],2097 true,2098 );2099 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');2100 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');2101 }2102}210321042105class FTGroup extends CollectionGroup {2106 /**2107 * Get collection object2108 * @param collectionId ID of collection2109 * @example getCollectionObject(2);2110 * @returns instance of UniqueFTCollection2111 */2112 getCollectionObject(collectionId: number): UniqueFTCollection {2113 return new UniqueFTCollection(collectionId, this.helper);2114 }21152116 /**2117 * Mint new fungible collection2118 * @param signer keyring of signer2119 * @param collectionOptions Collection options2120 * @param decimalPoints number of token decimals2121 * @example2122 * mintCollection(aliceKeyring, {2123 * name: 'New',2124 * description: 'New collection',2125 * tokenPrefix: 'NEW',2126 * }, 18)2127 * @returns newly created fungible collection2128 */2129 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {2130 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object2131 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');2132 collectionOptions.mode = {fungible: decimalPoints};2133 for(const key of ['name', 'description', 'tokenPrefix']) {2134 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);2135 }2136 const creationResult = await this.helper.executeExtrinsic(2137 signer,2138 'api.tx.unique.createCollectionEx', [collectionOptions],2139 true,2140 );2141 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));2142 }21432144 /**2145 * Mint tokens2146 * @param signer keyring of signer2147 * @param collectionId ID of collection2148 * @param owner address owner of new tokens2149 * @param amount amount of tokens to be meanted2150 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);2151 * @returns ```true``` if extrinsic success, otherwise ```false```2152 */2153 async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {2154 const creationResult = await this.helper.executeExtrinsic(2155 signer,2156 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {2157 Fungible: {2158 value: amount,2159 },2160 }],2161 true, // `Unable to mint fungible tokens for ${label}`,2162 );2163 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2164 }21652166 /**2167 * Mint multiple Fungible tokens with one owner2168 * @param signer keyring of signer2169 * @param collectionId ID of collection2170 * @param owner tokens owner2171 * @param tokens array of tokens with properties and pieces2172 * @returns ```true``` if extrinsic success, otherwise ```false```2173 */2174 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: { value: bigint }[], owner: ICrossAccountId): Promise<boolean> {2175 const rawTokens = [];2176 for(const token of tokens) {2177 const raw = {Fungible: {Value: token.value}};2178 rawTokens.push(raw);2179 }2180 const creationResult = await this.helper.executeExtrinsic(2181 signer,2182 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2183 true,2184 );2185 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2186 }21872188 /**2189 * Get the top 10 owners with the largest balance for the Fungible collection2190 * @param collectionId ID of collection2191 * @example getTop10Owners(10);2192 * @returns array of ```ICrossAccountId```2193 */2194 async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2195 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2196 }21972198 /**2199 * Get account balance2200 * @param collectionId ID of collection2201 * @param addressObj address of owner2202 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2203 * @returns amount of fungible tokens owned by address2204 */2205 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2206 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2207 }22082209 /**2210 * Transfer tokens to address2211 * @param signer keyring of signer2212 * @param collectionId ID of collection2213 * @param toAddressObj address recipient2214 * @param amount amount of tokens to be sent2215 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2216 * @returns ```true``` if extrinsic success, otherwise ```false```2217 */2218 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2219 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2220 }22212222 /**2223 * Transfer some tokens on behalf of the owner.2224 * @param signer keyring of signer2225 * @param collectionId ID of collection2226 * @param fromAddressObj address on behalf of which tokens will be sent2227 * @param toAddressObj address where token to be sent2228 * @param amount number of tokens to be sent2229 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2230 * @returns ```true``` if extrinsic success, otherwise ```false```2231 */2232 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {2233 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2234 }22352236 /**2237 * Destroy some amount of tokens2238 * @param signer keyring of signer2239 * @param collectionId ID of collection2240 * @param amount amount of tokens to be destroyed2241 * @example burnTokens(aliceKeyring, 10, 1000n);2242 * @returns ```true``` if extrinsic success, otherwise ```false```2243 */2244 async burnTokens(signer: IKeyringPair, collectionId: number, amount = 1n): Promise<boolean> {2245 return await super.burnToken(signer, collectionId, 0, amount);2246 }22472248 /**2249 * Burn some tokens on behalf of the owner.2250 * @param signer keyring of signer2251 * @param collectionId ID of collection2252 * @param fromAddressObj address on behalf of which tokens will be burnt2253 * @param amount amount of tokens to be burnt2254 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2255 * @returns ```true``` if extrinsic success, otherwise ```false```2256 */2257 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2258 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2259 }22602261 /**2262 * Get total collection supply2263 * @param collectionId2264 * @returns2265 */2266 async getTotalPieces(collectionId: number): Promise<bigint> {2267 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2268 }22692270 /**2271 * Set, change, or remove approved address to transfer tokens.2272 *2273 * @param signer keyring of signer2274 * @param collectionId ID of collection2275 * @param toAddressObj address to be approved2276 * @param amount amount of tokens to be approved2277 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2278 * @returns ```true``` if extrinsic success, otherwise ```false```2279 */2280 approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2281 return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2282 }22832284 /**2285 * Get amount of fungible tokens approved to transfer2286 * @param collectionId ID of collection2287 * @param fromAddressObj owner of tokens2288 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2289 * @returns number of tokens approved for the transfer2290 */2291 getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2292 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2293 }2294}229522962297class ChainGroup extends HelperGroup<ChainHelperBase> {2298 /**2299 * Get system properties of a chain2300 * @example getChainProperties();2301 * @returns ss58Format, token decimals, and token symbol2302 */2303 getChainProperties(): IChainProperties {2304 const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2305 return {2306 ss58Format: properties.ss58Format.toJSON(),2307 tokenDecimals: properties.tokenDecimals.toJSON(),2308 tokenSymbol: properties.tokenSymbol.toJSON(),2309 };2310 }23112312 /**2313 * Get chain header2314 * @example getLatestBlockNumber();2315 * @returns the number of the last block2316 */2317 async getLatestBlockNumber(): Promise<number> {2318 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2319 }23202321 /**2322 * Get block hash by block number2323 * @param blockNumber number of block2324 * @example getBlockHashByNumber(12345);2325 * @returns hash of a block2326 */2327 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2328 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2329 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2330 return blockHash;2331 }23322333 // TODO add docs2334 async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2335 const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2336 if(!blockHash) return null;2337 return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2338 }23392340 /**2341 * Get latest relay block2342 * @returns {number} relay block2343 */2344 async getRelayBlockNumber(): Promise<bigint> {2345 const blockNumber = (await this.helper.callRpc('api.query.parachainSystem.validationData')).toJSON().relayParentNumber;2346 return BigInt(blockNumber);2347 }23482349 /**2350 * Get account nonce2351 * @param address substrate address2352 * @example getNonce("5GrwvaEF5zXb26Fz...");2353 * @returns number, account's nonce2354 */2355 async getNonce(address: TSubstrateAccount): Promise<number> {2356 return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2357 }2358}23592360class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2361 /**2362 * Get substrate address balance2363 * @param address substrate address2364 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2365 * @returns amount of tokens on address2366 */2367 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2368 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2369 }23702371 /**2372 * Transfer tokens to substrate address2373 * @param signer keyring of signer2374 * @param address substrate address of a recipient2375 * @param amount amount of tokens to be transfered2376 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2377 * @returns ```true``` if extrinsic success, otherwise ```false```2378 */2379 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2380 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}`*/);23812382 let transfer = {from: null, to: null, amount: 0n} as any;2383 result.result.events.forEach(({event: {data, method, section}}) => {2384 if((section === 'balances') && (method === 'Transfer')) {2385 transfer = {2386 from: this.helper.address.normalizeSubstrate(data[0]),2387 to: this.helper.address.normalizeSubstrate(data[1]),2388 amount: BigInt(data[2]),2389 };2390 }2391 });2392 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2393 && this.helper.address.normalizeSubstrate(address) === transfer.to2394 && BigInt(amount) === transfer.amount;2395 return isSuccess;2396 }23972398 /**2399 * Get full substrate balance including free, frozen, and reserved2400 * @param address substrate address2401 * @returns2402 */2403 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2404 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2405 return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2406 }24072408 /**2409 * Get total issuance2410 * @returns2411 */2412 async getTotalIssuance(): Promise<bigint> {2413 const total = (await this.helper.callRpc('api.query.balances.totalIssuance', []));2414 return total.toBigInt();2415 }24162417 async getLocked(address: TSubstrateAccount): Promise<{ id: string, amount: bigint, reason: string }[]> {2418 const locks = (await this.helper.callRpc('api.query.balances.locks', [address])).toHuman();2419 return locks.map((lock: any) => ({id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}));2420 }2421 async getFrozen(address: TSubstrateAccount): Promise<{ id: string, amount: bigint }[]> {2422 const locks = (await this.helper.api!.query.balances.freezes(address)) as unknown as Array<any>;2423 return locks.map(lock => ({id: lock.id.toUtf8(), amount: lock.amount.toBigInt()}));2424 }2425}24262427class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2428 /**2429 * Get ethereum address balance2430 * @param address ethereum address2431 * @example getEthereum("0x9F0583DbB855d...")2432 * @returns amount of tokens on address2433 */2434 async getEthereum(address: TEthereumAccount): Promise<bigint> {2435 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2436 }24372438 /**2439 * Transfer tokens to address2440 * @param signer keyring of signer2441 * @param address Ethereum address of a recipient2442 * @param amount amount of tokens to be transfered2443 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2444 * @returns ```true``` if extrinsic success, otherwise ```false```2445 */2446 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2447 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);24482449 let transfer = {from: null, to: null, amount: 0n} as any;2450 result.result.events.forEach(({event: {data, method, section}}) => {2451 if((section === 'balances') && (method === 'Transfer')) {2452 transfer = {2453 from: data[0].toString(),2454 to: data[1].toString(),2455 amount: BigInt(data[2]),2456 };2457 }2458 });2459 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2460 && address === transfer.to2461 && BigInt(amount) === transfer.amount;2462 return isSuccess;2463 }2464}24652466class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2467 subBalanceGroup: SubstrateBalanceGroup<T>;2468 ethBalanceGroup: EthereumBalanceGroup<T>;24692470 constructor(helper: T) {2471 super(helper);2472 this.subBalanceGroup = new SubstrateBalanceGroup(helper);2473 this.ethBalanceGroup = new EthereumBalanceGroup(helper);2474 }24752476 getCollectionCreationPrice(): bigint {2477 return 2n * this.getOneTokenNominal();2478 }2479 /**2480 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2481 * @example getOneTokenNominal()2482 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2483 */2484 getOneTokenNominal(): bigint {2485 const chainProperties = this.helper.chain.getChainProperties();2486 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2487 }24882489 /**2490 * Get substrate address balance2491 * @param address substrate address2492 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2493 * @returns amount of tokens on address2494 */2495 getSubstrate(address: TSubstrateAccount): Promise<bigint> {2496 return this.subBalanceGroup.getSubstrate(address);2497 }24982499 /**2500 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2501 * @param address substrate address2502 * @returns2503 */2504 getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2505 return this.subBalanceGroup.getSubstrateFull(address);2506 }25072508 /**2509 * Get total issuance2510 * @returns2511 */2512 getTotalIssuance(): Promise<bigint> {2513 return this.subBalanceGroup.getTotalIssuance();2514 }25152516 /**2517 * Get locked balances2518 * @param address substrate address2519 * @returns locked balances with reason via api.query.balances.locks2520 * @deprecated all the methods should switch to getFrozen2521 */2522 getLocked(address: TSubstrateAccount) {2523 return this.subBalanceGroup.getLocked(address);2524 }25252526 /**2527 * Get frozen balances2528 * @param address substrate address2529 * @returns frozen balances with id via api.query.balances.freezes2530 */2531 getFrozen(address: TSubstrateAccount) {2532 return this.subBalanceGroup.getFrozen(address);2533 }25342535 /**2536 * Get ethereum address balance2537 * @param address ethereum address2538 * @example getEthereum("0x9F0583DbB855d...")2539 * @returns amount of tokens on address2540 */2541 getEthereum(address: TEthereumAccount): Promise<bigint> {2542 return this.ethBalanceGroup.getEthereum(address);2543 }25442545 async setBalanceSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint) {2546 await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceSetBalance', [address, amount], true);2547 }25482549 /**2550 * Transfer tokens to substrate address2551 * @param signer keyring of signer2552 * @param address substrate address of a recipient2553 * @param amount amount of tokens to be transfered2554 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2555 * @returns ```true``` if extrinsic success, otherwise ```false```2556 */2557 transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2558 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2559 }25602561 async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2562 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);25632564 let transfer = {from: null, to: null, amount: 0n} as any;2565 result.result.events.forEach(({event: {data, method, section}}) => {2566 if((section === 'balances') && (method === 'Transfer')) {2567 transfer = {2568 from: this.helper.address.normalizeSubstrate(data[0]),2569 to: this.helper.address.normalizeSubstrate(data[1]),2570 amount: BigInt(data[2]),2571 };2572 }2573 });2574 let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2575 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2576 isSuccess = isSuccess && BigInt(amount) === transfer.amount;2577 return isSuccess;2578 }25792580 /**2581 * Transfer tokens with the unlock period2582 * @param signer signers Keyring2583 * @param address Substrate address of recipient2584 * @param schedule Schedule params2585 * @example vestedTransfer(signer, recepient.address, 20000, 100, 10, 50 * nominal); // total amount of vested tokens will be 100 * 50 = 50002586 */2587 async vestedTransfer(signer: TSigner, address: TSubstrateAccount, schedule: { start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }): Promise<void> {2588 const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.vestedTransfer', [address, schedule]);2589 const event = result.result.events2590 .find(e => e.event.section === 'vesting' &&2591 e.event.method === 'VestingScheduleAdded' &&2592 e.event.data[0].toHuman() === signer.address);2593 if(!event) throw Error('Cannot find transfer in events');2594 }25952596 /**2597 * Get schedule for recepient of vested transfer2598 * @param address Substrate address of recipient2599 * @returns2600 */2601 async getVestingSchedules(address: TSubstrateAccount): Promise<{ start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }[]> {2602 const schedule = (await this.helper.callRpc('api.query.vesting.vestingSchedules', [address])).toJSON();2603 return schedule.map((schedule: any) => ({2604 start: BigInt(schedule.start),2605 period: BigInt(schedule.period),2606 periodCount: BigInt(schedule.periodCount),2607 perPeriod: BigInt(schedule.perPeriod),2608 }));2609 }26102611 /**2612 * Claim vested tokens2613 * @param signer signers Keyring2614 */2615 async claim(signer: TSigner) {2616 const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.claim', []);2617 const event = result.result.events2618 .find(e => e.event.section === 'vesting' &&2619 e.event.method === 'Claimed' &&2620 e.event.data[0].toHuman() === signer.address);2621 if(!event) throw Error('Cannot find claim in events');2622 }2623}26242625class AddressGroup extends HelperGroup<ChainHelperBase> {2626 /**2627 * Normalizes the address to the specified ss58 format, by default ```42```.2628 * @param address substrate address2629 * @param ss58Format format for address conversion, by default ```42```2630 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2631 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2632 */2633 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2634 return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2635 }26362637 /**2638 * Get address in the connected chain format2639 * @param address substrate address2640 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2641 * @returns address in chain format2642 */2643 normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2644 return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2645 }26462647 /**2648 * Get substrate mirror of an ethereum address2649 * @param ethAddress ethereum address2650 * @param toChainFormat false for normalized account2651 * @example ethToSubstrate('0x9F0583DbB855d...')2652 * @returns substrate mirror of a provided ethereum address2653 */2654 ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat = false): TSubstrateAccount {2655 return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2656 }26572658 /**2659 * Get ethereum mirror of a substrate address2660 * @param subAddress substrate account2661 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2662 * @returns ethereum mirror of a provided substrate address2663 */2664 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2665 return CrossAccountId.translateSubToEth(subAddress);2666 }26672668 /**2669 * Encode key to substrate address2670 * @param key key for encoding address2671 * @param ss58Format prefix for encoding to the address of the corresponding network2672 * @returns encoded substrate address2673 */2674 encodeSubstrateAddress(key: Uint8Array | string | bigint, ss58Format = 42): string {2675 const u8a: Uint8Array = typeof key === 'string'2676 ? hexToU8a(key)2677 : typeof key === 'bigint'2678 ? hexToU8a(key.toString(16))2679 : key;26802681 if(ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2682 throw new Error(`ss58Format is not valid, received ${typeofss58Format} "${ss58Format}"`);2683 }26842685 const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2686 if(!allowedDecodedLengths.includes(u8a.length)) {2687 throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2688 }26892690 const u8aPrefix = ss58Format < 642691 ? new Uint8Array([ss58Format])2692 : new Uint8Array([2693 ((ss58Format & 0xfc) >> 2) | 0x40,2694 (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2695 ]);26962697 const input = u8aConcat(u8aPrefix, u8a);26982699 return base58Encode(u8aConcat(2700 input,2701 blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2702 ));2703 }27042705 /**2706 * Restore substrate address from bigint representation2707 * @param number decimal representation of substrate address2708 * @returns substrate address2709 */2710 restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2711 if(this.helper.api === null) {2712 throw 'Not connected';2713 }2714 const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2715 if(res === undefined || res === null) {2716 throw 'Restore address error';2717 }2718 return res.toString();2719 }27202721 /**2722 * Convert etherium cross account id to substrate cross account id2723 * @param ethCrossAccount etherium cross account2724 * @returns substrate cross account id2725 */2726 convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2727 if(ethCrossAccount.sub === '0') {2728 return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2729 }27302731 const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2732 return {Substrate: ss58};2733 }27342735 paraSiblingSovereignAccount(paraid: number) {2736 // We are getting a *sibling* parachain sovereign account,2737 // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2738 const siblingPrefix = '0x7369626c';27392740 const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2741 const suffix = '000000000000000000000000000000000000000000000000';27422743 return siblingPrefix + encodedParaId + suffix;2744 }2745}27462747class StakingGroup extends HelperGroup<UniqueHelper> {2748 /**2749 * Stake tokens for App Promotion2750 * @param signer keyring of signer2751 * @param amountToStake amount of tokens to stake2752 * @param label extra label for log2753 * @returns2754 */2755 async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2756 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2757 const _stakeResult = await this.helper.executeExtrinsic(2758 signer, 'api.tx.appPromotion.stake',2759 [amountToStake], true,2760 );2761 // TODO extract info from stakeResult2762 return true;2763 }27642765 /**2766 * Unstake all staked tokens2767 * @param signer keyring of signer2768 * @param amountToUnstake amount of tokens to unstake2769 * @param label extra label for log2770 * @returns block hash where unstake happened2771 */2772 async unstakeAll(signer: TSigner, label?: string): Promise<string> {2773 if(typeof label === 'undefined') label = `${signer.address}`;2774 const unstakeResult = await this.helper.executeExtrinsic(2775 signer, 'api.tx.appPromotion.unstakeAll',2776 [], true,2777 );2778 return unstakeResult.blockHash;2779 }27802781 /**2782 * Unstake the part of a staked tokens2783 * @param signer keyring of signer2784 * @param amount amount of tokens to unstake2785 * @param label extra label for log2786 * @returns block hash where unstake happened2787 */2788 async unstakePartial(signer: TSigner, amount: bigint, label?: string): Promise<string> {2789 if(typeof label === 'undefined') label = `${signer.address}`;2790 const unstakeResult = await this.helper.executeExtrinsic(2791 signer, 'api.tx.appPromotion.unstakePartial',2792 [amount], true,2793 );2794 return unstakeResult.blockHash;2795 }27962797 /**2798 * Get total number of active stakes2799 * @param address substrate address2800 * @returns {number}2801 */2802 async getStakesNumber(address: ICrossAccountId): Promise<number> {2803 if('Ethereum' in address) throw Error('only substrate address');2804 return (await this.helper.callRpc('api.query.appPromotion.stakesPerAccount', [address.Substrate])).toNumber();2805 }28062807 /**2808 * Get total staked amount for address2809 * @param address substrate or ethereum address2810 * @returns total staked amount2811 */2812 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2813 if(address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2814 return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2815 }28162817 /**2818 * Get total staked per block2819 * @param address substrate or ethereum address2820 * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2821 */2822 async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2823 const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2824 return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => ({2825 block: block.toBigInt(),2826 amount: amount.toBigInt(),2827 }));2828 }28292830 /**2831 * Get total pending unstake amount for address2832 * @param address substrate or ethereum address2833 * @returns total pending unstake amount2834 */2835 async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2836 return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2837 }28382839 /**2840 * Get pending unstake amount per block for address2841 * @param address substrate or ethereum address2842 * @returns array of pending stakes. `block` – the number of the block in which the unstake was made. `amount` - the number of tokens unstaked in the block2843 */2844 async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2845 const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2846 const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => ({2847 block: block.toBigInt(),2848 amount: amount.toBigInt(),2849 }));2850 return result;2851 }2852}28532854class SchedulerGroup extends HelperGroup<UniqueHelper> {2855 constructor(helper: UniqueHelper) {2856 super(helper);2857 }28582859 cancelScheduled(signer: TSigner, scheduledId: string) {2860 return this.helper.executeExtrinsic(2861 signer,2862 'api.tx.scheduler.cancelNamed',2863 [scheduledId],2864 true,2865 );2866 }28672868 changePriority(signer: TSigner, scheduledId: string, priority: number) {2869 return this.helper.executeExtrinsic(2870 signer,2871 'api.tx.scheduler.changeNamedPriority',2872 [scheduledId, priority],2873 true,2874 );2875 }28762877 scheduleAt<T extends UniqueHelper>(2878 executionBlockNumber: number,2879 options: ISchedulerOptions = {},2880 ) {2881 return this.schedule<T>('schedule', executionBlockNumber, options);2882 }28832884 scheduleAfter<T extends UniqueHelper>(2885 blocksBeforeExecution: number,2886 options: ISchedulerOptions = {},2887 ) {2888 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2889 }28902891 schedule<T extends UniqueHelper>(2892 scheduleFn: 'schedule' | 'scheduleAfter',2893 blocksNum: number,2894 options: ISchedulerOptions = {},2895 ) {2896 // eslint-disable-next-line @typescript-eslint/naming-convention2897 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2898 return this.helper.clone(ScheduledHelperType, {2899 scheduleFn,2900 blocksNum,2901 options,2902 }) as T;2903 }2904}29052906class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {2907 //todo:collator documentation2908 addInvulnerable(signer: TSigner, address: string) {2909 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);2910 }29112912 removeInvulnerable(signer: TSigner, address: string) {2913 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);2914 }29152916 async getInvulnerables(): Promise<string[]> {2917 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());2918 }29192920 /** and also total max invulnerables */2921 maxCollators(): number {2922 return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);2923 }29242925 async getDesiredCollators(): Promise<number> {2926 return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();2927 }29282929 setLicenseBond(signer: TSigner, amount: bigint) {2930 return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);2931 }29322933 async getLicenseBond(): Promise<bigint> {2934 return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();2935 }29362937 obtainLicense(signer: TSigner) {2938 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);2939 }29402941 releaseLicense(signer: TSigner) {2942 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);2943 }29442945 forceReleaseLicense(signer: TSigner, released: string) {2946 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);2947 }29482949 async hasLicense(address: string): Promise<bigint> {2950 return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();2951 }29522953 onboard(signer: TSigner) {2954 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);2955 }29562957 offboard(signer: TSigner) {2958 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);2959 }29602961 async getCandidates(): Promise<string[]> {2962 return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());2963 }2964}29652966class CollectiveGroup extends HelperGroup<UniqueHelper> {2967 /**2968 * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee'2969 */2970 private collective: string;29712972 constructor(helper: UniqueHelper, collective: string) {2973 super(helper);2974 this.collective = collective;2975 }29762977 /**2978 * Check the result of a proposal execution for the success of the underlying proposed extrinsic.2979 * @param events events of the proposal execution2980 * @returns proposal hash2981 */2982 private checkExecutedEvent(events: IPhasicEvent[]): string {2983 const executionEvents = events.filter(x =>2984 x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted'));29852986 if(executionEvents.length != 1) {2987 if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0)2988 throw new Error(`Disapproved by ${this.collective}`);2989 else2990 throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`);2991 }29922993 const result = (executionEvents[0].event.data as any).result;29942995 if(result.isErr) {2996 if(result.asErr.isModule) {2997 const error = result.asErr.asModule;2998 const metaError = this.helper.getApi()?.registry.findMetaError(error);2999 throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`);3000 } else {3001 throw new Error('Proposal execution failed with ' + result.asErr.toHuman());3002 }3003 }30043005 return (executionEvents[0].event.data as any).proposalHash;3006 }30073008 /**3009 * Returns an array of members' addresses.3010 */3011 async getMembers() {3012 return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman();3013 }30143015 /**3016 * Returns the optional address of the prime member of the collective.3017 */3018 async getPrimeMember() {3019 return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman();3020 }30213022 /**3023 * Returns an array of proposal hashes that are currently active for this collective.3024 */3025 async getProposals() {3026 return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman();3027 }30283029 /**3030 * Returns the call originally encoded under the specified hash.3031 * @param hash h256-encoded proposal3032 * @returns the optional call that the proposal hash stands for.3033 */3034 async getProposalCallOf(hash: string) {3035 return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman();3036 }30373038 /**3039 * Returns the total number of proposals so far.3040 */3041 async getTotalProposalsCount() {3042 return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber();3043 }30443045 /**3046 * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately.3047 * @param signer keyring of the proposer3048 * @param proposal constructed call to be executed if the proposal is successful3049 * @param voteThreshold minimal number of votes for the proposal to be verified and executed3050 * @param lengthBound byte length of the encoded call3051 * @returns promise of extrinsic execution and its result3052 */3053 async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) {3054 return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]);3055 }30563057 /**3058 * Casts a vote to either approve or reject a proposal.3059 * @param signer keyring of the voter3060 * @param proposalHash hash of the proposal to be voted for3061 * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors3062 * @param approve aye or nay3063 * @returns promise of extrinsic execution and its result3064 */3065 vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {3066 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]);3067 }30683069 /**3070 * Executes a call immediately as a member of the collective. Needed for the Member origin.3071 * @param signer keyring of the executor member3072 * @param proposal constructed call to be executed by the member3073 * @param lengthBound byte length of the encoded call3074 * @returns promise of extrinsic execution3075 */3076 async execute(signer: TSigner, proposal: any, lengthBound = 10000) {3077 const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]);3078 this.checkExecutedEvent(result.result.events);3079 return result;3080 }30813082 /**3083 * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing.3084 * @param signer keyring of the executor. Can be absolutely anyone.3085 * @param proposalHash hash of the proposal to close3086 * @param proposalIndex index of the proposal generated on its creation3087 * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call.3088 * @param lengthBound byte length of the encoded call3089 * @returns promise of extrinsic execution and its result3090 */3091 async close(3092 signer: TSigner,3093 proposalHash: string,3094 proposalIndex: number,3095 weightBound: [number, number] | any = [20_000_000_000, 1000_000],3096 lengthBound = 10_000,3097 ) {3098 const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [3099 proposalHash,3100 proposalIndex,3101 weightBound,3102 lengthBound,3103 ]);3104 this.checkExecutedEvent(result.result.events);3105 return result;3106 }31073108 /**3109 * Shut down a proposal, regardless of its current state.3110 * @param signer keyring of the disapprover. Must be root3111 * @param proposalHash hash of the proposal to close3112 * @returns promise of extrinsic execution and its result3113 */3114 disapproveProposal(signer: TSigner, proposalHash: string) {3115 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]);3116 }3117}31183119class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {3120 /**3121 * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership'3122 */3123 private membership: string;31243125 constructor(helper: UniqueHelper, membership: string) {3126 super(helper);3127 this.membership = membership;3128 }31293130 /**3131 * Returns an array of members' addresses according to the membership pallet's perception.3132 * Note that it does not recognize the original pallet's members set with `setMembers()`.3133 */3134 async getMembers() {3135 return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman();3136 }31373138 /**3139 * Returns the optional address of the prime member of the collective.3140 */3141 async getPrimeMember() {3142 return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman();3143 }31443145 /**3146 * Add a member to the collective.3147 * @param signer keyring of the setter. Must be root3148 * @param member address of the member to add3149 * @returns promise of extrinsic execution and its result3150 */3151 addMember(signer: TSigner, member: string) {3152 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]);3153 }31543155 addMemberCall(member: string) {3156 return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]);3157 }31583159 /**3160 * Remove a member from the collective.3161 * @param signer keyring of the setter. Must be root3162 * @param member address of the member to remove3163 * @returns promise of extrinsic execution and its result3164 */3165 removeMember(signer: TSigner, member: string) {3166 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]);3167 }31683169 removeMemberCall(member: string) {3170 return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]);3171 }31723173 /**3174 * Set members of the collective to the given list of addresses.3175 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3176 * @param members addresses of the members to set3177 * @returns promise of extrinsic execution and its result3178 */3179 resetMembers(signer: TSigner, members: string[]) {3180 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]);3181 }31823183 /**3184 * Set the collective's prime member to the given address.3185 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3186 * @param prime address of the prime member of the collective3187 * @returns promise of extrinsic execution and its result3188 */3189 setPrime(signer: TSigner, prime: string) {3190 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]);3191 }31923193 setPrimeCall(member: string) {3194 return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]);3195 }31963197 /**3198 * Remove the collective's prime member.3199 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3200 * @returns promise of extrinsic execution and its result3201 */3202 clearPrime(signer: TSigner) {3203 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []);3204 }32053206 clearPrimeCall() {3207 return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []);3208 }3209}32103211class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {3212 /**3213 * Pallet name to make an API call to. Examples: 'FellowshipCollective'3214 */3215 private collective: string;32163217 constructor(helper: UniqueHelper, collective: string) {3218 super(helper);3219 this.collective = collective;3220 }32213222 addMember(signer: TSigner, newMember: string) {3223 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]);3224 }32253226 addMemberCall(newMember: string) {3227 return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]);3228 }32293230 removeMember(signer: TSigner, member: string, minRank: number) {3231 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]);3232 }32333234 removeMemberCall(newMember: string, minRank: number) {3235 return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]);3236 }32373238 promote(signer: TSigner, member: string) {3239 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]);3240 }32413242 promoteCall(newMember: string) {3243 return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [newMember]);3244 }32453246 demote(signer: TSigner, member: string) {3247 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]);3248 }32493250 demoteCall(newMember: string) {3251 return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]);3252 }32533254 vote(signer: TSigner, pollIndex: number, aye: boolean) {3255 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]);3256 }32573258 async getMembers() {3259 return (await this.helper.getApi().query.fellowshipCollective.members.keys())3260 .map((key) => key.args[0].toString());3261 }3262}32633264class ReferendaGroup extends HelperGroup<UniqueHelper> {3265 /**3266 * Pallet name to make an API call to. Examples: 'FellowshipReferenda'3267 */3268 private referenda: string;32693270 constructor(helper: UniqueHelper, referenda: string) {3271 super(helper);3272 this.referenda = referenda;3273 }32743275 submit(3276 signer: TSigner,3277 proposalOrigin: string,3278 proposal: any,3279 enactmentMoment: any,3280 ) {3281 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [3282 {Origins: proposalOrigin},3283 proposal,3284 enactmentMoment,3285 ]);3286 }32873288 placeDecisionDeposit(signer: TSigner, referendumIndex: number) {3289 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]);3290 }32913292 cancel(signer: TSigner, referendumIndex: number) {3293 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]);3294 }32953296 cancelCall(referendumIndex: number) {3297 return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]);3298 }32993300 async referendumInfo(referendumIndex: number) {3301 return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON();3302 }33033304 async enactmentEventId(referendumIndex: number) {3305 const api = await this.helper.getApi();33063307 const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a();3308 return blake2AsHex(bytes, 256);3309 }3310}33113312export interface IFellowshipGroup {3313 collective: RankedCollectiveGroup;3314 referenda: ReferendaGroup;3315}33163317export interface ICollectiveGroup {3318 collective: CollectiveGroup;3319 membership: CollectiveMembershipGroup;3320}33213322class DemocracyGroup extends HelperGroup<UniqueHelper> {3323 // todo displace proposal into types?3324 propose(signer: TSigner, call: any, deposit: bigint) {3325 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3326 }33273328 proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) {3329 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]);3330 }33313332 proposeCall(call: any, deposit: bigint) {3333 return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3334 }33353336 second(signer: TSigner, proposalIndex: number) {3337 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]);3338 }33393340 externalPropose(signer: TSigner, proposalCall: any) {3341 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3342 }33433344 externalProposeMajority(signer: TSigner, proposalCall: any) {3345 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3346 }33473348 externalProposeDefault(signer: TSigner, proposalCall: any) {3349 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3350 }33513352 externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) {3353 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);3354 }33553356 externalProposeCall(proposalCall: any) {3357 return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3358 }33593360 externalProposeMajorityCall(proposalCall: any) {3361 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3362 }33633364 externalProposeDefaultCall(proposalCall: any) {3365 return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3366 }33673368 // ... and blacklist external proposal hash.3369 vetoExternal(signer: TSigner, proposalHash: string) {3370 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]);3371 }33723373 vetoExternalCall(proposalHash: string) {3374 return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]);3375 }33763377 blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) {3378 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3379 }33803381 blacklistCall(proposalHash: string, referendumIndex: number | null = null) {3382 return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3383 }33843385 // proposal. CancelProposalOrigin (root or all techcom)3386 cancelProposal(signer: TSigner, proposalIndex: number) {3387 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]);3388 }33893390 cancelProposalCall(proposalIndex: number) {3391 return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]);3392 }33933394 clearPublicProposals(signer: TSigner) {3395 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []);3396 }33973398 fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) {3399 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3400 }34013402 fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) {3403 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3404 }34053406 // referendum. CancellationOrigin (TechCom member)3407 emergencyCancel(signer: TSigner, referendumIndex: number) {3408 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]);3409 }34103411 emergencyCancelCall(referendumIndex: number) {3412 return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]);3413 }34143415 vote(signer: TSigner, referendumIndex: number, vote: any) {3416 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]);3417 }34183419 removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) {3420 if(targetAccount) {3421 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]);3422 } else {3423 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]);3424 }3425 }34263427 unlock(signer: TSigner, targetAccount: string) {3428 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]);3429 }34303431 delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) {3432 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]);3433 }34343435 undelegate(signer: TSigner) {3436 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []);3437 }34383439 async referendumInfo(referendumIndex: number) {3440 return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON();3441 }34423443 async publicProposals() {3444 return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON();3445 }34463447 async findPublicProposal(proposalIndex: number) {3448 const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex);34493450 return proposalInfo ? proposalInfo[1] : null;3451 }34523453 async expectPublicProposal(proposalIndex: number) {3454 const proposal = await this.findPublicProposal(proposalIndex);34553456 if(proposal) {3457 return proposal;3458 } else {3459 throw Error(`Proposal #${proposalIndex} is expected to exist`);3460 }3461 }34623463 async getExternalProposal() {3464 return (await this.helper.callRpc('api.query.democracy.nextExternal', []));3465 }34663467 async expectExternalProposal() {3468 const proposal = await this.getExternalProposal();34693470 if(proposal) {3471 return proposal;3472 } else {3473 throw Error('An external proposal is expected to exist');3474 }3475 }34763477 /* setMetadata? */34783479 /* todo?3480 referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {3481 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);3482 }*/3483}34843485class PreimageGroup extends HelperGroup<UniqueHelper> {3486 async getPreimageInfo(h256: string) {3487 return (await this.helper.callRpc('api.query.preimage.statusFor', [h256])).toJSON();3488 }34893490 /**3491 * Create a preimage from an API call.3492 * @param signer keyring of the signer.3493 * @param call an extrinsic call3494 * @example await notePreimageFromCall(preimageMaker,3495 * helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd])3496 * );3497 * @returns promise of extrinsic execution.3498 */3499 notePreimageFromCall(signer: TSigner, call: any, returnPreimageHash = false) {3500 return this.notePreimage(signer, call.method.toHex(), returnPreimageHash);3501 }35023503 /**3504 * Create a preimage with a hex or a byte array.3505 * @param signer keyring of the signer.3506 * @param bytes preimage encoded in hex or a byte array, e.g. an extrinsic call.3507 * @example await notePreimage(preimageMaker,3508 * helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex()3509 * );3510 * @returns promise of extrinsic execution.3511 */3512 async notePreimage(signer: TSigner, bytes: string | Uint8Array, returnPreimageHash = false) {3513 const promise = this.helper.executeExtrinsic(signer, 'api.tx.preimage.notePreimage', [bytes]);3514 if(returnPreimageHash) {3515 const result = await promise;3516 const events = result.result.events.filter(x => x.event.method === 'Noted' && x.event.section === 'preimage');3517 const preimageHash = events[0].event.data[0].toHuman();3518 return preimageHash;3519 }3520 return promise;3521 }35223523 /**3524 * Delete an existing preimage and return the deposit.3525 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3526 * @param h256 hash of the preimage.3527 * @returns promise of extrinsic execution.3528 */3529 unnotePreimage(signer: TSigner, h256: string) {3530 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unnotePreimage', [h256]);3531 }35323533 /**3534 * Request a preimage be uploaded to the chain without paying any fees or deposits.3535 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3536 * @param h256 hash of the preimage.3537 * @returns promise of extrinsic execution.3538 */3539 requestPreimage(signer: TSigner, h256: string) {3540 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.requestPreimage', [h256]);3541 }35423543 /**3544 * Clear a previously made request for a preimage.3545 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3546 * @param h256 hash of the preimage.3547 * @returns promise of extrinsic execution.3548 */3549 unrequestPreimage(signer: TSigner, h256: string) {3550 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]);3551 }3552}35533554class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {3555 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {3556 await this.helper.executeExtrinsic(3557 signer,3558 'api.tx.foreignAssets.registerForeignAsset',3559 [ownerAddress, location, metadata],3560 true,3561 );3562 }35633564 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {3565 await this.helper.executeExtrinsic(3566 signer,3567 'api.tx.foreignAssets.updateForeignAsset',3568 [foreignAssetId, location, metadata],3569 true,3570 );3571 }3572}35733574class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {3575 palletName: string;35763577 constructor(helper: T, palletName: string) {3578 super(helper);35793580 this.palletName = palletName;3581 }35823583 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {3584 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);3585 }35863587 async setSafeXcmVersion(signer: TSigner, version: number) {3588 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);3589 }35903591 async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {3592 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);3593 }35943595 async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {3596 const destinationContent = {3597 parents: 0,3598 interior: {3599 X1: {3600 Parachain: destinationParaId,3601 },3602 },3603 };36043605 const beneficiaryContent = {3606 parents: 0,3607 interior: {3608 X1: {3609 AccountId32: {3610 network: 'Any',3611 id: targetAccount,3612 },3613 },3614 },3615 };36163617 const assetsContent = [3618 {3619 id: {3620 Concrete: {3621 parents: 0,3622 interior: 'Here',3623 },3624 },3625 fun: {3626 Fungible: amount,3627 },3628 },3629 ];36303631 let destination;3632 let beneficiary;3633 let assets;36343635 if(xcmVersion == 2) {3636 destination = {V1: destinationContent};3637 beneficiary = {V1: beneficiaryContent};3638 assets = {V1: assetsContent};36393640 } else if(xcmVersion == 3) {3641 destination = {V2: destinationContent};3642 beneficiary = {V2: beneficiaryContent};3643 assets = {V2: assetsContent};36443645 } else {3646 throw Error('Unknown XCM version: ' + xcmVersion);3647 }36483649 const feeAssetItem = 0;36503651 await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);3652 }36533654 async send(signer: IKeyringPair, destination: any, message: any) {3655 await this.helper.executeExtrinsic(3656 signer,3657 `api.tx.${this.palletName}.send`,3658 [3659 destination,3660 message,3661 ],3662 true,3663 );3664 }3665}36663667class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3668 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {3669 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);3670 }36713672 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {3673 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);3674 }36753676 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {3677 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);3678 }3679}36803681class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3682 async accounts(address: string, currencyId: any) {3683 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;3684 return BigInt(free);3685 }3686}36873688class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {3689 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {3690 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);3691 }36923693 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {3694 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);3695 }36963697 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {3698 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);3699 }37003701 async account(assetId: string | number, address: string) {3702 const accountAsset = (3703 await this.helper.callRpc('api.query.assets.account', [assetId, address])3704 ).toJSON()! as any;37053706 if(accountAsset !== null) {3707 return BigInt(accountAsset['balance']);3708 } else {3709 return null;3710 }3711 }3712}37133714class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {3715 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {3716 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);3717 }3718}37193720class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {3721 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {3722 const apiPrefix = 'api.tx.assetManager.';37233724 const registerTx = this.helper.constructApiCall(3725 apiPrefix + 'registerForeignAsset',3726 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],3727 );37283729 const setUnitsTx = this.helper.constructApiCall(3730 apiPrefix + 'setAssetUnitsPerSecond',3731 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],3732 );37333734 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);3735 const encodedProposal = batchCall?.method.toHex() || '';3736 return encodedProposal;3737 }37383739 async assetTypeId(location: any) {3740 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);3741 }3742}37433744class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {3745 notePreimagePallet: string;37463747 constructor(helper: MoonbeamHelper, options: { [key: string]: any } = {}) {3748 super(helper);3749 this.notePreimagePallet = options.notePreimagePallet;3750 }37513752 async notePreimage(signer: TSigner, encodedProposal: string) {3753 await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);3754 }37553756 externalProposeMajority(proposal: any) {3757 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);3758 }37593760 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {3761 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3762 }37633764 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {3765 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);3766 }3767}37683769class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {3770 collective: string;37713772 constructor(helper: MoonbeamHelper, collective: string) {3773 super(helper);37743775 this.collective = collective;3776 }37773778 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {3779 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);3780 }37813782 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {3783 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);3784 }37853786 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {3787 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);3788 }37893790 async proposalCount() {3791 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));3792 }3793}37943795export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;3796export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;37973798export class UniqueHelper extends ChainHelperBase {3799 balance: BalanceGroup<UniqueHelper>;3800 collection: CollectionGroup;3801 nft: NFTGroup;3802 rft: RFTGroup;3803 ft: FTGroup;3804 staking: StakingGroup;3805 scheduler: SchedulerGroup;3806 collatorSelection: CollatorSelectionGroup;3807 council: ICollectiveGroup;3808 technicalCommittee: ICollectiveGroup;3809 fellowship: IFellowshipGroup;3810 democracy: DemocracyGroup;3811 preimage: PreimageGroup;3812 foreignAssets: ForeignAssetsGroup;3813 xcm: XcmGroup<UniqueHelper>;3814 xTokens: XTokensGroup<UniqueHelper>;3815 tokens: TokensGroup<UniqueHelper>;38163817 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3818 super(logger, options.helperBase ?? UniqueHelper);38193820 this.balance = new BalanceGroup(this);3821 this.collection = new CollectionGroup(this);3822 this.nft = new NFTGroup(this);3823 this.rft = new RFTGroup(this);3824 this.ft = new FTGroup(this);3825 this.staking = new StakingGroup(this);3826 this.scheduler = new SchedulerGroup(this);3827 this.collatorSelection = new CollatorSelectionGroup(this);3828 this.council = {3829 collective: new CollectiveGroup(this, 'council'),3830 membership: new CollectiveMembershipGroup(this, 'councilMembership'),3831 };3832 this.technicalCommittee = {3833 collective: new CollectiveGroup(this, 'technicalCommittee'),3834 membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),3835 };3836 this.fellowship = {3837 collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),3838 referenda: new ReferendaGroup(this, 'fellowshipReferenda'),3839 };3840 this.democracy = new DemocracyGroup(this);3841 this.preimage = new PreimageGroup(this);3842 this.foreignAssets = new ForeignAssetsGroup(this);3843 this.xcm = new XcmGroup(this, 'polkadotXcm');3844 this.xTokens = new XTokensGroup(this);3845 this.tokens = new TokensGroup(this);3846 }38473848 getSudo<T extends UniqueHelper>() {3849 // eslint-disable-next-line @typescript-eslint/naming-convention3850 const SudoHelperType = SudoHelper(this.helperBase);3851 return this.clone(SudoHelperType) as T;3852 }3853}38543855export class XcmChainHelper extends ChainHelperBase {3856 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {3857 const wsProvider = new WsProvider(wsEndpoint);3858 this.api = new ApiPromise({3859 provider: wsProvider,3860 });3861 await this.api.isReadyOrError;3862 this.network = await UniqueHelper.detectNetwork(this.api);3863 }3864}38653866export class RelayHelper extends XcmChainHelper {3867 balance: SubstrateBalanceGroup<RelayHelper>;3868 xcm: XcmGroup<RelayHelper>;38693870 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3871 super(logger, options.helperBase ?? RelayHelper);38723873 this.balance = new SubstrateBalanceGroup(this);3874 this.xcm = new XcmGroup(this, 'xcmPallet');3875 }3876}38773878export class WestmintHelper extends XcmChainHelper {3879 balance: SubstrateBalanceGroup<WestmintHelper>;3880 xcm: XcmGroup<WestmintHelper>;3881 assets: AssetsGroup<WestmintHelper>;3882 xTokens: XTokensGroup<WestmintHelper>;38833884 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3885 super(logger, options.helperBase ?? WestmintHelper);38863887 this.balance = new SubstrateBalanceGroup(this);3888 this.xcm = new XcmGroup(this, 'polkadotXcm');3889 this.assets = new AssetsGroup(this);3890 this.xTokens = new XTokensGroup(this);3891 }3892}38933894export class MoonbeamHelper extends XcmChainHelper {3895 balance: EthereumBalanceGroup<MoonbeamHelper>;3896 assetManager: MoonbeamAssetManagerGroup;3897 assets: AssetsGroup<MoonbeamHelper>;3898 xTokens: XTokensGroup<MoonbeamHelper>;3899 democracy: MoonbeamDemocracyGroup;3900 collective: {3901 council: MoonbeamCollectiveGroup,3902 techCommittee: MoonbeamCollectiveGroup,3903 };39043905 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3906 super(logger, options.helperBase ?? MoonbeamHelper);39073908 this.balance = new EthereumBalanceGroup(this);3909 this.assetManager = new MoonbeamAssetManagerGroup(this);3910 this.assets = new AssetsGroup(this);3911 this.xTokens = new XTokensGroup(this);3912 this.democracy = new MoonbeamDemocracyGroup(this, options);3913 this.collective = {3914 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),3915 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),3916 };3917 }3918}39193920export class AstarHelper extends XcmChainHelper {3921 balance: SubstrateBalanceGroup<AstarHelper>;3922 assets: AssetsGroup<AstarHelper>;3923 xcm: XcmGroup<AstarHelper>;39243925 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3926 super(logger, options.helperBase ?? AstarHelper);39273928 this.balance = new SubstrateBalanceGroup(this);3929 this.assets = new AssetsGroup(this);3930 this.xcm = new XcmGroup(this, 'polkadotXcm');3931 }39323933 getSudo<T extends UniqueHelper>() {3934 // eslint-disable-next-line @typescript-eslint/naming-convention3935 const SudoHelperType = SudoHelper(this.helperBase);3936 return this.clone(SudoHelperType) as T;3937 }3938}39393940export class AcalaHelper extends XcmChainHelper {3941 balance: SubstrateBalanceGroup<AcalaHelper>;3942 assetRegistry: AcalaAssetRegistryGroup;3943 xTokens: XTokensGroup<AcalaHelper>;3944 tokens: TokensGroup<AcalaHelper>;3945 xcm: XcmGroup<AcalaHelper>;39463947 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3948 super(logger, options.helperBase ?? AcalaHelper);39493950 this.balance = new SubstrateBalanceGroup(this);3951 this.assetRegistry = new AcalaAssetRegistryGroup(this);3952 this.xTokens = new XTokensGroup(this);3953 this.tokens = new TokensGroup(this);3954 this.xcm = new XcmGroup(this, 'polkadotXcm');3955 }39563957 getSudo<T extends AcalaHelper>() {3958 // eslint-disable-next-line @typescript-eslint/naming-convention3959 const SudoHelperType = SudoHelper(this.helperBase);3960 return this.clone(SudoHelperType) as T;3961 }3962}39633964// eslint-disable-next-line @typescript-eslint/naming-convention3965function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {3966 return class extends Base {3967 scheduleFn: 'schedule' | 'scheduleAfter';3968 blocksNum: number;3969 options: ISchedulerOptions;39703971 constructor(...args: any[]) {3972 const logger = args[0] as ILogger;3973 const options = args[1] as {3974 scheduleFn: 'schedule' | 'scheduleAfter',3975 blocksNum: number,3976 options: ISchedulerOptions3977 };39783979 super(logger);39803981 this.scheduleFn = options.scheduleFn;3982 this.blocksNum = options.blocksNum;3983 this.options = options.options;3984 }39853986 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {3987 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);39883989 const mandatorySchedArgs = [3990 this.blocksNum,3991 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,3992 this.options.priority ?? null,3993 scheduledTx,3994 ];39953996 let schedArgs;3997 let scheduleFn;39983999 if(this.options.scheduledId) {4000 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];40014002 if(this.scheduleFn == 'schedule') {4003 scheduleFn = 'scheduleNamed';4004 } else if(this.scheduleFn == 'scheduleAfter') {4005 scheduleFn = 'scheduleNamedAfter';4006 }4007 } else {4008 schedArgs = mandatorySchedArgs;4009 scheduleFn = this.scheduleFn;4010 }40114012 const extrinsic = 'api.tx.scheduler.' + scheduleFn;40134014 return super.executeExtrinsic(4015 sender,4016 extrinsic as any,4017 schedArgs,4018 expectSuccess,4019 );4020 }4021 };4022}40234024// eslint-disable-next-line @typescript-eslint/naming-convention4025function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {4026 return class extends Base {4027 constructor(...args: any[]) {4028 super(...args);4029 }40304031 async executeExtrinsic(4032 sender: IKeyringPair,4033 extrinsic: string,4034 params: any[],4035 expectSuccess?: boolean,4036 options: Partial<SignerOptions> | null = null,4037 ): Promise<ITransactionResult> {4038 const call = this.constructApiCall(extrinsic, params);4039 const result = await super.executeExtrinsic(4040 sender,4041 'api.tx.sudo.sudo',4042 [call],4043 expectSuccess,4044 options,4045 );40464047 if(result.status === 'Fail') return result;40484049 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;4050 if(data.isErr) {4051 if(data.asErr.isModule) {4052 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;4053 const metaError = super.getApi()?.registry.findMetaError(error);4054 throw new Error(`${metaError.section}.${metaError.name}`);4055 } else if(data.asErr.isToken) {4056 throw new Error(`Token: ${data.asErr.asToken}`);4057 }4058 // May be [object Object] in case of unhandled non-unit enum4059 throw new Error(`Misc: ${data.asErr.toHuman()}`);4060 }4061 return result;4062 }4063 };4064}40654066export class UniqueBaseCollection {4067 helper: UniqueHelper;4068 collectionId: number;40694070 constructor(collectionId: number, uniqueHelper: UniqueHelper) {4071 this.collectionId = collectionId;4072 this.helper = uniqueHelper;4073 }40744075 async getData() {4076 return await this.helper.collection.getData(this.collectionId);4077 }40784079 async getLastTokenId() {4080 return await this.helper.collection.getLastTokenId(this.collectionId);4081 }40824083 async doesTokenExist(tokenId: number) {4084 return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);4085 }40864087 async getAdmins() {4088 return await this.helper.collection.getAdmins(this.collectionId);4089 }40904091 async getAllowList() {4092 return await this.helper.collection.getAllowList(this.collectionId);4093 }40944095 async getEffectiveLimits() {4096 return await this.helper.collection.getEffectiveLimits(this.collectionId);4097 }40984099 async getProperties(propertyKeys?: string[] | null) {4100 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);4101 }41024103 async getPropertiesConsumedSpace() {4104 return await this.helper.collection.getPropertiesConsumedSpace(this.collectionId);4105 }41064107 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {4108 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);4109 }41104111 async getOptions() {4112 return await this.helper.collection.getCollectionOptions(this.collectionId);4113 }41144115 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {4116 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);4117 }41184119 async confirmSponsorship(signer: TSigner) {4120 return await this.helper.collection.confirmSponsorship(signer, this.collectionId);4121 }41224123 async removeSponsor(signer: TSigner) {4124 return await this.helper.collection.removeSponsor(signer, this.collectionId);4125 }41264127 async setLimits(signer: TSigner, limits: ICollectionLimits) {4128 return await this.helper.collection.setLimits(signer, this.collectionId, limits);4129 }41304131 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {4132 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);4133 }41344135 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {4136 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);4137 }41384139 async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {4140 return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);4141 }41424143 async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {4144 return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);4145 }41464147 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {4148 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);4149 }41504151 async setProperties(signer: TSigner, properties: IProperty[]) {4152 return await this.helper.collection.setProperties(signer, this.collectionId, properties);4153 }41544155 async deleteProperties(signer: TSigner, propertyKeys: string[]) {4156 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);4157 }41584159 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {4160 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);4161 }41624163 async enableNesting(signer: TSigner, permissions: INestingPermissions) {4164 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);4165 }41664167 async disableNesting(signer: TSigner) {4168 return await this.helper.collection.disableNesting(signer, this.collectionId);4169 }41704171 async burn(signer: TSigner) {4172 return await this.helper.collection.burn(signer, this.collectionId);4173 }41744175 scheduleAt<T extends UniqueHelper>(4176 executionBlockNumber: number,4177 options: ISchedulerOptions = {},4178 ) {4179 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4180 return new UniqueBaseCollection(this.collectionId, scheduledHelper);4181 }41824183 scheduleAfter<T extends UniqueHelper>(4184 blocksBeforeExecution: number,4185 options: ISchedulerOptions = {},4186 ) {4187 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4188 return new UniqueBaseCollection(this.collectionId, scheduledHelper);4189 }41904191 getSudo<T extends UniqueHelper>() {4192 return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());4193 }4194}419541964197export class UniqueNFTCollection extends UniqueBaseCollection {4198 getTokenObject(tokenId: number) {4199 return new UniqueNFToken(tokenId, this);4200 }42014202 async getTokensByAddress(addressObj: ICrossAccountId) {4203 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);4204 }42054206 async getToken(tokenId: number, blockHashAt?: string) {4207 return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);4208 }42094210 async getTokenOwner(tokenId: number, blockHashAt?: string) {4211 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);4212 }42134214 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {4215 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);4216 }42174218 async getTokenChildren(tokenId: number, blockHashAt?: string) {4219 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);4220 }42214222 async getPropertyPermissions(propertyKeys: string[] | null = null) {4223 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);4224 }42254226 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {4227 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);4228 }42294230 async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {4231 const api = this.helper.getApi();4232 const props = (await api.query.nonfungible.tokenProperties(this.collectionId, tokenId)).toJSON();42334234 return (props! as any).consumedSpace;4235 }42364237 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {4238 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);4239 }42404241 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4242 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);4243 }42444245 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {4246 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);4247 }42484249 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {4250 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);4251 }42524253 async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {4254 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});4255 }42564257 async mintMultipleTokens(signer: TSigner, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]) {4258 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);4259 }42604261 async burnToken(signer: TSigner, tokenId: number) {4262 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);4263 }42644265 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {4266 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);4267 }42684269 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {4270 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);4271 }42724273 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {4274 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);4275 }42764277 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {4278 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);4279 }42804281 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {4282 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);4283 }42844285 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4286 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);4287 }42884289 scheduleAt<T extends UniqueHelper>(4290 executionBlockNumber: number,4291 options: ISchedulerOptions = {},4292 ) {4293 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4294 return new UniqueNFTCollection(this.collectionId, scheduledHelper);4295 }42964297 scheduleAfter<T extends UniqueHelper>(4298 blocksBeforeExecution: number,4299 options: ISchedulerOptions = {},4300 ) {4301 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4302 return new UniqueNFTCollection(this.collectionId, scheduledHelper);4303 }43044305 getSudo<T extends UniqueHelper>() {4306 return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());4307 }4308}430943104311export class UniqueRFTCollection extends UniqueBaseCollection {4312 getTokenObject(tokenId: number) {4313 return new UniqueRFToken(tokenId, this);4314 }43154316 async getToken(tokenId: number, blockHashAt?: string) {4317 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);4318 }43194320 async getTokenOwner(tokenId: number, blockHashAt?: string) {4321 return await this.helper.rft.getTokenOwner(this.collectionId, tokenId, blockHashAt);4322 }43234324 async getTokensByAddress(addressObj: ICrossAccountId) {4325 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);4326 }43274328 async getTop10TokenOwners(tokenId: number) {4329 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);4330 }43314332 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {4333 return await this.helper.rft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);4334 }43354336 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {4337 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);4338 }43394340 async getTokenTotalPieces(tokenId: number) {4341 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);4342 }43434344 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4345 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);4346 }43474348 async getPropertyPermissions(propertyKeys: string[] | null = null) {4349 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);4350 }43514352 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {4353 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);4354 }43554356 async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {4357 const api = this.helper.getApi();4358 const props = (await api.query.refungible.tokenProperties(this.collectionId, tokenId)).toJSON();43594360 return (props! as any).consumedSpace;4361 }43624363 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount = 1n) {4364 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);4365 }43664367 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4368 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);4369 }43704371 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {4372 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);4373 }43744375 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {4376 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);4377 }43784379 async mintToken(signer: TSigner, pieces = 1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {4380 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});4381 }43824383 async mintMultipleTokens(signer: TSigner, tokens: { pieces: bigint, owner: ICrossAccountId, properties?: IProperty[] }[]) {4384 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);4385 }43864387 async burnToken(signer: TSigner, tokenId: number, amount = 1n) {4388 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);4389 }43904391 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n) {4392 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);4393 }43944395 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {4396 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);4397 }43984399 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {4400 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);4401 }44024403 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {4404 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);4405 }44064407 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {4408 return await this.helper.rft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);4409 }44104411 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4412 return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);4413 }44144415 scheduleAt<T extends UniqueHelper>(4416 executionBlockNumber: number,4417 options: ISchedulerOptions = {},4418 ) {4419 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4420 return new UniqueRFTCollection(this.collectionId, scheduledHelper);4421 }44224423 scheduleAfter<T extends UniqueHelper>(4424 blocksBeforeExecution: number,4425 options: ISchedulerOptions = {},4426 ) {4427 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4428 return new UniqueRFTCollection(this.collectionId, scheduledHelper);4429 }44304431 getSudo<T extends UniqueHelper>() {4432 return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());4433 }4434}443544364437export class UniqueFTCollection extends UniqueBaseCollection {4438 async getBalance(addressObj: ICrossAccountId) {4439 return await this.helper.ft.getBalance(this.collectionId, addressObj);4440 }44414442 async getTotalPieces() {4443 return await this.helper.ft.getTotalPieces(this.collectionId);4444 }44454446 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4447 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);4448 }44494450 async getTop10Owners() {4451 return await this.helper.ft.getTop10Owners(this.collectionId);4452 }44534454 async mint(signer: TSigner, amount = 1n, owner: ICrossAccountId = {Substrate: signer.address}) {4455 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);4456 }44574458 async mintWithOneOwner(signer: TSigner, tokens: { value: bigint }[], owner: ICrossAccountId = {Substrate: signer.address}) {4459 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);4460 }44614462 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4463 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);4464 }44654466 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4467 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);4468 }44694470 async burnTokens(signer: TSigner, amount = 1n) {4471 return await this.helper.ft.burnTokens(signer, this.collectionId, amount);4472 }44734474 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {4475 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);4476 }44774478 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4479 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);4480 }44814482 scheduleAt<T extends UniqueHelper>(4483 executionBlockNumber: number,4484 options: ISchedulerOptions = {},4485 ) {4486 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4487 return new UniqueFTCollection(this.collectionId, scheduledHelper);4488 }44894490 scheduleAfter<T extends UniqueHelper>(4491 blocksBeforeExecution: number,4492 options: ISchedulerOptions = {},4493 ) {4494 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4495 return new UniqueFTCollection(this.collectionId, scheduledHelper);4496 }44974498 getSudo<T extends UniqueHelper>() {4499 return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());4500 }4501}450245034504export class UniqueBaseToken {4505 collection: UniqueNFTCollection | UniqueRFTCollection;4506 collectionId: number;4507 tokenId: number;45084509 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {4510 this.collection = collection;4511 this.collectionId = collection.collectionId;4512 this.tokenId = tokenId;4513 }45144515 async getNextSponsored(addressObj: ICrossAccountId) {4516 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);4517 }45184519 async getProperties(propertyKeys?: string[] | null) {4520 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);4521 }45224523 async getTokenPropertiesConsumedSpace() {4524 return await this.collection.getTokenPropertiesConsumedSpace(this.tokenId);4525 }45264527 async setProperties(signer: TSigner, properties: IProperty[]) {4528 return await this.collection.setTokenProperties(signer, this.tokenId, properties);4529 }45304531 async deleteProperties(signer: TSigner, propertyKeys: string[]) {4532 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);4533 }45344535 async doesExist() {4536 return await this.collection.doesTokenExist(this.tokenId);4537 }45384539 nestingAccount() {4540 return this.collection.helper.util.getTokenAccount(this);4541 }45424543 scheduleAt<T extends UniqueHelper>(4544 executionBlockNumber: number,4545 options: ISchedulerOptions = {},4546 ) {4547 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4548 return new UniqueBaseToken(this.tokenId, scheduledCollection);4549 }45504551 scheduleAfter<T extends UniqueHelper>(4552 blocksBeforeExecution: number,4553 options: ISchedulerOptions = {},4554 ) {4555 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4556 return new UniqueBaseToken(this.tokenId, scheduledCollection);4557 }45584559 getSudo<T extends UniqueHelper>() {4560 return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());4561 }4562}456345644565export class UniqueNFToken extends UniqueBaseToken {4566 collection: UniqueNFTCollection;45674568 constructor(tokenId: number, collection: UniqueNFTCollection) {4569 super(tokenId, collection);4570 this.collection = collection;4571 }45724573 async getData(blockHashAt?: string) {4574 return await this.collection.getToken(this.tokenId, blockHashAt);4575 }45764577 async getOwner(blockHashAt?: string) {4578 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);4579 }45804581 async getTopmostOwner(blockHashAt?: string) {4582 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);4583 }45844585 async getChildren(blockHashAt?: string) {4586 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);4587 }45884589 async nest(signer: TSigner, toTokenObj: IToken) {4590 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);4591 }45924593 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4594 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);4595 }45964597 async transfer(signer: TSigner, addressObj: ICrossAccountId) {4598 return await this.collection.transferToken(signer, this.tokenId, addressObj);4599 }46004601 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4602 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);4603 }46044605 async approve(signer: TSigner, toAddressObj: ICrossAccountId) {4606 return await this.collection.approveToken(signer, this.tokenId, toAddressObj);4607 }46084609 async isApproved(toAddressObj: ICrossAccountId) {4610 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);4611 }46124613 async burn(signer: TSigner) {4614 return await this.collection.burnToken(signer, this.tokenId);4615 }46164617 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {4618 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);4619 }46204621 scheduleAt<T extends UniqueHelper>(4622 executionBlockNumber: number,4623 options: ISchedulerOptions = {},4624 ) {4625 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4626 return new UniqueNFToken(this.tokenId, scheduledCollection);4627 }46284629 scheduleAfter<T extends UniqueHelper>(4630 blocksBeforeExecution: number,4631 options: ISchedulerOptions = {},4632 ) {4633 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4634 return new UniqueNFToken(this.tokenId, scheduledCollection);4635 }46364637 getSudo<T extends UniqueHelper>() {4638 return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());4639 }4640}46414642export class UniqueRFToken extends UniqueBaseToken {4643 collection: UniqueRFTCollection;46444645 constructor(tokenId: number, collection: UniqueRFTCollection) {4646 super(tokenId, collection);4647 this.collection = collection;4648 }46494650 async getData(blockHashAt?: string) {4651 return await this.collection.getToken(this.tokenId, blockHashAt);4652 }46534654 async getOwner(blockHashAt?: string) {4655 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);4656 }46574658 async getTop10Owners() {4659 return await this.collection.getTop10TokenOwners(this.tokenId);4660 }46614662 async getTopmostOwner(blockHashAt?: string) {4663 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);4664 }46654666 async nest(signer: TSigner, toTokenObj: IToken) {4667 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);4668 }46694670 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4671 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);4672 }46734674 async getBalance(addressObj: ICrossAccountId) {4675 return await this.collection.getTokenBalance(this.tokenId, addressObj);4676 }46774678 async getTotalPieces() {4679 return await this.collection.getTokenTotalPieces(this.tokenId);4680 }46814682 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {4683 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);4684 }46854686 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount = 1n) {4687 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);4688 }46894690 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4691 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);4692 }46934694 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4695 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);4696 }46974698 async repartition(signer: TSigner, amount: bigint) {4699 return await this.collection.repartitionToken(signer, this.tokenId, amount);4700 }47014702 async burn(signer: TSigner, amount = 1n) {4703 return await this.collection.burnToken(signer, this.tokenId, amount);4704 }47054706 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {4707 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);4708 }47094710 scheduleAt<T extends UniqueHelper>(4711 executionBlockNumber: number,4712 options: ISchedulerOptions = {},4713 ) {4714 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4715 return new UniqueRFToken(this.tokenId, scheduledCollection);4716 }47174718 scheduleAfter<T extends UniqueHelper>(4719 blocksBeforeExecution: number,4720 options: ISchedulerOptions = {},4721 ) {4722 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4723 return new UniqueRFToken(this.tokenId, scheduledCollection);4724 }47254726 getSudo<T extends UniqueHelper>() {4727 return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());4728 }4729}