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}1// 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 IPhasicEvent,48} from './types';49import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';50import type {Vec} from '@polkadot/types-codec';51import {FrameSystemEventRecord, PalletDemocracyConviction} from '@polkadot/types/lookup';5253export class CrossAccountId {54 Substrate!: TSubstrateAccount;55 Ethereum!: TEthereumAccount;5657 constructor(account: ICrossAccountId) {58 if('Substrate' in account) this.Substrate = account.Substrate;59 else this.Ethereum = account.Ethereum;60 }6162 static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {63 switch (domain) {64 case 'Substrate': return new CrossAccountId({Substrate: account.address});65 case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();66 }67 }6869 static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {70 if('substrate' in address) return new CrossAccountId({Substrate: address.substrate});71 else return new CrossAccountId({Ethereum: address.ethereum});72 }7374 static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {75 return encodeAddress(decodeAddress(address), ss58Format);76 }7778 static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId {79 return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)});80 }8182 withNormalizedSubstrate(ss58Format = 42): CrossAccountId {83 if(this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format);84 return this;85 }8687 static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {88 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));89 }9091 toEthereum(): CrossAccountId {92 if(this.Substrate) return new CrossAccountId({Ethereum: CrossAccountId.translateSubToEth(this.Substrate)});93 return this;94 }9596 static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {97 return evmToAddress(address, ss58Format);98 }99100 toSubstrate(ss58Format?: number): CrossAccountId {101 if(this.Ethereum) return new CrossAccountId({Substrate: CrossAccountId.translateEthToSub(this.Ethereum, ss58Format)});102 return this;103 }104105 toLowerCase(): CrossAccountId {106 if(this.Substrate) this.Substrate = this.Substrate.toLowerCase();107 if(this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase();108 return this;109 }110}111112const nesting = {113 toChecksumAddress(address: string): string {114 if(typeof address === 'undefined') return '';115116 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);117118 address = address.toLowerCase().replace(/^0x/i, '');119 const addressHash = keccakAsHex(address).replace(/^0x/i, '');120 const checksumAddress = ['0x'];121122 for(let i = 0; i < address.length; i++) {123 // If ith character is 8 to f then make it uppercase124 if(parseInt(addressHash[i], 16) > 7) {125 checksumAddress.push(address[i].toUpperCase());126 } else {127 checksumAddress.push(address[i]);128 }129 }130 return checksumAddress.join('');131 },132 tokenIdToAddress(collectionId: number, tokenId: number) {133 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);134 },135};136137class UniqueUtil {138 static transactionStatus = {139 NOT_READY: 'NotReady',140 FAIL: 'Fail',141 SUCCESS: 'Success',142 };143144 static chainLogType = {145 EXTRINSIC: 'extrinsic',146 RPC: 'rpc',147 };148149 static getTokenAccount(token: IToken): CrossAccountId {150 return new CrossAccountId({Ethereum: this.getTokenAddress(token)});151 }152153 static getTokenAddress(token: IToken): string {154 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);155 }156157 static getDefaultLogger(): ILogger {158 return {159 log(msg: any, level = 'INFO') {160 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));161 },162 level: {163 ERROR: 'ERROR',164 WARNING: 'WARNING',165 INFO: 'INFO',166 },167 };168 }169170 static vec2str(arr: string[] | number[]) {171 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');172 }173174 static str2vec(string: string) {175 if(typeof string !== 'string') return string;176 return Array.from(string).map(x => x.charCodeAt(0));177 }178179 static fromSeed(seed: string, ss58Format = 42) {180 const keyring = new Keyring({type: 'sr25519', ss58Format});181 return keyring.addFromUri(seed);182 }183184 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {185 if(creationResult.status !== this.transactionStatus.SUCCESS) {186 throw Error('Unable to create collection!');187 }188189 let collectionId = null;190 creationResult.result.events.forEach(({event: {data, method, section}}) => {191 if((section === 'common') && (method === 'CollectionCreated')) {192 collectionId = parseInt(data[0].toString(), 10);193 }194 });195196 if(collectionId === null) {197 throw Error('No CollectionCreated event was found!');198 }199200 return collectionId;201 }202203 static extractTokensFromCreationResult(creationResult: ITransactionResult): {204 success: boolean,205 tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],206 } {207 if(creationResult.status !== this.transactionStatus.SUCCESS) {208 throw Error('Unable to create tokens!');209 }210 let success = false;211 const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];212 creationResult.result.events.forEach(({event: {data, method, section}}) => {213 if(method === 'ExtrinsicSuccess') {214 success = true;215 } else if((section === 'common') && (method === 'ItemCreated')) {216 tokens.push({217 collectionId: parseInt(data[0].toString(), 10),218 tokenId: parseInt(data[1].toString(), 10),219 owner: data[2].toHuman(),220 amount: data[3].toBigInt(),221 });222 }223 });224 return {success, tokens};225 }226227 static extractTokensFromBurnResult(burnResult: ITransactionResult): {228 success: boolean,229 tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],230 } {231 if(burnResult.status !== this.transactionStatus.SUCCESS) {232 throw Error('Unable to burn tokens!');233 }234 let success = false;235 const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];236 burnResult.result.events.forEach(({event: {data, method, section}}) => {237 if(method === 'ExtrinsicSuccess') {238 success = true;239 } else if((section === 'common') && (method === 'ItemDestroyed')) {240 tokens.push({241 collectionId: parseInt(data[0].toString(), 10),242 tokenId: parseInt(data[1].toString(), 10),243 owner: data[2].toHuman(),244 amount: data[3].toBigInt(),245 });246 }247 });248 return {success, tokens};249 }250251 static findCollectionInEvents(events: { event: IEvent }[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {252 let eventId = null;253 events.forEach(({event: {data, method, section}}) => {254 if((section === expectedSection) && (method === expectedMethod)) {255 eventId = parseInt(data[0].toString(), 10);256 }257 });258259 if(eventId === null) {260 throw Error(`No ${expectedMethod} event was found!`);261 }262 return eventId === collectionId;263 }264265 static isTokenTransferSuccess(events: { event: IEvent }[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {266 const normalizeAddress = (address: string | ICrossAccountId) => {267 if(typeof address === 'string') return address;268 const obj = {} as any;269 Object.keys(address).forEach(k => {270 obj[k.toLocaleLowerCase()] = (address as any)[k];271 });272 if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate);273 if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase();274 return address;275 };276 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;277 events.forEach(({event: {data, method, section}}) => {278 if((section === 'common') && (method === 'Transfer')) {279 const hData = (data as any).toJSON();280 transfer = {281 collectionId: hData[0],282 tokenId: hData[1],283 from: normalizeAddress(hData[2]),284 to: normalizeAddress(hData[3]),285 amount: BigInt(hData[4]),286 };287 }288 });289 let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;290 isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);291 isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);292 isSuccess = isSuccess && amount === transfer.amount;293 return isSuccess;294 }295296 static bigIntToDecimals(number: bigint, decimals = 18) {297 const numberStr = number.toString();298 const dotPos = numberStr.length - decimals;299300 if(dotPos <= 0) {301 return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr;302 } else {303 const intPart = numberStr.substring(0, dotPos);304 const fractPart = numberStr.substring(dotPos);305 return intPart + '.' + fractPart;306 }307 }308}309310class UniqueEventHelper {311 private static extractIndex(index: any): [number, number] | string {312 if(index.toRawType() === '[u8;2]') return [index[0], index[1]];313 return index.toJSON();314 }315316 private static extractSub(data: any, subTypes: any): { [key: string]: any } {317 let obj: any = {};318 let index = 0;319320 if(data.entries) {321 for(const [key, value] of data.entries()) {322 obj[key] = this.extractData(value, subTypes[index]);323 index++;324 }325 } else obj = data.toJSON();326327 return obj;328 }329330 private static toHuman(data: any) {331 return data && data.toHuman ? data.toHuman() : `${data}`;332 }333334 private static extractData(data: any, type: any): any {335 if(!type) return this.toHuman(data);336 if(['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber();337 if(['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt();338 if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub);339 return this.toHuman(data);340 }341342 public static extractEvents(events: { event: any, phase: any }[]): IEvent[] {343 const parsedEvents: IEvent[] = [];344345 events.forEach((record) => {346 const {event, phase} = record;347 const types = event.typeDef;348349 const eventData: IEvent = {350 section: event.section.toString(),351 method: event.method.toString(),352 index: this.extractIndex(event.index),353 data: [],354 phase: phase.toJSON(),355 };356357 event.data.forEach((val: any, index: number) => {358 eventData.data.push(this.extractData(val, types[index]));359 });360361 parsedEvents.push(eventData);362 });363364 return parsedEvents;365 }366}367const InvalidTypeSymbol = Symbol('Invalid type');368// eslint-disable-next-line @typescript-eslint/no-unused-vars369export type Invalid<ErrorMessage> =370 | ((371 invalidType: typeof InvalidTypeSymbol,372 ..._: typeof InvalidTypeSymbol[]373 ) => typeof InvalidTypeSymbol)374 | null375 | undefined;376// Has slightly better error messages than Get377type Get2<T, P extends string, E> =378 P extends `${infer Key}.${infer Key2}` ? Key extends keyof T ? Key2 extends keyof T[Key] ? T[Key][Key2] : E : E : E;379type ForceFunction<T> = T extends (...args: any) => any ? T : (...args: any) => Invalid<'not a function'>;380381export class ChainHelperBase {382 helperBase: any;383384 transactionStatus = UniqueUtil.transactionStatus;385 chainLogType = UniqueUtil.chainLogType;386 util: typeof UniqueUtil;387 eventHelper: typeof UniqueEventHelper;388 logger: ILogger;389 api: ApiPromise | null;390 forcedNetwork: TNetworks | null;391 network: TNetworks | null;392 wsEndpoint: string | null;393 chainLog: IUniqueHelperLog[];394 children: ChainHelperBase[];395 address: AddressGroup;396 chain: ChainGroup;397398 constructor(logger?: ILogger, helperBase?: any) {399 this.helperBase = helperBase;400401 this.util = UniqueUtil;402 this.eventHelper = UniqueEventHelper;403 if(typeof logger == 'undefined') logger = this.util.getDefaultLogger();404 this.logger = logger;405 this.api = null;406 this.forcedNetwork = null;407 this.network = null;408 this.wsEndpoint = null;409 this.chainLog = [];410 this.children = [];411 this.address = new AddressGroup(this);412 this.chain = new ChainGroup(this);413 }414415 clone(helperCls: ChainHelperBaseConstructor, options: { [key: string]: any } = {}) {416 Object.setPrototypeOf(helperCls.prototype, this);417 const newHelper = new helperCls(this.logger, options);418419 newHelper.api = this.api;420 newHelper.network = this.network;421 newHelper.forceNetwork = this.forceNetwork;422423 this.children.push(newHelper);424425 return newHelper;426 }427428 getEndpoint(): string {429 if(this.wsEndpoint === null) throw Error('No connection was established');430 return this.wsEndpoint;431 }432433 getApi(): ApiPromise {434 if(this.api === null) throw Error('API not initialized');435 return this.api;436 }437438 async subscribeEvents(expectedEvents: { section: string, names: string[] }[]) {439 const collectedEvents: IEvent[] = [];440 const unsubscribe = await this.getApi().query.system.events((events: Vec<FrameSystemEventRecord>) => {441 const ievents = this.eventHelper.extractEvents(events);442 ievents.forEach((event) => {443 expectedEvents.forEach((e => {444 if(event.section === e.section && e.names.includes(event.method)) {445 collectedEvents.push(event);446 }447 }));448 });449 });450 return {unsubscribe: unsubscribe as any, collectedEvents};451 }452453 clearChainLog(): void {454 this.chainLog = [];455 }456457 forceNetwork(value: TNetworks): void {458 this.forcedNetwork = value;459 }460461 async connect(wsEndpoint: string, listeners?: IApiListeners) {462 if(this.api !== null) throw Error('Already connected');463 const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);464 this.wsEndpoint = wsEndpoint;465 this.api = api;466 this.network = network;467 }468469 async disconnect() {470 for(const child of this.children) {471 child.clearApi();472 }473474 if(this.api === null) return;475 await this.api.disconnect();476 this.clearApi();477 }478479 clearApi() {480 this.api = null;481 this.network = null;482 }483484 static async detectNetwork(api: ApiPromise): Promise<TNetworks> {485 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;486 const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];487488 if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;489490 if(['quartz', 'unique', 'sapphire'].indexOf(spec.specName) > -1) return spec.specName;491 return 'opal';492 }493494 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {495 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});496 await api.isReady;497498 const network = await this.detectNetwork(api);499500 await api.disconnect();501502 return network;503 }504505 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{506 api: ApiPromise;507 network: TNetworks;508 }> {509 if(typeof network === 'undefined' || network === null) network = 'opal';510 const supportedRPC = {511 opal: {512 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,513 },514 quartz: {515 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,516 },517 unique: {518 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,519 },520 rococo: {},521 westend: {},522 moonbeam: {},523 moonriver: {},524 acala: {},525 karura: {},526 westmint: {},527 };528 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);529 const rpc = supportedRPC[network];530531 // TODO: investigate how to replace rpc in runtime532 // api._rpcCore.addUserInterfaces(rpc);533534 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});535536 await api.isReadyOrError;537538 if(typeof listeners === 'undefined') listeners = {};539 for(const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {540 if(!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;541 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);542 }543544 return {api, network};545 }546547 getTransactionStatus(data: { events: { event: IEvent }[], status: any }) {548 const {events, status} = data;549 if(status.isReady) {550 return this.transactionStatus.NOT_READY;551 }552 if(status.isBroadcast) {553 return this.transactionStatus.NOT_READY;554 }555 if(status.isInBlock || status.isFinalized) {556 const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');557 if(errors.length > 0) {558 return this.transactionStatus.FAIL;559 }560 if(events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {561 return this.transactionStatus.SUCCESS;562 }563 }564565 return this.transactionStatus.FAIL;566 }567568 signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {569 const sign = (callback: any) => {570 if(options !== null) return transaction.signAndSend(sender, options, callback);571 return transaction.signAndSend(sender, callback);572 };573 // eslint-disable-next-line no-async-promise-executor574 return new Promise(async (resolve, reject) => {575 try {576 const unsub = await sign((result: any) => {577 const status = this.getTransactionStatus(result);578579 if(status === this.transactionStatus.SUCCESS) {580 this.logger.log(`${label} successful`);581 unsub();582 resolve({result, status, blockHash: result.status.asInBlock.toHuman()});583 } else if(status === this.transactionStatus.FAIL) {584 let moduleError = null;585586 if(result.hasOwnProperty('dispatchError')) {587 const dispatchError = result['dispatchError'];588589 if(dispatchError) {590 if(dispatchError.isModule) {591 const modErr = dispatchError.asModule;592 const errorMeta = dispatchError.registry.findMetaError(modErr);593594 moduleError = `${errorMeta.section}.${errorMeta.name}`;595 } else if(dispatchError.isToken) {596 moduleError = `Token: ${dispatchError.asToken}`;597 } else {598 // May be [object Object] in case of unhandled non-unit enum599 moduleError = `Misc: ${dispatchError.toHuman()}`;600 }601 } else {602 this.logger.log(result, this.logger.level.ERROR);603 }604 }605606 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);607 unsub();608 reject({status, moduleError, result});609 }610 });611 } catch (e) {612 this.logger.log(e, this.logger.level.ERROR);613 reject(e);614 }615 });616 }617618 async signTransactionWithoutSending(signer: TSigner, tx: any) {619 const api = this.getApi();620 const signingInfo = await api.derive.tx.signingInfo(signer.address);621622 tx.sign(signer, {623 blockHash: api.genesisHash,624 genesisHash: api.genesisHash,625 runtimeVersion: api.runtimeVersion,626 nonce: signingInfo.nonce,627 });628629 return tx.toHex();630 }631632 async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {633 const api = this.getApi();634 const signingInfo = await api.derive.tx.signingInfo(signer.address);635636 // We need to sign the tx because637 // unsigned transactions does not have an inclusion fee638 tx.sign(signer, {639 blockHash: api.genesisHash,640 genesisHash: api.genesisHash,641 runtimeVersion: api.runtimeVersion,642 nonce: signingInfo.nonce,643 });644645 if(len === null) {646 return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;647 } else {648 return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;649 }650 }651652 constructApiCall(apiCall: string, params: any[]) {653 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);654 let call = this.getApi() as any;655 for(const part of apiCall.slice(4).split('.')) {656 call = call[part];657 if(!call) {658 const advice = part.includes('_') ? ' Looks like it needs to be converted to camel case.' : '';659 throw Error(`Function ${part} of api call ${apiCall} not found.${advice}`);660 }661 }662 return call(...params);663 }664665 encodeApiCall(apiCall: string, params: any[]) {666 return this.constructApiCall(apiCall, params).method.toHex();667 }668669 async executeExtrinsic<670 E extends string,671 V extends (672 ...args: any) => any = ForceFunction<673 Get2<674 AugmentedSubmittables<'promise'>,675 E, (...args: any) => Invalid<'not found'>676 >677 >678 >(679 sender: TSigner,680 extrinsic: `api.tx.${E}`,681 params: Parameters<V>,682 expectSuccess = true,683 options: Partial<SignerOptions> | null = null,/*, failureMessage='expected success'*/684 ): Promise<ITransactionResult> {685 if(this.api === null) throw Error('API not initialized');686687 const startTime = (new Date()).getTime();688 let result: ITransactionResult;689 let events: IEvent[] = [];690 try {691 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;692 events = this.eventHelper.extractEvents(result.result.events);693 const errorEvent = events.find((event) => event.method == 'ExecutedFailed' || event.method == 'CreatedFailed');694 if(errorEvent)695 throw Error(errorEvent.method + ': ' + extrinsic);696 }697 catch (e) {698 if(!(e as object).hasOwnProperty('status')) throw e;699 result = e as ITransactionResult;700 }701702 const endTime = (new Date()).getTime();703704 const log = {705 executedAt: endTime,706 executionTime: endTime - startTime,707 type: this.chainLogType.EXTRINSIC,708 status: result.status,709 call: extrinsic,710 signer: this.getSignerAddress(sender),711 params,712 } as IUniqueHelperLog;713714 let errorMessage = '';715716 if(result.status !== this.transactionStatus.SUCCESS) {717 if(result.moduleError) {718 errorMessage = typeof result.moduleError === 'string'719 ? result.moduleError720 : `${Object.keys(result.moduleError)[0]}: ${Object.values(result.moduleError)[0]}`;721 log.moduleError = errorMessage;722 }723 else if(result.result.dispatchError) log.dispatchError = result.result.dispatchError;724 }725 if(events.length > 0) log.events = events;726727 this.chainLog.push(log);728729 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {730 if(result.moduleError) throw Error(`${errorMessage}`);731 else if(result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));732 }733 return result as any;734 }735736 async callRpc737 // TODO: make it strongly typed, or use api.query/api.rpc directly738 // <739 // K extends 'rpc' | 'query',740 // E extends string,741 // V extends (...args: any) => any = ForceFunction<742 // Get2<743 // K extends 'rpc' ? DecoratedRpc<'promise', RpcInterface> : QueryableStorage<'promise'>,744 // E, (...args: any) => Invalid<'not found'>745 // >746 // >,747 // P = Parameters<V>,748 // >749 (rpc: string, params?: any[]): Promise<any> {750751 if(typeof params === 'undefined') params = [] as any;752 if(this.api === null) throw Error('API not initialized');753 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);754755 const startTime = (new Date()).getTime();756 let result;757 let error = null;758 const log = {759 type: this.chainLogType.RPC,760 call: rpc,761 params,762 } as any as IUniqueHelperLog;763764 try {765 result = await this.constructApiCall(rpc, params as any);766 }767 catch (e) {768 error = e;769 }770771 const endTime = (new Date()).getTime();772773 log.executedAt = endTime;774 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';775 log.executionTime = endTime - startTime;776777 this.chainLog.push(log);778779 if(error !== null) throw error;780781 return result;782 }783784 getSignerAddress(signer: IKeyringPair | string): string {785 if(typeof signer === 'string') return signer;786 return signer.address;787 }788789 fetchAllPalletNames(): string[] {790 if(this.api === null) throw Error('API not initialized');791 return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()).sort();792 }793794 fetchMissingPalletNames(requiredPallets: readonly string[]): string[] {795 const palletNames = this.fetchAllPalletNames();796 return requiredPallets.filter(p => !palletNames.includes(p));797 }798}799800801class HelperGroup<T extends ChainHelperBase> {802 helper: T;803804 constructor(uniqueHelper: T) {805 this.helper = uniqueHelper;806 }807}808809810class CollectionGroup extends HelperGroup<UniqueHelper> {811 /**812 * Get number of blocks when sponsored transaction is available.813 *814 * @param collectionId ID of collection815 * @param tokenId ID of token816 * @param addressObj address for which the sponsorship is checked817 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});818 * @returns number of blocks or null if sponsorship hasn't been set819 */820 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {821 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();822 }823824 /**825 * Get the number of created collections.826 *827 * @returns number of created collections828 */829 async getTotalCount(): Promise<number> {830 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();831 }832833 /**834 * Get information about the collection with additional data,835 * including the number of tokens it contains, its administrators,836 * the normalized address of the collection's owner, and decoded name and description.837 *838 * @param collectionId ID of collection839 * @example await getData(2)840 * @returns collection information object841 */842 async getData(collectionId: number): Promise<{843 id: number;844 name: string;845 description: string;846 tokensCount: number;847 admins: CrossAccountId[];848 normalizedOwner: TSubstrateAccount;849 raw: any850 } | null> {851 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);852 const humanCollection = collection.toHuman(), collectionData = {853 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],854 raw: humanCollection,855 } as any, jsonCollection = collection.toJSON();856 if(humanCollection === null) return null;857 collectionData.raw.limits = jsonCollection.limits;858 collectionData.raw.permissions = jsonCollection.permissions;859 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);860 for(const key of ['name', 'description']) {861 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);862 }863864 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))865 ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)866 : 0;867 collectionData.admins = await this.getAdmins(collectionId);868869 return collectionData;870 }871872 /**873 * Get the addresses of the collection's administrators, optionally normalized.874 *875 * @param collectionId ID of collection876 * @param normalize whether to normalize the addresses to the default ss58 format877 * @example await getAdmins(1)878 * @returns array of administrators879 */880 async getAdmins(collectionId: number, normalize = false): Promise<CrossAccountId[]> {881 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman();882883 return normalize884 ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate())885 : admins;886 }887888 /**889 * Get the addresses added to the collection allow-list, optionally normalized.890 * @param collectionId ID of collection891 * @param normalize whether to normalize the addresses to the default ss58 format892 * @example await getAllowList(1)893 * @returns array of allow-listed addresses894 */895 async getAllowList(collectionId: number, normalize = false): Promise<CrossAccountId[]> {896 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman();897 return normalize898 ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate())899 : allowListed;900 }901902 /**903 * Get the effective limits of the collection instead of null for default values904 *905 * @param collectionId ID of collection906 * @example await getEffectiveLimits(2)907 * @returns object of collection limits908 */909 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {910 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();911 }912913 /**914 * Burns the collection if the signer has sufficient permissions and collection is empty.915 *916 * @param signer keyring of signer917 * @param collectionId ID of collection918 * @example await helper.collection.burn(aliceKeyring, 3);919 * @returns ```true``` if extrinsic success, otherwise ```false```920 */921 async burn(signer: TSigner, collectionId: number): Promise<boolean> {922 const result = await this.helper.executeExtrinsic(923 signer,924 'api.tx.unique.destroyCollection', [collectionId],925 true,926 );927928 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');929 }930931 /**932 * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.933 *934 * @param signer keyring of signer935 * @param collectionId ID of collection936 * @param sponsorAddress Sponsor substrate address937 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")938 * @returns ```true``` if extrinsic success, otherwise ```false```939 */940 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {941 const result = await this.helper.executeExtrinsic(942 signer,943 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],944 true,945 );946947 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorSet');948 }949950 /**951 * Confirms consent to sponsor the collection on behalf of the signer.952 *953 * @param signer keyring of signer954 * @param collectionId ID of collection955 * @example confirmSponsorship(aliceKeyring, 10)956 * @returns ```true``` if extrinsic success, otherwise ```false```957 */958 async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {959 const result = await this.helper.executeExtrinsic(960 signer,961 'api.tx.unique.confirmSponsorship', [collectionId],962 true,963 );964965 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'SponsorshipConfirmed');966 }967968 /**969 * Removes the sponsor of a collection, regardless if it consented or not.970 *971 * @param signer keyring of signer972 * @param collectionId ID of collection973 * @example removeSponsor(aliceKeyring, 10)974 * @returns ```true``` if extrinsic success, otherwise ```false```975 */976 async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {977 const result = await this.helper.executeExtrinsic(978 signer,979 'api.tx.unique.removeCollectionSponsor', [collectionId],980 true,981 );982983 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorRemoved');984 }985986 /**987 * Sets the limits of the collection. At least one limit must be specified for a correct call.988 *989 * @param signer keyring of signer990 * @param collectionId ID of collection991 * @param limits collection limits object992 * @example993 * await setLimits(994 * aliceKeyring,995 * 10,996 * {997 * sponsorTransferTimeout: 0,998 * ownerCanDestroy: false999 * }1000 * )1001 * @returns ```true``` if extrinsic success, otherwise ```false```1002 */1003 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {1004 const result = await this.helper.executeExtrinsic(1005 signer,1006 'api.tx.unique.setCollectionLimits', [collectionId, limits],1007 true,1008 );10091010 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionLimitSet');1011 }10121013 /**1014 * Changes the owner of the collection to the new Substrate address.1015 *1016 * @param signer keyring of signer1017 * @param collectionId ID of collection1018 * @param ownerAddress substrate address of new owner1019 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")1020 * @returns ```true``` if extrinsic success, otherwise ```false```1021 */1022 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {1023 const result = await this.helper.executeExtrinsic(1024 signer,1025 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],1026 true,1027 );10281029 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionOwnerChanged');1030 }10311032 /**1033 * Adds a collection administrator.1034 *1035 * @param signer keyring of signer1036 * @param collectionId ID of collection1037 * @param adminAddressObj Administrator address (substrate or ethereum)1038 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1039 * @returns ```true``` if extrinsic success, otherwise ```false```1040 */1041 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1042 const result = await this.helper.executeExtrinsic(1043 signer,1044 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],1045 true,1046 );10471048 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminAdded');1049 }10501051 /**1052 * Removes a collection administrator.1053 *1054 * @param signer keyring of signer1055 * @param collectionId ID of collection1056 * @param adminAddressObj Administrator address (substrate or ethereum)1057 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1058 * @returns ```true``` if extrinsic success, otherwise ```false```1059 */1060 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1061 const result = await this.helper.executeExtrinsic(1062 signer,1063 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],1064 true,1065 );10661067 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminRemoved');1068 }10691070 /**1071 * Check if user is in allow list.1072 *1073 * @param collectionId ID of collection1074 * @param user Account to check1075 * @example await getAdmins(1)1076 * @returns is user in allow list1077 */1078 async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {1079 return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();1080 }10811082 /**1083 * Adds an address to allow list1084 * @param signer keyring of signer1085 * @param collectionId ID of collection1086 * @param addressObj address to add to the allow list1087 * @returns ```true``` if extrinsic success, otherwise ```false```1088 */1089 async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1090 const result = await this.helper.executeExtrinsic(1091 signer,1092 'api.tx.unique.addToAllowList', [collectionId, addressObj],1093 true,1094 );10951096 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressAdded');1097 }10981099 /**1100 * Removes an address from allow list1101 *1102 * @param signer keyring of signer1103 * @param collectionId ID of collection1104 * @param addressObj address to remove from the allow list1105 * @returns ```true``` if extrinsic success, otherwise ```false```1106 */1107 async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1108 const result = await this.helper.executeExtrinsic(1109 signer,1110 'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1111 true,1112 );11131114 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressRemoved');1115 }11161117 /**1118 * Sets onchain permissions for selected collection.1119 *1120 * @param signer keyring of signer1121 * @param collectionId ID of collection1122 * @param permissions collection permissions object1123 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1124 * @returns ```true``` if extrinsic success, otherwise ```false```1125 */1126 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1127 const result = await this.helper.executeExtrinsic(1128 signer,1129 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1130 true,1131 );11321133 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPermissionSet');1134 }11351136 /**1137 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1138 *1139 * @param signer keyring of signer1140 * @param collectionId ID of collection1141 * @param permissions nesting permissions object1142 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1143 * @returns ```true``` if extrinsic success, otherwise ```false```1144 */1145 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1146 return await this.setPermissions(signer, collectionId, {nesting: permissions});1147 }11481149 /**1150 * Disables nesting for selected collection.1151 *1152 * @param signer keyring of signer1153 * @param collectionId ID of collection1154 * @example disableNesting(aliceKeyring, 10);1155 * @returns ```true``` if extrinsic success, otherwise ```false```1156 */1157 async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1158 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1159 }11601161 /**1162 * Sets onchain properties to the collection.1163 *1164 * @param signer keyring of signer1165 * @param collectionId ID of collection1166 * @param properties array of property objects1167 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1168 * @returns ```true``` if extrinsic success, otherwise ```false```1169 */1170 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1171 const result = await this.helper.executeExtrinsic(1172 signer,1173 'api.tx.unique.setCollectionProperties', [collectionId, properties],1174 true,1175 );11761177 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1178 }11791180 /**1181 * Get collection properties.1182 *1183 * @param collectionId ID of collection1184 * @param propertyKeys optionally filter the returned properties to only these keys1185 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1186 * @returns array of key-value pairs1187 */1188 async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1189 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1190 }11911192 async getPropertiesConsumedSpace(collectionId: number): Promise<number> {1193 const api = this.helper.getApi();1194 const props = (await api.query.common.collectionProperties(collectionId)).toJSON();11951196 return (props! as any).consumedSpace;1197 }11981199 async getCollectionOptions(collectionId: number) {1200 return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1201 }12021203 /**1204 * Deletes onchain properties from the collection.1205 *1206 * @param signer keyring of signer1207 * @param collectionId ID of collection1208 * @param propertyKeys array of property keys to delete1209 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1210 * @returns ```true``` if extrinsic success, otherwise ```false```1211 */1212 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1213 const result = await this.helper.executeExtrinsic(1214 signer,1215 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1216 true,1217 );12181219 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1220 }12211222 /**1223 * Changes the owner of the token.1224 *1225 * @param signer keyring of signer1226 * @param collectionId ID of collection1227 * @param tokenId ID of token1228 * @param addressObj address of a new owner1229 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1230 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1231 * @returns true if the token success, otherwise false1232 */1233 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1234 const result = await this.helper.executeExtrinsic(1235 signer,1236 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1237 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1238 );12391240 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1241 }12421243 /**1244 *1245 * Change ownership of a token(s) on behalf of the owner.1246 *1247 * @param signer keyring of signer1248 * @param collectionId ID of collection1249 * @param tokenId ID of token1250 * @param fromAddressObj address on behalf of which the token will be sent1251 * @param toAddressObj new token owner1252 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1253 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1254 * @returns true if the token success, otherwise false1255 */1256 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1257 const result = await this.helper.executeExtrinsic(1258 signer,1259 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1260 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1261 );1262 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1263 }12641265 /**1266 *1267 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1268 *1269 * @param signer keyring of signer1270 * @param collectionId ID of collection1271 * @param tokenId ID of token1272 * @param amount amount of tokens to be burned. For NFT must be set to 1n1273 * @example burnToken(aliceKeyring, 10, 5);1274 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1275 */1276 async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {1277 const burnResult = await this.helper.executeExtrinsic(1278 signer,1279 'api.tx.unique.burnItem', [collectionId, tokenId, amount],1280 true, // `Unable to burn token for ${label}`,1281 );1282 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1283 if(burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1284 return burnedTokens.success;1285 }12861287 /**1288 * Destroys a concrete instance of NFT on behalf of the owner1289 *1290 * @param signer keyring of signer1291 * @param collectionId ID of collection1292 * @param tokenId ID of token1293 * @param fromAddressObj address on behalf of which the token will be burnt1294 * @param amount amount of tokens to be burned. For NFT must be set to 1n1295 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1296 * @returns ```true``` if extrinsic success, otherwise ```false```1297 */1298 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1299 const burnResult = await this.helper.executeExtrinsic(1300 signer,1301 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1302 true, // `Unable to burn token from for ${label}`,1303 );1304 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1305 return burnedTokens.success && burnedTokens.tokens.length > 0;1306 }13071308 /**1309 * Set, change, or remove approved address to transfer the ownership of the NFT.1310 *1311 * @param signer keyring of signer1312 * @param collectionId ID of collection1313 * @param tokenId ID of token1314 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1315 * @param amount amount of token to be approved. For NFT must be set to 1n1316 * @returns ```true``` if extrinsic success, otherwise ```false```1317 */1318 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1319 const approveResult = await this.helper.executeExtrinsic(1320 signer,1321 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1322 true, // `Unable to approve token for ${label}`,1323 );13241325 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1326 }13271328 /**1329 * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1330 *1331 * @param signer keyring of signer1332 * @param collectionId ID of collection1333 * @param tokenId ID of token1334 * @param fromAddressObj Signer's Ethereum address containing her tokens1335 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1336 * @param amount amount of token to be approved. For NFT must be set to 1n1337 * @returns ```true``` if extrinsic success, otherwise ```false```1338 */1339 async approveTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {1340 const approveResult = await this.helper.executeExtrinsic(1341 signer,1342 'api.tx.unique.approveFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1343 true, // `Unable to approve token for ${label}`,1344 );13451346 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1347 }13481349 /**1350 * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1351 *1352 * @param signer keyring of signer1353 * @param collectionId ID of collection1354 * @param tokenId ID of token1355 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1356 * @param amount amount of token to be approved. For NFT must be set to 1n1357 * @returns ```true``` if extrinsic success, otherwise ```false```1358 */1359 async approveTokenFromEth(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1360 const ethMirror = CrossAccountId.fromKeyring(signer).toEthereum();1361 return await this.approveTokenFrom(signer, collectionId, tokenId, ethMirror, toAddressObj, amount);1362 }13631364 /**1365 * Get the amount of token pieces approved to transfer or burn. Normally 0.1366 *1367 * @param collectionId ID of collection1368 * @param tokenId ID of token1369 * @param toAccountObj address which is approved to use token pieces1370 * @param fromAccountObj address which may have allowed the use of its owned tokens1371 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1372 * @returns number of approved to transfer pieces1373 */1374 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1375 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1376 }13771378 /**1379 * Get the last created token ID in a collection1380 *1381 * @param collectionId ID of collection1382 * @example getLastTokenId(10);1383 * @returns id of the last created token1384 */1385 async getLastTokenId(collectionId: number): Promise<number> {1386 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1387 }13881389 /**1390 * Check if token exists1391 *1392 * @param collectionId ID of collection1393 * @param tokenId ID of token1394 * @example doesTokenExist(10, 20);1395 * @returns true if the token exists, otherwise false1396 */1397 async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1398 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1399 }1400}14011402class NFTnRFT extends CollectionGroup {1403 /**1404 * Get tokens owned by account1405 *1406 * @param collectionId ID of collection1407 * @param addressObj tokens owner1408 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1409 * @returns array of token ids owned by account1410 */1411 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1412 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1413 }14141415 /**1416 * Get token data1417 *1418 * @param collectionId ID of collection1419 * @param tokenId ID of token1420 * @param propertyKeys optionally filter the token properties to only these keys1421 * @param blockHashAt optionally query the data at some block with this hash1422 * @example getToken(10, 5);1423 * @returns human readable token data1424 */1425 async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1426 properties: IProperty[];1427 owner: CrossAccountId;1428 normalizedOwner: CrossAccountId;1429 } | null> {1430 let tokenData;1431 if(typeof blockHashAt === 'undefined') {1432 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);1433 }1434 else {1435 if(propertyKeys.length == 0) {1436 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1437 if(!collection) return null;1438 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1439 }1440 tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);1441 }1442 tokenData = tokenData.toHuman();1443 if(tokenData === null || tokenData.owner === null) return null;1444 const owner = {} as any;1445 for(const key of Object.keys(tokenData.owner)) {1446 owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate'1447 ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key])1448 : tokenData.owner[key];1449 }1450 tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner);1451 return tokenData;1452 }14531454 /**1455 * Get token's owner1456 * @param collectionId ID of collection1457 * @param tokenId ID of token1458 * @param blockHashAt optionally query the data at the block with this hash1459 * @example getTokenOwner(10, 5);1460 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1461 */1462 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {1463 let owner;1464 if(typeof blockHashAt === 'undefined') {1465 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1466 } else {1467 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1468 }1469 return CrossAccountId.fromLowerCaseKeys(owner.toJSON());1470 }14711472 /**1473 * Recursively find the address that owns the token1474 * @param collectionId ID of collection1475 * @param tokenId ID of token1476 * @param blockHashAt1477 * @example getTokenTopmostOwner(10, 5);1478 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1479 */1480 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {1481 let owner;1482 if(typeof blockHashAt === 'undefined') {1483 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1484 } else {1485 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1486 }14871488 if(owner === null) return null;14891490 return owner.toHuman();1491 }14921493 /**1494 * Nest one token into another1495 * @param signer keyring of signer1496 * @param tokenObj token to be nested1497 * @param rootTokenObj token to be parent1498 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1499 * @returns ```true``` if extrinsic success, otherwise ```false```1500 */1501 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1502 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1503 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1504 if(!result) {1505 throw Error('Unable to nest token!');1506 }1507 return result;1508 }15091510 /**1511 * Remove token from nested state1512 * @param signer keyring of signer1513 * @param tokenObj token to unnest1514 * @param rootTokenObj parent of a token1515 * @param toAddressObj address of a new token owner1516 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1517 * @returns ```true``` if extrinsic success, otherwise ```false```1518 */1519 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1520 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1521 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1522 if(!result) {1523 throw Error('Unable to unnest token!');1524 }1525 return result;1526 }15271528 /**1529 * Set permissions to change token properties1530 *1531 * @param signer keyring of signer1532 * @param collectionId ID of collection1533 * @param permissions permissions to change a property by the collection admin or token owner1534 * @example setTokenPropertyPermissions(1535 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1536 * )1537 * @returns true if extrinsic success otherwise false1538 */1539 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1540 const result = await this.helper.executeExtrinsic(1541 signer,1542 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1543 true,1544 );15451546 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1547 }15481549 /**1550 * Get token property permissions.1551 *1552 * @param collectionId ID of collection1553 * @param propertyKeys optionally filter the returned property permissions to only these keys1554 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1555 * @returns array of key-permission pairs1556 */1557 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1558 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1559 }15601561 /**1562 * Set token properties1563 *1564 * @param signer keyring of signer1565 * @param collectionId ID of collection1566 * @param tokenId ID of token1567 * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1568 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1569 * @returns ```true``` if extrinsic success, otherwise ```false```1570 */1571 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1572 const result = await this.helper.executeExtrinsic(1573 signer,1574 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1575 true,1576 );15771578 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1579 }15801581 /**1582 * Get properties, metadata assigned to a token.1583 *1584 * @param collectionId ID of collection1585 * @param tokenId ID of token1586 * @param propertyKeys optionally filter the returned properties to only these keys1587 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1588 * @returns array of key-value pairs1589 */1590 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1591 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1592 }15931594 /**1595 * Delete the provided properties of a token1596 * @param signer keyring of signer1597 * @param collectionId ID of collection1598 * @param tokenId ID of token1599 * @param propertyKeys property keys to be deleted1600 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1601 * @returns ```true``` if extrinsic success, otherwise ```false```1602 */1603 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1604 const result = await this.helper.executeExtrinsic(1605 signer,1606 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1607 true,1608 );16091610 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1611 }16121613 /**1614 * Mint new collection1615 *1616 * @param signer keyring of signer1617 * @param collectionOptions basic collection options and properties1618 * @param mode NFT or RFT type of a collection1619 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1620 * @returns object of the created collection1621 */1622 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1623 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1624 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1625 for(const key of ['name', 'description', 'tokenPrefix']) {1626 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);1627 }16281629 let flags = 0;1630 // convert CollectionFlags to number and join them in one number1631 if(collectionOptions.flags) {1632 for(let i = 0; i < collectionOptions.flags.length; i++){1633 const flag = collectionOptions.flags[i];1634 flags = flags | flag;1635 }1636 }1637 collectionOptions.flags = [flags];16381639 const creationResult = await this.helper.executeExtrinsic(1640 signer,1641 'api.tx.unique.createCollectionEx', [collectionOptions],1642 true, // errorLabel,1643 );1644 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1645 }16461647 getCollectionObject(_collectionId: number): any {1648 return null;1649 }16501651 getTokenObject(_collectionId: number, _tokenId: number): any {1652 return null;1653 }16541655 /**1656 * Tells whether the given `owner` approves the `operator`.1657 * @param collectionId ID of collection1658 * @param owner owner address1659 * @param operator operator addrees1660 * @returns true if operator is enabled1661 */1662 async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1663 return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1664 }16651666 /** Sets or unsets the approval of a given operator.1667 * The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1668 * @param operator Operator1669 * @param approved Should operator status be granted or revoked?1670 * @returns ```true``` if extrinsic success, otherwise ```false```1671 */1672 async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1673 const result = await this.helper.executeExtrinsic(1674 signer,1675 'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1676 true,1677 );1678 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1679 }1680}168116821683class NFTGroup extends NFTnRFT {1684 /**1685 * Get collection object1686 * @param collectionId ID of collection1687 * @example getCollectionObject(2);1688 * @returns instance of UniqueNFTCollection1689 */1690 getCollectionObject(collectionId: number): UniqueNFTCollection {1691 return new UniqueNFTCollection(collectionId, this.helper);1692 }16931694 /**1695 * Get token object1696 * @param collectionId ID of collection1697 * @param tokenId ID of token1698 * @example getTokenObject(10, 5);1699 * @returns instance of UniqueNFTToken1700 */1701 getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1702 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1703 }17041705 /**1706 * Is token approved to transfer1707 * @param collectionId ID of collection1708 * @param tokenId ID of token1709 * @param toAccountObj address to be approved1710 * @returns ```true``` if extrinsic success, otherwise ```false```1711 */1712 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1713 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;1714 }17151716 /**1717 * Changes the owner of the token.1718 *1719 * @param signer keyring of signer1720 * @param collectionId ID of collection1721 * @param tokenId ID of token1722 * @param addressObj address of a new owner1723 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1724 * @returns ```true``` if extrinsic success, otherwise ```false```1725 */1726 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1727 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1728 }17291730 /**1731 *1732 * Change ownership of a NFT on behalf of the owner.1733 *1734 * @param signer keyring of signer1735 * @param collectionId ID of collection1736 * @param tokenId ID of token1737 * @param fromAddressObj address on behalf of which the token will be sent1738 * @param toAddressObj new token owner1739 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1740 * @returns ```true``` if extrinsic success, otherwise ```false```1741 */1742 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1743 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1744 }17451746 /**1747 * Get tokens nested in the provided token1748 * @param collectionId ID of collection1749 * @param tokenId ID of token1750 * @param blockHashAt optionally query the data at the block with this hash1751 * @example getTokenChildren(10, 5);1752 * @returns tokens whose depth of nesting is <= 51753 */1754 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1755 let children;1756 if(typeof blockHashAt === 'undefined') {1757 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1758 } else {1759 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1760 }17611762 return children.toJSON().map((x: any) => ({collectionId: x.collection, tokenId: x.token}));1763 }17641765 /**1766 * Mint new collection1767 * @param signer keyring of signer1768 * @param collectionOptions Collection options1769 * @example1770 * mintCollection(aliceKeyring, {1771 * name: 'New',1772 * description: 'New collection',1773 * tokenPrefix: 'NEW',1774 * })1775 * @returns object of the created collection1776 */1777 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1778 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1779 }17801781 /**1782 * Mint new token1783 * @param signer keyring of signer1784 * @param data token data1785 * @returns created token object1786 */1787 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1788 const creationResult = await this.helper.executeExtrinsic(1789 signer,1790 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1791 NFT: {1792 properties: data.properties,1793 },1794 }],1795 true,1796 );1797 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1798 if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1799 if(createdTokens.tokens.length < 1) throw Error('No tokens minted');1800 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1801 }18021803 /**1804 * Mint multiple NFT tokens1805 * @param signer keyring of signer1806 * @param collectionId ID of collection1807 * @param tokens array of tokens with owner and properties1808 * @example1809 * mintMultipleTokens(aliceKeyring, 10, [{1810 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1811 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1812 * },{1813 * owner: {Ethereum: "0x9F0583DbB855d..."},1814 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1815 * }]);1816 * @returns ```true``` if extrinsic success, otherwise ```false```1817 */1818 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1819 const creationResult = await this.helper.executeExtrinsic(1820 signer,1821 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1822 true,1823 );1824 const collection = this.getCollectionObject(collectionId);1825 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1826 }18271828 /**1829 * Mint multiple NFT tokens with one owner1830 * @param signer keyring of signer1831 * @param collectionId ID of collection1832 * @param owner tokens owner1833 * @param tokens array of tokens with owner and properties1834 * @example1835 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1836 * properties: [{1837 * key: "gender",1838 * value: "female",1839 * },{1840 * key: "age",1841 * value: "33",1842 * }],1843 * }]);1844 * @returns array of newly created tokens1845 */1846 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1847 const rawTokens = [];1848 for(const token of tokens) {1849 const raw = {NFT: {properties: token.properties}};1850 rawTokens.push(raw);1851 }1852 const creationResult = await this.helper.executeExtrinsic(1853 signer,1854 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1855 true,1856 );1857 const collection = this.getCollectionObject(collectionId);1858 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1859 }18601861 /**1862 * Set, change, or remove approved address to transfer the ownership of the NFT.1863 *1864 * @param signer keyring of signer1865 * @param collectionId ID of collection1866 * @param tokenId ID of token1867 * @param toAddressObj address to approve1868 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1869 * @returns ```true``` if extrinsic success, otherwise ```false```1870 */1871 approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1872 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1873 }1874}187518761877class RFTGroup extends NFTnRFT {1878 /**1879 * Get collection object1880 * @param collectionId ID of collection1881 * @example getCollectionObject(2);1882 * @returns instance of UniqueRFTCollection1883 */1884 getCollectionObject(collectionId: number): UniqueRFTCollection {1885 return new UniqueRFTCollection(collectionId, this.helper);1886 }18871888 /**1889 * Get token object1890 * @param collectionId ID of collection1891 * @param tokenId ID of token1892 * @example getTokenObject(10, 5);1893 * @returns instance of UniqueNFTToken1894 */1895 getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1896 return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1897 }18981899 /**1900 * Get top 10 token owners with the largest number of pieces1901 * @param collectionId ID of collection1902 * @param tokenId ID of token1903 * @example getTokenTop10Owners(10, 5);1904 * @returns array of top 10 owners1905 */1906 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<CrossAccountId[]> {1907 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys);1908 }19091910 /**1911 * Get number of pieces owned by address1912 * @param collectionId ID of collection1913 * @param tokenId ID of token1914 * @param addressObj address token owner1915 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1916 * @returns number of pieces ownerd by address1917 */1918 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1919 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1920 }19211922 /**1923 * Transfer pieces of token to another address1924 * @param signer keyring of signer1925 * @param collectionId ID of collection1926 * @param tokenId ID of token1927 * @param addressObj address of a new owner1928 * @param amount number of pieces to be transfered1929 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1930 * @returns ```true``` if extrinsic success, otherwise ```false```1931 */1932 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1933 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1934 }19351936 /**1937 * Change ownership of some pieces of RFT on behalf of the owner.1938 * @param signer keyring of signer1939 * @param collectionId ID of collection1940 * @param tokenId ID of token1941 * @param fromAddressObj address on behalf of which the token will be sent1942 * @param toAddressObj new token owner1943 * @param amount number of pieces to be transfered1944 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1945 * @returns ```true``` if extrinsic success, otherwise ```false```1946 */1947 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1948 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1949 }19501951 /**1952 * Mint new collection1953 * @param signer keyring of signer1954 * @param collectionOptions Collection options1955 * @example1956 * mintCollection(aliceKeyring, {1957 * name: 'New',1958 * description: 'New collection',1959 * tokenPrefix: 'NEW',1960 * })1961 * @returns object of the created collection1962 */1963 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1964 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1965 }19661967 /**1968 * Mint new token1969 * @param signer keyring of signer1970 * @param data token data1971 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1972 * @returns created token object1973 */1974 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1975 const creationResult = await this.helper.executeExtrinsic(1976 signer,1977 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1978 ReFungible: {1979 pieces: data.pieces,1980 properties: data.properties,1981 },1982 }],1983 true,1984 );1985 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1986 if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1987 if(createdTokens.tokens.length < 1) throw Error('No tokens minted');1988 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1989 }19901991 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {1992 throw Error('Not implemented');1993 const creationResult = await this.helper.executeExtrinsic(1994 signer,1995 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],1996 true, // `Unable to mint RFT tokens for ${label}`,1997 );1998 const collection = this.getCollectionObject(collectionId);1999 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2000 }20012002 /**2003 * Mint multiple RFT tokens with one owner2004 * @param signer keyring of signer2005 * @param collectionId ID of collection2006 * @param owner tokens owner2007 * @param tokens array of tokens with properties and pieces2008 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);2009 * @returns array of newly created RFT tokens2010 */2011 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {2012 const rawTokens = [];2013 for(const token of tokens) {2014 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};2015 rawTokens.push(raw);2016 }2017 const creationResult = await this.helper.executeExtrinsic(2018 signer,2019 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2020 true,2021 );2022 const collection = this.getCollectionObject(collectionId);2023 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2024 }20252026 /**2027 * Destroys a concrete instance of RFT.2028 * @param signer keyring of signer2029 * @param collectionId ID of collection2030 * @param tokenId ID of token2031 * @param amount number of pieces to be burnt2032 * @example burnToken(aliceKeyring, 10, 5);2033 * @returns ```true``` if the extrinsic is successful, otherwise ```false```2034 */2035 async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {2036 return await super.burnToken(signer, collectionId, tokenId, amount);2037 }20382039 /**2040 * Destroys a concrete instance of RFT on behalf of the owner.2041 * @param signer keyring of signer2042 * @param collectionId ID of collection2043 * @param tokenId ID of token2044 * @param fromAddressObj address on behalf of which the token will be burnt2045 * @param amount number of pieces to be burnt2046 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)2047 * @returns ```true``` if extrinsic success, otherwise ```false```2048 */2049 async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2050 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);2051 }20522053 /**2054 * Set, change, or remove approved address to transfer the ownership of the RFT.2055 *2056 * @param signer keyring of signer2057 * @param collectionId ID of collection2058 * @param tokenId ID of token2059 * @param toAddressObj address to approve2060 * @param amount number of pieces to be approved2061 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);2062 * @returns true if the token success, otherwise false2063 */2064 approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {2065 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);2066 }20672068 /**2069 * Get total number of pieces2070 * @param collectionId ID of collection2071 * @param tokenId ID of token2072 * @example getTokenTotalPieces(10, 5);2073 * @returns number of pieces2074 */2075 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {2076 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();2077 }20782079 /**2080 * Change number of token pieces. Signer must be the owner of all token pieces.2081 * @param signer keyring of signer2082 * @param collectionId ID of collection2083 * @param tokenId ID of token2084 * @param amount new number of pieces2085 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);2086 * @returns true if the repartion was success, otherwise false2087 */2088 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {2089 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);2090 const repartitionResult = await this.helper.executeExtrinsic(2091 signer,2092 'api.tx.unique.repartition', [collectionId, tokenId, amount],2093 true,2094 );2095 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');2096 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');2097 }2098}209921002101class FTGroup extends CollectionGroup {2102 /**2103 * Get collection object2104 * @param collectionId ID of collection2105 * @example getCollectionObject(2);2106 * @returns instance of UniqueFTCollection2107 */2108 getCollectionObject(collectionId: number): UniqueFTCollection {2109 return new UniqueFTCollection(collectionId, this.helper);2110 }21112112 /**2113 * Mint new fungible collection2114 * @param signer keyring of signer2115 * @param collectionOptions Collection options2116 * @param decimalPoints number of token decimals2117 * @example2118 * mintCollection(aliceKeyring, {2119 * name: 'New',2120 * description: 'New collection',2121 * tokenPrefix: 'NEW',2122 * }, 18)2123 * @returns newly created fungible collection2124 */2125 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {2126 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object2127 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');2128 collectionOptions.mode = {fungible: decimalPoints};2129 for(const key of ['name', 'description', 'tokenPrefix']) {2130 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);2131 }2132 const creationResult = await this.helper.executeExtrinsic(2133 signer,2134 'api.tx.unique.createCollectionEx', [collectionOptions],2135 true,2136 );2137 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));2138 }21392140 /**2141 * Mint tokens2142 * @param signer keyring of signer2143 * @param collectionId ID of collection2144 * @param owner address owner of new tokens2145 * @param amount amount of tokens to be meanted2146 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);2147 * @returns ```true``` if extrinsic success, otherwise ```false```2148 */2149 async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {2150 const creationResult = await this.helper.executeExtrinsic(2151 signer,2152 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {2153 Fungible: {2154 value: amount,2155 },2156 }],2157 true, // `Unable to mint fungible tokens for ${label}`,2158 );2159 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2160 }21612162 /**2163 * Mint multiple Fungible tokens with one owner2164 * @param signer keyring of signer2165 * @param collectionId ID of collection2166 * @param owner tokens owner2167 * @param tokens array of tokens with properties and pieces2168 * @returns ```true``` if extrinsic success, otherwise ```false```2169 */2170 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: { value: bigint }[], owner: ICrossAccountId): Promise<boolean> {2171 const rawTokens = [];2172 for(const token of tokens) {2173 const raw = {Fungible: {Value: token.value}};2174 rawTokens.push(raw);2175 }2176 const creationResult = await this.helper.executeExtrinsic(2177 signer,2178 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2179 true,2180 );2181 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2182 }21832184 /**2185 * Get the top 10 owners with the largest balance for the Fungible collection2186 * @param collectionId ID of collection2187 * @example getTop10Owners(10);2188 * @returns array of ```ICrossAccountId```2189 */2190 async getTop10Owners(collectionId: number): Promise<CrossAccountId[]> {2191 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys);2192 }21932194 /**2195 * Get account balance2196 * @param collectionId ID of collection2197 * @param addressObj address of owner2198 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2199 * @returns amount of fungible tokens owned by address2200 */2201 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2202 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2203 }22042205 /**2206 * Transfer tokens to address2207 * @param signer keyring of signer2208 * @param collectionId ID of collection2209 * @param toAddressObj address recipient2210 * @param amount amount of tokens to be sent2211 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2212 * @returns ```true``` if extrinsic success, otherwise ```false```2213 */2214 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2215 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2216 }22172218 /**2219 * Transfer some tokens on behalf of the owner.2220 * @param signer keyring of signer2221 * @param collectionId ID of collection2222 * @param fromAddressObj address on behalf of which tokens will be sent2223 * @param toAddressObj address where token to be sent2224 * @param amount number of tokens to be sent2225 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2226 * @returns ```true``` if extrinsic success, otherwise ```false```2227 */2228 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {2229 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2230 }22312232 /**2233 * Destroy some amount of tokens2234 * @param signer keyring of signer2235 * @param collectionId ID of collection2236 * @param amount amount of tokens to be destroyed2237 * @example burnTokens(aliceKeyring, 10, 1000n);2238 * @returns ```true``` if extrinsic success, otherwise ```false```2239 */2240 async burnTokens(signer: IKeyringPair, collectionId: number, amount = 1n): Promise<boolean> {2241 return await super.burnToken(signer, collectionId, 0, amount);2242 }22432244 /**2245 * Burn some tokens on behalf of the owner.2246 * @param signer keyring of signer2247 * @param collectionId ID of collection2248 * @param fromAddressObj address on behalf of which tokens will be burnt2249 * @param amount amount of tokens to be burnt2250 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2251 * @returns ```true``` if extrinsic success, otherwise ```false```2252 */2253 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2254 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2255 }22562257 /**2258 * Get total collection supply2259 * @param collectionId2260 * @returns2261 */2262 async getTotalPieces(collectionId: number): Promise<bigint> {2263 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2264 }22652266 /**2267 * Set, change, or remove approved address to transfer tokens.2268 *2269 * @param signer keyring of signer2270 * @param collectionId ID of collection2271 * @param toAddressObj address to be approved2272 * @param amount amount of tokens to be approved2273 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2274 * @returns ```true``` if extrinsic success, otherwise ```false```2275 */2276 approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2277 return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2278 }22792280 /**2281 * Get amount of fungible tokens approved to transfer2282 * @param collectionId ID of collection2283 * @param fromAddressObj owner of tokens2284 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2285 * @returns number of tokens approved for the transfer2286 */2287 getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2288 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2289 }2290}229122922293class ChainGroup extends HelperGroup<ChainHelperBase> {2294 /**2295 * Get system properties of a chain2296 * @example getChainProperties();2297 * @returns ss58Format, token decimals, and token symbol2298 */2299 getChainProperties(): IChainProperties {2300 const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2301 return {2302 ss58Format: properties.ss58Format.toJSON(),2303 tokenDecimals: properties.tokenDecimals.toJSON(),2304 tokenSymbol: properties.tokenSymbol.toJSON(),2305 };2306 }23072308 /**2309 * Get chain header2310 * @example getLatestBlockNumber();2311 * @returns the number of the last block2312 */2313 async getLatestBlockNumber(): Promise<number> {2314 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2315 }23162317 /**2318 * Get block hash by block number2319 * @param blockNumber number of block2320 * @example getBlockHashByNumber(12345);2321 * @returns hash of a block2322 */2323 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2324 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2325 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2326 return blockHash;2327 }23282329 // TODO add docs2330 async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2331 const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2332 if(!blockHash) return null;2333 return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2334 }23352336 /**2337 * Get latest relay block2338 * @returns {number} relay block2339 */2340 async getRelayBlockNumber(): Promise<bigint> {2341 const blockNumber = (await this.helper.callRpc('api.query.parachainSystem.validationData')).toJSON().relayParentNumber;2342 return BigInt(blockNumber);2343 }23442345 /**2346 * Get account nonce2347 * @param address substrate address2348 * @example getNonce("5GrwvaEF5zXb26Fz...");2349 * @returns number, account's nonce2350 */2351 async getNonce(address: TSubstrateAccount): Promise<number> {2352 return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2353 }2354}23552356class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2357 /**2358 * Get substrate address balance2359 * @param address substrate address2360 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2361 * @returns amount of tokens on address2362 */2363 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2364 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2365 }23662367 /**2368 * Transfer tokens to substrate address2369 * @param signer keyring of signer2370 * @param address substrate address of a recipient2371 * @param amount amount of tokens to be transfered2372 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2373 * @returns ```true``` if extrinsic success, otherwise ```false```2374 */2375 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2376 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}`*/);23772378 let transfer = {from: null, to: null, amount: 0n} as any;2379 result.result.events.forEach(({event: {data, method, section}}) => {2380 if((section === 'balances') && (method === 'Transfer')) {2381 transfer = {2382 from: this.helper.address.normalizeSubstrate(data[0]),2383 to: this.helper.address.normalizeSubstrate(data[1]),2384 amount: BigInt(data[2]),2385 };2386 }2387 });2388 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2389 && this.helper.address.normalizeSubstrate(address) === transfer.to2390 && BigInt(amount) === transfer.amount;2391 return isSuccess;2392 }23932394 /**2395 * Get full substrate balance including free, frozen, and reserved2396 * @param address substrate address2397 * @returns2398 */2399 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2400 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2401 return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2402 }24032404 /**2405 * Get total issuance2406 * @returns2407 */2408 async getTotalIssuance(): Promise<bigint> {2409 const total = (await this.helper.callRpc('api.query.balances.totalIssuance', []));2410 return total.toBigInt();2411 }24122413 async getLocked(address: TSubstrateAccount): Promise<{ id: string, amount: bigint, reason: string }[]> {2414 const locks = (await this.helper.callRpc('api.query.balances.locks', [address])).toHuman();2415 return locks.map((lock: any) => ({id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}));2416 }2417 async getFrozen(address: TSubstrateAccount): Promise<{ id: string, amount: bigint }[]> {2418 const locks = (await this.helper.api!.query.balances.freezes(address)) as unknown as Array<any>;2419 return locks.map(lock => ({id: lock.id.toUtf8(), amount: lock.amount.toBigInt()}));2420 }2421}24222423class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2424 /**2425 * Get ethereum address balance2426 * @param address ethereum address2427 * @example getEthereum("0x9F0583DbB855d...")2428 * @returns amount of tokens on address2429 */2430 async getEthereum(address: TEthereumAccount): Promise<bigint> {2431 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2432 }24332434 /**2435 * Transfer tokens to address2436 * @param signer keyring of signer2437 * @param address Ethereum address of a recipient2438 * @param amount amount of tokens to be transfered2439 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2440 * @returns ```true``` if extrinsic success, otherwise ```false```2441 */2442 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2443 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);24442445 let transfer = {from: null, to: null, amount: 0n} as any;2446 result.result.events.forEach(({event: {data, method, section}}) => {2447 if((section === 'balances') && (method === 'Transfer')) {2448 transfer = {2449 from: data[0].toString(),2450 to: data[1].toString(),2451 amount: BigInt(data[2]),2452 };2453 }2454 });2455 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2456 && address === transfer.to2457 && BigInt(amount) === transfer.amount;2458 return isSuccess;2459 }2460}24612462class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2463 subBalanceGroup: SubstrateBalanceGroup<T>;2464 ethBalanceGroup: EthereumBalanceGroup<T>;24652466 constructor(helper: T) {2467 super(helper);2468 this.subBalanceGroup = new SubstrateBalanceGroup(helper);2469 this.ethBalanceGroup = new EthereumBalanceGroup(helper);2470 }24712472 getCollectionCreationPrice(): bigint {2473 return 2n * this.getOneTokenNominal();2474 }2475 /**2476 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2477 * @example getOneTokenNominal()2478 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2479 */2480 getOneTokenNominal(): bigint {2481 const chainProperties = this.helper.chain.getChainProperties();2482 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2483 }24842485 /**2486 * Get substrate address balance2487 * @param address substrate address2488 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2489 * @returns amount of tokens on address2490 */2491 getSubstrate(address: TSubstrateAccount): Promise<bigint> {2492 return this.subBalanceGroup.getSubstrate(address);2493 }24942495 /**2496 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2497 * @param address substrate address2498 * @returns2499 */2500 getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2501 return this.subBalanceGroup.getSubstrateFull(address);2502 }25032504 /**2505 * Get total issuance2506 * @returns2507 */2508 getTotalIssuance(): Promise<bigint> {2509 return this.subBalanceGroup.getTotalIssuance();2510 }25112512 /**2513 * Get locked balances2514 * @param address substrate address2515 * @returns locked balances with reason via api.query.balances.locks2516 * @deprecated all the methods should switch to getFrozen2517 */2518 getLocked(address: TSubstrateAccount) {2519 return this.subBalanceGroup.getLocked(address);2520 }25212522 /**2523 * Get frozen balances2524 * @param address substrate address2525 * @returns frozen balances with id via api.query.balances.freezes2526 */2527 getFrozen(address: TSubstrateAccount) {2528 return this.subBalanceGroup.getFrozen(address);2529 }25302531 /**2532 * Get ethereum address balance2533 * @param address ethereum address2534 * @example getEthereum("0x9F0583DbB855d...")2535 * @returns amount of tokens on address2536 */2537 getEthereum(address: TEthereumAccount): Promise<bigint> {2538 return this.ethBalanceGroup.getEthereum(address);2539 }25402541 async setBalanceSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint) {2542 await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceSetBalance', [address, amount], true);2543 }25442545 /**2546 * Transfer tokens to substrate address2547 * @param signer keyring of signer2548 * @param address substrate address of a recipient2549 * @param amount amount of tokens to be transfered2550 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2551 * @returns ```true``` if extrinsic success, otherwise ```false```2552 */2553 transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2554 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2555 }25562557 async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2558 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);25592560 let transfer = {from: null, to: null, amount: 0n} as any;2561 result.result.events.forEach(({event: {data, method, section}}) => {2562 if((section === 'balances') && (method === 'Transfer')) {2563 transfer = {2564 from: this.helper.address.normalizeSubstrate(data[0]),2565 to: this.helper.address.normalizeSubstrate(data[1]),2566 amount: BigInt(data[2]),2567 };2568 }2569 });2570 let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2571 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2572 isSuccess = isSuccess && BigInt(amount) === transfer.amount;2573 return isSuccess;2574 }25752576 /**2577 * Transfer tokens with the unlock period2578 * @param signer signers Keyring2579 * @param address Substrate address of recipient2580 * @param schedule Schedule params2581 * @example vestedTransfer(signer, recepient.address, 20000, 100, 10, 50 * nominal); // total amount of vested tokens will be 100 * 50 = 50002582 */2583 async vestedTransfer(signer: TSigner, address: TSubstrateAccount, schedule: { start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }): Promise<void> {2584 const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.vestedTransfer', [address, schedule]);2585 const event = result.result.events2586 .find(e => e.event.section === 'vesting' &&2587 e.event.method === 'VestingScheduleAdded' &&2588 e.event.data[0].toHuman() === signer.address);2589 if(!event) throw Error('Cannot find transfer in events');2590 }25912592 /**2593 * Get schedule for recepient of vested transfer2594 * @param address Substrate address of recipient2595 * @returns2596 */2597 async getVestingSchedules(address: TSubstrateAccount): Promise<{ start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }[]> {2598 const schedule = (await this.helper.callRpc('api.query.vesting.vestingSchedules', [address])).toJSON();2599 return schedule.map((schedule: any) => ({2600 start: BigInt(schedule.start),2601 period: BigInt(schedule.period),2602 periodCount: BigInt(schedule.periodCount),2603 perPeriod: BigInt(schedule.perPeriod),2604 }));2605 }26062607 /**2608 * Claim vested tokens2609 * @param signer signers Keyring2610 */2611 async claim(signer: TSigner) {2612 const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.claim', []);2613 const event = result.result.events2614 .find(e => e.event.section === 'vesting' &&2615 e.event.method === 'Claimed' &&2616 e.event.data[0].toHuman() === signer.address);2617 if(!event) throw Error('Cannot find claim in events');2618 }2619}26202621class AddressGroup extends HelperGroup<ChainHelperBase> {2622 /**2623 * Normalizes the address to the specified ss58 format, by default ```42```.2624 * @param address substrate address2625 * @param ss58Format format for address conversion, by default ```42```2626 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2627 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2628 */2629 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2630 return CrossAccountId.normalizeSubstrateAddress(address, ss58Format);2631 }26322633 /**2634 * Get address in the connected chain format2635 * @param address substrate address2636 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2637 * @returns address in chain format2638 */2639 normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2640 return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2641 }26422643 /**2644 * Get substrate mirror of an ethereum address2645 * @param ethAddress ethereum address2646 * @param toChainFormat false for normalized account2647 * @example ethToSubstrate('0x9F0583DbB855d...')2648 * @returns substrate mirror of a provided ethereum address2649 */2650 ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat = false): TSubstrateAccount {2651 return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2652 }26532654 /**2655 * Get ethereum mirror of a substrate address2656 * @param subAddress substrate account2657 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2658 * @returns ethereum mirror of a provided substrate address2659 */2660 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2661 return CrossAccountId.translateSubToEth(subAddress);2662 }26632664 /**2665 * Encode key to substrate address2666 * @param key key for encoding address2667 * @param ss58Format prefix for encoding to the address of the corresponding network2668 * @returns encoded substrate address2669 */2670 encodeSubstrateAddress(key: Uint8Array | string | bigint, ss58Format = 42): string {2671 const u8a: Uint8Array = typeof key === 'string'2672 ? hexToU8a(key)2673 : typeof key === 'bigint'2674 ? hexToU8a(key.toString(16))2675 : key;26762677 if(ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2678 throw new Error(`ss58Format is not valid, received ${typeofss58Format} "${ss58Format}"`);2679 }26802681 const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2682 if(!allowedDecodedLengths.includes(u8a.length)) {2683 throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2684 }26852686 const u8aPrefix = ss58Format < 642687 ? new Uint8Array([ss58Format])2688 : new Uint8Array([2689 ((ss58Format & 0xfc) >> 2) | 0x40,2690 (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2691 ]);26922693 const input = u8aConcat(u8aPrefix, u8a);26942695 return base58Encode(u8aConcat(2696 input,2697 blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2698 ));2699 }27002701 /**2702 * Restore substrate address from bigint representation2703 * @param number decimal representation of substrate address2704 * @returns substrate address2705 */2706 restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2707 if(this.helper.api === null) {2708 throw 'Not connected';2709 }2710 const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2711 if(res === undefined || res === null) {2712 throw 'Restore address error';2713 }2714 return res.toString();2715 }27162717 /**2718 * Convert etherium cross account id to substrate cross account id2719 * @param ethCrossAccount etherium cross account2720 * @returns substrate cross account id2721 */2722 convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2723 if(ethCrossAccount.sub === '0') {2724 return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2725 }27262727 const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2728 return {Substrate: ss58};2729 }27302731 paraSiblingSovereignAccount(paraid: number) {2732 // We are getting a *sibling* parachain sovereign account,2733 // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2734 const siblingPrefix = '0x7369626c';27352736 const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2737 const suffix = '000000000000000000000000000000000000000000000000';27382739 return siblingPrefix + encodedParaId + suffix;2740 }2741}27422743class StakingGroup extends HelperGroup<UniqueHelper> {2744 /**2745 * Stake tokens for App Promotion2746 * @param signer keyring of signer2747 * @param amountToStake amount of tokens to stake2748 * @param label extra label for log2749 * @returns2750 */2751 async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2752 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2753 const _stakeResult = await this.helper.executeExtrinsic(2754 signer, 'api.tx.appPromotion.stake',2755 [amountToStake], true,2756 );2757 // TODO extract info from stakeResult2758 return true;2759 }27602761 /**2762 * Unstake all staked tokens2763 * @param signer keyring of signer2764 * @param amountToUnstake amount of tokens to unstake2765 * @param label extra label for log2766 * @returns block hash where unstake happened2767 */2768 async unstakeAll(signer: TSigner, label?: string): Promise<string> {2769 if(typeof label === 'undefined') label = `${signer.address}`;2770 const unstakeResult = await this.helper.executeExtrinsic(2771 signer, 'api.tx.appPromotion.unstakeAll',2772 [], true,2773 );2774 return unstakeResult.blockHash;2775 }27762777 /**2778 * Unstake the part of a staked tokens2779 * @param signer keyring of signer2780 * @param amount amount of tokens to unstake2781 * @param label extra label for log2782 * @returns block hash where unstake happened2783 */2784 async unstakePartial(signer: TSigner, amount: bigint, label?: string): Promise<string> {2785 if(typeof label === 'undefined') label = `${signer.address}`;2786 const unstakeResult = await this.helper.executeExtrinsic(2787 signer, 'api.tx.appPromotion.unstakePartial',2788 [amount], true,2789 );2790 return unstakeResult.blockHash;2791 }27922793 /**2794 * Get total number of active stakes2795 * @param address substrate address2796 * @returns {number}2797 */2798 async getStakesNumber(address: ICrossAccountId): Promise<number> {2799 if('Ethereum' in address) throw Error('only substrate address');2800 return (await this.helper.callRpc('api.query.appPromotion.stakesPerAccount', [address.Substrate])).toNumber();2801 }28022803 /**2804 * Get total staked amount for address2805 * @param address substrate or ethereum address2806 * @returns total staked amount2807 */2808 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2809 if(address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2810 return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2811 }28122813 /**2814 * Get total staked per block2815 * @param address substrate or ethereum address2816 * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2817 */2818 async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2819 const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2820 return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => ({2821 block: block.toBigInt(),2822 amount: amount.toBigInt(),2823 }));2824 }28252826 /**2827 * Get total pending unstake amount for address2828 * @param address substrate or ethereum address2829 * @returns total pending unstake amount2830 */2831 async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2832 return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2833 }28342835 /**2836 * Get pending unstake amount per block for address2837 * @param address substrate or ethereum address2838 * @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 block2839 */2840 async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2841 const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2842 const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => ({2843 block: block.toBigInt(),2844 amount: amount.toBigInt(),2845 }));2846 return result;2847 }2848}28492850class SchedulerGroup extends HelperGroup<UniqueHelper> {2851 constructor(helper: UniqueHelper) {2852 super(helper);2853 }28542855 cancelScheduled(signer: TSigner, scheduledId: string) {2856 return this.helper.executeExtrinsic(2857 signer,2858 'api.tx.scheduler.cancelNamed',2859 [scheduledId],2860 true,2861 );2862 }28632864 changePriority(signer: TSigner, scheduledId: string, priority: number) {2865 return this.helper.executeExtrinsic(2866 signer,2867 'api.tx.scheduler.changeNamedPriority',2868 [scheduledId, priority],2869 true,2870 );2871 }28722873 scheduleAt<T extends UniqueHelper>(2874 executionBlockNumber: number,2875 options: ISchedulerOptions = {},2876 ) {2877 return this.schedule<T>('schedule', executionBlockNumber, options);2878 }28792880 scheduleAfter<T extends UniqueHelper>(2881 blocksBeforeExecution: number,2882 options: ISchedulerOptions = {},2883 ) {2884 return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);2885 }28862887 schedule<T extends UniqueHelper>(2888 scheduleFn: 'schedule' | 'scheduleAfter',2889 blocksNum: number,2890 options: ISchedulerOptions = {},2891 ) {2892 // eslint-disable-next-line @typescript-eslint/naming-convention2893 const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);2894 return this.helper.clone(ScheduledHelperType, {2895 scheduleFn,2896 blocksNum,2897 options,2898 }) as T;2899 }2900}29012902class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {2903 //todo:collator documentation2904 addInvulnerable(signer: TSigner, address: string) {2905 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);2906 }29072908 removeInvulnerable(signer: TSigner, address: string) {2909 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);2910 }29112912 async getInvulnerables(): Promise<string[]> {2913 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());2914 }29152916 /** and also total max invulnerables */2917 maxCollators(): number {2918 return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);2919 }29202921 async getDesiredCollators(): Promise<number> {2922 return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();2923 }29242925 setLicenseBond(signer: TSigner, amount: bigint) {2926 return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);2927 }29282929 async getLicenseBond(): Promise<bigint> {2930 return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();2931 }29322933 obtainLicense(signer: TSigner) {2934 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);2935 }29362937 releaseLicense(signer: TSigner) {2938 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);2939 }29402941 forceReleaseLicense(signer: TSigner, released: string) {2942 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);2943 }29442945 async hasLicense(address: string): Promise<bigint> {2946 return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();2947 }29482949 onboard(signer: TSigner) {2950 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);2951 }29522953 offboard(signer: TSigner) {2954 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);2955 }29562957 async getCandidates(): Promise<string[]> {2958 return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());2959 }2960}29612962class CollectiveGroup extends HelperGroup<UniqueHelper> {2963 /**2964 * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee'2965 */2966 private collective: string;29672968 constructor(helper: UniqueHelper, collective: string) {2969 super(helper);2970 this.collective = collective;2971 }29722973 /**2974 * Check the result of a proposal execution for the success of the underlying proposed extrinsic.2975 * @param events events of the proposal execution2976 * @returns proposal hash2977 */2978 private checkExecutedEvent(events: IPhasicEvent[]): string {2979 const executionEvents = events.filter(x =>2980 x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted'));29812982 if(executionEvents.length != 1) {2983 if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0)2984 throw new Error(`Disapproved by ${this.collective}`);2985 else2986 throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`);2987 }29882989 const result = (executionEvents[0].event.data as any).result;29902991 if(result.isErr) {2992 if(result.asErr.isModule) {2993 const error = result.asErr.asModule;2994 const metaError = this.helper.getApi()?.registry.findMetaError(error);2995 throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`);2996 } else {2997 throw new Error('Proposal execution failed with ' + result.asErr.toHuman());2998 }2999 }30003001 return (executionEvents[0].event.data as any).proposalHash;3002 }30033004 /**3005 * Returns an array of members' addresses.3006 */3007 async getMembers() {3008 return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman();3009 }30103011 /**3012 * Returns the optional address of the prime member of the collective.3013 */3014 async getPrimeMember() {3015 return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman();3016 }30173018 /**3019 * Returns an array of proposal hashes that are currently active for this collective.3020 */3021 async getProposals() {3022 return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman();3023 }30243025 /**3026 * Returns the call originally encoded under the specified hash.3027 * @param hash h256-encoded proposal3028 * @returns the optional call that the proposal hash stands for.3029 */3030 async getProposalCallOf(hash: string) {3031 return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman();3032 }30333034 /**3035 * Returns the total number of proposals so far.3036 */3037 async getTotalProposalsCount() {3038 return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber();3039 }30403041 /**3042 * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately.3043 * @param signer keyring of the proposer3044 * @param proposal constructed call to be executed if the proposal is successful3045 * @param voteThreshold minimal number of votes for the proposal to be verified and executed3046 * @param lengthBound byte length of the encoded call3047 * @returns promise of extrinsic execution and its result3048 */3049 async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) {3050 return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]);3051 }30523053 /**3054 * Casts a vote to either approve or reject a proposal.3055 * @param signer keyring of the voter3056 * @param proposalHash hash of the proposal to be voted for3057 * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors3058 * @param approve aye or nay3059 * @returns promise of extrinsic execution and its result3060 */3061 vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {3062 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]);3063 }30643065 /**3066 * Executes a call immediately as a member of the collective. Needed for the Member origin.3067 * @param signer keyring of the executor member3068 * @param proposal constructed call to be executed by the member3069 * @param lengthBound byte length of the encoded call3070 * @returns promise of extrinsic execution3071 */3072 async execute(signer: TSigner, proposal: any, lengthBound = 10000) {3073 const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]);3074 this.checkExecutedEvent(result.result.events);3075 return result;3076 }30773078 /**3079 * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing.3080 * @param signer keyring of the executor. Can be absolutely anyone.3081 * @param proposalHash hash of the proposal to close3082 * @param proposalIndex index of the proposal generated on its creation3083 * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call.3084 * @param lengthBound byte length of the encoded call3085 * @returns promise of extrinsic execution and its result3086 */3087 async close(3088 signer: TSigner,3089 proposalHash: string,3090 proposalIndex: number,3091 weightBound: [number, number] | any = [20_000_000_000, 1000_000],3092 lengthBound = 10_000,3093 ) {3094 const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [3095 proposalHash,3096 proposalIndex,3097 weightBound,3098 lengthBound,3099 ]);3100 this.checkExecutedEvent(result.result.events);3101 return result;3102 }31033104 /**3105 * Shut down a proposal, regardless of its current state.3106 * @param signer keyring of the disapprover. Must be root3107 * @param proposalHash hash of the proposal to close3108 * @returns promise of extrinsic execution and its result3109 */3110 disapproveProposal(signer: TSigner, proposalHash: string) {3111 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]);3112 }3113}31143115class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {3116 /**3117 * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership'3118 */3119 private membership: string;31203121 constructor(helper: UniqueHelper, membership: string) {3122 super(helper);3123 this.membership = membership;3124 }31253126 /**3127 * Returns an array of members' addresses according to the membership pallet's perception.3128 * Note that it does not recognize the original pallet's members set with `setMembers()`.3129 */3130 async getMembers() {3131 return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman();3132 }31333134 /**3135 * Returns the optional address of the prime member of the collective.3136 */3137 async getPrimeMember() {3138 return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman();3139 }31403141 /**3142 * Add a member to the collective.3143 * @param signer keyring of the setter. Must be root3144 * @param member address of the member to add3145 * @returns promise of extrinsic execution and its result3146 */3147 addMember(signer: TSigner, member: string) {3148 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]);3149 }31503151 addMemberCall(member: string) {3152 return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]);3153 }31543155 /**3156 * Remove a member from the collective.3157 * @param signer keyring of the setter. Must be root3158 * @param member address of the member to remove3159 * @returns promise of extrinsic execution and its result3160 */3161 removeMember(signer: TSigner, member: string) {3162 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]);3163 }31643165 removeMemberCall(member: string) {3166 return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]);3167 }31683169 /**3170 * Set members of the collective to the given list of addresses.3171 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3172 * @param members addresses of the members to set3173 * @returns promise of extrinsic execution and its result3174 */3175 resetMembers(signer: TSigner, members: string[]) {3176 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]);3177 }31783179 /**3180 * Set the collective's prime member to the given address.3181 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3182 * @param prime address of the prime member of the collective3183 * @returns promise of extrinsic execution and its result3184 */3185 setPrime(signer: TSigner, prime: string) {3186 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]);3187 }31883189 setPrimeCall(member: string) {3190 return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]);3191 }31923193 /**3194 * Remove the collective's prime member.3195 * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)3196 * @returns promise of extrinsic execution and its result3197 */3198 clearPrime(signer: TSigner) {3199 return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []);3200 }32013202 clearPrimeCall() {3203 return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []);3204 }3205}32063207class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {3208 /**3209 * Pallet name to make an API call to. Examples: 'FellowshipCollective'3210 */3211 private collective: string;32123213 constructor(helper: UniqueHelper, collective: string) {3214 super(helper);3215 this.collective = collective;3216 }32173218 addMember(signer: TSigner, newMember: string) {3219 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]);3220 }32213222 addMemberCall(newMember: string) {3223 return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]);3224 }32253226 removeMember(signer: TSigner, member: string, minRank: number) {3227 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]);3228 }32293230 removeMemberCall(newMember: string, minRank: number) {3231 return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]);3232 }32333234 promote(signer: TSigner, member: string) {3235 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]);3236 }32373238 promoteCall(newMember: string) {3239 return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [newMember]);3240 }32413242 demote(signer: TSigner, member: string) {3243 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]);3244 }32453246 demoteCall(newMember: string) {3247 return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]);3248 }32493250 vote(signer: TSigner, pollIndex: number, aye: boolean) {3251 return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]);3252 }32533254 async getMembers() {3255 return (await this.helper.getApi().query.fellowshipCollective.members.keys())3256 .map((key) => key.args[0].toString());3257 }3258}32593260class ReferendaGroup extends HelperGroup<UniqueHelper> {3261 /**3262 * Pallet name to make an API call to. Examples: 'FellowshipReferenda'3263 */3264 private referenda: string;32653266 constructor(helper: UniqueHelper, referenda: string) {3267 super(helper);3268 this.referenda = referenda;3269 }32703271 submit(3272 signer: TSigner,3273 proposalOrigin: string,3274 proposal: any,3275 enactmentMoment: any,3276 ) {3277 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [3278 {Origins: proposalOrigin},3279 proposal,3280 enactmentMoment,3281 ]);3282 }32833284 placeDecisionDeposit(signer: TSigner, referendumIndex: number) {3285 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]);3286 }32873288 cancel(signer: TSigner, referendumIndex: number) {3289 return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]);3290 }32913292 cancelCall(referendumIndex: number) {3293 return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]);3294 }32953296 async referendumInfo(referendumIndex: number) {3297 return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON();3298 }32993300 async enactmentEventId(referendumIndex: number) {3301 const api = await this.helper.getApi();33023303 const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a();3304 return blake2AsHex(bytes, 256);3305 }3306}33073308export interface IFellowshipGroup {3309 collective: RankedCollectiveGroup;3310 referenda: ReferendaGroup;3311}33123313export interface ICollectiveGroup {3314 collective: CollectiveGroup;3315 membership: CollectiveMembershipGroup;3316}33173318class DemocracyGroup extends HelperGroup<UniqueHelper> {3319 // todo displace proposal into types?3320 propose(signer: TSigner, call: any, deposit: bigint) {3321 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3322 }33233324 proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) {3325 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]);3326 }33273328 proposeCall(call: any, deposit: bigint) {3329 return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);3330 }33313332 second(signer: TSigner, proposalIndex: number) {3333 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]);3334 }33353336 externalPropose(signer: TSigner, proposalCall: any) {3337 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3338 }33393340 externalProposeMajority(signer: TSigner, proposalCall: any) {3341 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3342 }33433344 externalProposeDefault(signer: TSigner, proposalCall: any) {3345 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3346 }33473348 externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) {3349 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);3350 }33513352 externalProposeCall(proposalCall: any) {3353 return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);3354 }33553356 externalProposeMajorityCall(proposalCall: any) {3357 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);3358 }33593360 externalProposeDefaultCall(proposalCall: any) {3361 return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);3362 }33633364 // ... and blacklist external proposal hash.3365 vetoExternal(signer: TSigner, proposalHash: string) {3366 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]);3367 }33683369 vetoExternalCall(proposalHash: string) {3370 return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]);3371 }33723373 blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) {3374 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3375 }33763377 blacklistCall(proposalHash: string, referendumIndex: number | null = null) {3378 return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]);3379 }33803381 // proposal. CancelProposalOrigin (root or all techcom)3382 cancelProposal(signer: TSigner, proposalIndex: number) {3383 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]);3384 }33853386 cancelProposalCall(proposalIndex: number) {3387 return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]);3388 }33893390 clearPublicProposals(signer: TSigner) {3391 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []);3392 }33933394 fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) {3395 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3396 }33973398 fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) {3399 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3400 }34013402 // referendum. CancellationOrigin (TechCom member)3403 emergencyCancel(signer: TSigner, referendumIndex: number) {3404 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]);3405 }34063407 emergencyCancelCall(referendumIndex: number) {3408 return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]);3409 }34103411 vote(signer: TSigner, referendumIndex: number, vote: any) {3412 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]);3413 }34143415 removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) {3416 if(targetAccount) {3417 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]);3418 } else {3419 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]);3420 }3421 }34223423 unlock(signer: TSigner, targetAccount: string) {3424 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]);3425 }34263427 delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) {3428 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]);3429 }34303431 undelegate(signer: TSigner) {3432 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []);3433 }34343435 async referendumInfo(referendumIndex: number) {3436 return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON();3437 }34383439 async publicProposals() {3440 return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON();3441 }34423443 async findPublicProposal(proposalIndex: number) {3444 const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex);34453446 return proposalInfo ? proposalInfo[1] : null;3447 }34483449 async expectPublicProposal(proposalIndex: number) {3450 const proposal = await this.findPublicProposal(proposalIndex);34513452 if(proposal) {3453 return proposal;3454 } else {3455 throw Error(`Proposal #${proposalIndex} is expected to exist`);3456 }3457 }34583459 async getExternalProposal() {3460 return (await this.helper.callRpc('api.query.democracy.nextExternal', []));3461 }34623463 async expectExternalProposal() {3464 const proposal = await this.getExternalProposal();34653466 if(proposal) {3467 return proposal;3468 } else {3469 throw Error('An external proposal is expected to exist');3470 }3471 }34723473 /* setMetadata? */34743475 /* todo?3476 referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {3477 return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);3478 }*/3479}34803481class PreimageGroup extends HelperGroup<UniqueHelper> {3482 async getPreimageInfo(h256: string) {3483 return (await this.helper.callRpc('api.query.preimage.statusFor', [h256])).toJSON();3484 }34853486 /**3487 * Create a preimage from an API call.3488 * @param signer keyring of the signer.3489 * @param call an extrinsic call3490 * @example await notePreimageFromCall(preimageMaker,3491 * helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd])3492 * );3493 * @returns promise of extrinsic execution.3494 */3495 notePreimageFromCall(signer: TSigner, call: any, returnPreimageHash = false) {3496 return this.notePreimage(signer, call.method.toHex(), returnPreimageHash);3497 }34983499 /**3500 * Create a preimage with a hex or a byte array.3501 * @param signer keyring of the signer.3502 * @param bytes preimage encoded in hex or a byte array, e.g. an extrinsic call.3503 * @example await notePreimage(preimageMaker,3504 * helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex()3505 * );3506 * @returns promise of extrinsic execution.3507 */3508 async notePreimage(signer: TSigner, bytes: string | Uint8Array, returnPreimageHash = false) {3509 const promise = this.helper.executeExtrinsic(signer, 'api.tx.preimage.notePreimage', [bytes]);3510 if(returnPreimageHash) {3511 const result = await promise;3512 const events = result.result.events.filter(x => x.event.method === 'Noted' && x.event.section === 'preimage');3513 const preimageHash = events[0].event.data[0].toHuman();3514 return preimageHash;3515 }3516 return promise;3517 }35183519 /**3520 * Delete an existing preimage and return the deposit.3521 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3522 * @param h256 hash of the preimage.3523 * @returns promise of extrinsic execution.3524 */3525 unnotePreimage(signer: TSigner, h256: string) {3526 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unnotePreimage', [h256]);3527 }35283529 /**3530 * Request a preimage be uploaded to the chain without paying any fees or deposits.3531 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3532 * @param h256 hash of the preimage.3533 * @returns promise of extrinsic execution.3534 */3535 requestPreimage(signer: TSigner, h256: string) {3536 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.requestPreimage', [h256]);3537 }35383539 /**3540 * Clear a previously made request for a preimage.3541 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).3542 * @param h256 hash of the preimage.3543 * @returns promise of extrinsic execution.3544 */3545 unrequestPreimage(signer: TSigner, h256: string) {3546 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]);3547 }3548}35493550class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {3551 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {3552 await this.helper.executeExtrinsic(3553 signer,3554 'api.tx.foreignAssets.registerForeignAsset',3555 [ownerAddress, location, metadata],3556 true,3557 );3558 }35593560 async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {3561 await this.helper.executeExtrinsic(3562 signer,3563 'api.tx.foreignAssets.updateForeignAsset',3564 [foreignAssetId, location, metadata],3565 true,3566 );3567 }3568}35693570class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {3571 palletName: string;35723573 constructor(helper: T, palletName: string) {3574 super(helper);35753576 this.palletName = palletName;3577 }35783579 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {3580 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);3581 }35823583 async setSafeXcmVersion(signer: TSigner, version: number) {3584 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);3585 }35863587 async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {3588 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);3589 }35903591 async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {3592 const destinationContent = {3593 parents: 0,3594 interior: {3595 X1: {3596 Parachain: destinationParaId,3597 },3598 },3599 };36003601 const beneficiaryContent = {3602 parents: 0,3603 interior: {3604 X1: {3605 AccountId32: {3606 network: 'Any',3607 id: targetAccount,3608 },3609 },3610 },3611 };36123613 const assetsContent = [3614 {3615 id: {3616 Concrete: {3617 parents: 0,3618 interior: 'Here',3619 },3620 },3621 fun: {3622 Fungible: amount,3623 },3624 },3625 ];36263627 let destination;3628 let beneficiary;3629 let assets;36303631 if(xcmVersion == 2) {3632 destination = {V1: destinationContent};3633 beneficiary = {V1: beneficiaryContent};3634 assets = {V1: assetsContent};36353636 } else if(xcmVersion == 3) {3637 destination = {V2: destinationContent};3638 beneficiary = {V2: beneficiaryContent};3639 assets = {V2: assetsContent};36403641 } else {3642 throw Error('Unknown XCM version: ' + xcmVersion);3643 }36443645 const feeAssetItem = 0;36463647 await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);3648 }36493650 async send(signer: IKeyringPair, destination: any, message: any) {3651 await this.helper.executeExtrinsic(3652 signer,3653 `api.tx.${this.palletName}.send`,3654 [3655 destination,3656 message,3657 ],3658 true,3659 );3660 }3661}36623663class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3664 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {3665 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);3666 }36673668 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {3669 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);3670 }36713672 async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {3673 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);3674 }3675}36763677class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {3678 async accounts(address: string, currencyId: any) {3679 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;3680 return BigInt(free);3681 }3682}36833684class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {3685 async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {3686 await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);3687 }36883689 async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {3690 await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);3691 }36923693 async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {3694 await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);3695 }36963697 async account(assetId: string | number, address: string) {3698 const accountAsset = (3699 await this.helper.callRpc('api.query.assets.account', [assetId, address])3700 ).toJSON()! as any;37013702 if(accountAsset !== null) {3703 return BigInt(accountAsset['balance']);3704 } else {3705 return null;3706 }3707 }3708}37093710class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {3711 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {3712 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);3713 }3714}37153716class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {3717 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {3718 const apiPrefix = 'api.tx.assetManager.';37193720 const registerTx = this.helper.constructApiCall(3721 apiPrefix + 'registerForeignAsset',3722 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],3723 );37243725 const setUnitsTx = this.helper.constructApiCall(3726 apiPrefix + 'setAssetUnitsPerSecond',3727 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],3728 );37293730 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);3731 const encodedProposal = batchCall?.method.toHex() || '';3732 return encodedProposal;3733 }37343735 async assetTypeId(location: any) {3736 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);3737 }3738}37393740class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {3741 notePreimagePallet: string;37423743 constructor(helper: MoonbeamHelper, options: { [key: string]: any } = {}) {3744 super(helper);3745 this.notePreimagePallet = options.notePreimagePallet;3746 }37473748 async notePreimage(signer: TSigner, encodedProposal: string) {3749 await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);3750 }37513752 externalProposeMajority(proposal: any) {3753 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);3754 }37553756 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {3757 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);3758 }37593760 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {3761 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);3762 }3763}37643765class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {3766 collective: string;37673768 constructor(helper: MoonbeamHelper, collective: string) {3769 super(helper);37703771 this.collective = collective;3772 }37733774 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {3775 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);3776 }37773778 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {3779 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);3780 }37813782 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {3783 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);3784 }37853786 async proposalCount() {3787 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));3788 }3789}37903791export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;3792export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;37933794export class UniqueHelper extends ChainHelperBase {3795 balance: BalanceGroup<UniqueHelper>;3796 collection: CollectionGroup;3797 nft: NFTGroup;3798 rft: RFTGroup;3799 ft: FTGroup;3800 staking: StakingGroup;3801 scheduler: SchedulerGroup;3802 collatorSelection: CollatorSelectionGroup;3803 council: ICollectiveGroup;3804 technicalCommittee: ICollectiveGroup;3805 fellowship: IFellowshipGroup;3806 democracy: DemocracyGroup;3807 preimage: PreimageGroup;3808 foreignAssets: ForeignAssetsGroup;3809 xcm: XcmGroup<UniqueHelper>;3810 xTokens: XTokensGroup<UniqueHelper>;3811 tokens: TokensGroup<UniqueHelper>;38123813 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3814 super(logger, options.helperBase ?? UniqueHelper);38153816 this.balance = new BalanceGroup(this);3817 this.collection = new CollectionGroup(this);3818 this.nft = new NFTGroup(this);3819 this.rft = new RFTGroup(this);3820 this.ft = new FTGroup(this);3821 this.staking = new StakingGroup(this);3822 this.scheduler = new SchedulerGroup(this);3823 this.collatorSelection = new CollatorSelectionGroup(this);3824 this.council = {3825 collective: new CollectiveGroup(this, 'council'),3826 membership: new CollectiveMembershipGroup(this, 'councilMembership'),3827 };3828 this.technicalCommittee = {3829 collective: new CollectiveGroup(this, 'technicalCommittee'),3830 membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),3831 };3832 this.fellowship = {3833 collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),3834 referenda: new ReferendaGroup(this, 'fellowshipReferenda'),3835 };3836 this.democracy = new DemocracyGroup(this);3837 this.preimage = new PreimageGroup(this);3838 this.foreignAssets = new ForeignAssetsGroup(this);3839 this.xcm = new XcmGroup(this, 'polkadotXcm');3840 this.xTokens = new XTokensGroup(this);3841 this.tokens = new TokensGroup(this);3842 }38433844 getSudo<T extends UniqueHelper>() {3845 // eslint-disable-next-line @typescript-eslint/naming-convention3846 const SudoHelperType = SudoHelper(this.helperBase);3847 return this.clone(SudoHelperType) as T;3848 }3849}38503851export class XcmChainHelper extends ChainHelperBase {3852 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {3853 const wsProvider = new WsProvider(wsEndpoint);3854 this.api = new ApiPromise({3855 provider: wsProvider,3856 });3857 await this.api.isReadyOrError;3858 this.network = await UniqueHelper.detectNetwork(this.api);3859 }3860}38613862export class RelayHelper extends XcmChainHelper {3863 balance: SubstrateBalanceGroup<RelayHelper>;3864 xcm: XcmGroup<RelayHelper>;38653866 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3867 super(logger, options.helperBase ?? RelayHelper);38683869 this.balance = new SubstrateBalanceGroup(this);3870 this.xcm = new XcmGroup(this, 'xcmPallet');3871 }3872}38733874export class WestmintHelper extends XcmChainHelper {3875 balance: SubstrateBalanceGroup<WestmintHelper>;3876 xcm: XcmGroup<WestmintHelper>;3877 assets: AssetsGroup<WestmintHelper>;3878 xTokens: XTokensGroup<WestmintHelper>;38793880 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3881 super(logger, options.helperBase ?? WestmintHelper);38823883 this.balance = new SubstrateBalanceGroup(this);3884 this.xcm = new XcmGroup(this, 'polkadotXcm');3885 this.assets = new AssetsGroup(this);3886 this.xTokens = new XTokensGroup(this);3887 }3888}38893890export class MoonbeamHelper extends XcmChainHelper {3891 balance: EthereumBalanceGroup<MoonbeamHelper>;3892 assetManager: MoonbeamAssetManagerGroup;3893 assets: AssetsGroup<MoonbeamHelper>;3894 xTokens: XTokensGroup<MoonbeamHelper>;3895 democracy: MoonbeamDemocracyGroup;3896 collective: {3897 council: MoonbeamCollectiveGroup,3898 techCommittee: MoonbeamCollectiveGroup,3899 };39003901 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3902 super(logger, options.helperBase ?? MoonbeamHelper);39033904 this.balance = new EthereumBalanceGroup(this);3905 this.assetManager = new MoonbeamAssetManagerGroup(this);3906 this.assets = new AssetsGroup(this);3907 this.xTokens = new XTokensGroup(this);3908 this.democracy = new MoonbeamDemocracyGroup(this, options);3909 this.collective = {3910 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),3911 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),3912 };3913 }3914}39153916export class AstarHelper extends XcmChainHelper {3917 balance: SubstrateBalanceGroup<AstarHelper>;3918 assets: AssetsGroup<AstarHelper>;3919 xcm: XcmGroup<AstarHelper>;39203921 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3922 super(logger, options.helperBase ?? AstarHelper);39233924 this.balance = new SubstrateBalanceGroup(this);3925 this.assets = new AssetsGroup(this);3926 this.xcm = new XcmGroup(this, 'polkadotXcm');3927 }39283929 getSudo<T extends UniqueHelper>() {3930 // eslint-disable-next-line @typescript-eslint/naming-convention3931 const SudoHelperType = SudoHelper(this.helperBase);3932 return this.clone(SudoHelperType) as T;3933 }3934}39353936export class AcalaHelper extends XcmChainHelper {3937 balance: SubstrateBalanceGroup<AcalaHelper>;3938 assetRegistry: AcalaAssetRegistryGroup;3939 xTokens: XTokensGroup<AcalaHelper>;3940 tokens: TokensGroup<AcalaHelper>;3941 xcm: XcmGroup<AcalaHelper>;39423943 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {3944 super(logger, options.helperBase ?? AcalaHelper);39453946 this.balance = new SubstrateBalanceGroup(this);3947 this.assetRegistry = new AcalaAssetRegistryGroup(this);3948 this.xTokens = new XTokensGroup(this);3949 this.tokens = new TokensGroup(this);3950 this.xcm = new XcmGroup(this, 'polkadotXcm');3951 }39523953 getSudo<T extends AcalaHelper>() {3954 // eslint-disable-next-line @typescript-eslint/naming-convention3955 const SudoHelperType = SudoHelper(this.helperBase);3956 return this.clone(SudoHelperType) as T;3957 }3958}39593960// eslint-disable-next-line @typescript-eslint/naming-convention3961function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {3962 return class extends Base {3963 scheduleFn: 'schedule' | 'scheduleAfter';3964 blocksNum: number;3965 options: ISchedulerOptions;39663967 constructor(...args: any[]) {3968 const logger = args[0] as ILogger;3969 const options = args[1] as {3970 scheduleFn: 'schedule' | 'scheduleAfter',3971 blocksNum: number,3972 options: ISchedulerOptions3973 };39743975 super(logger);39763977 this.scheduleFn = options.scheduleFn;3978 this.blocksNum = options.blocksNum;3979 this.options = options.options;3980 }39813982 executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {3983 const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);39843985 const mandatorySchedArgs = [3986 this.blocksNum,3987 this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,3988 this.options.priority ?? null,3989 scheduledTx,3990 ];39913992 let schedArgs;3993 let scheduleFn;39943995 if(this.options.scheduledId) {3996 schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];39973998 if(this.scheduleFn == 'schedule') {3999 scheduleFn = 'scheduleNamed';4000 } else if(this.scheduleFn == 'scheduleAfter') {4001 scheduleFn = 'scheduleNamedAfter';4002 }4003 } else {4004 schedArgs = mandatorySchedArgs;4005 scheduleFn = this.scheduleFn;4006 }40074008 const extrinsic = 'api.tx.scheduler.' + scheduleFn;40094010 return super.executeExtrinsic(4011 sender,4012 extrinsic as any,4013 schedArgs,4014 expectSuccess,4015 );4016 }4017 };4018}40194020// eslint-disable-next-line @typescript-eslint/naming-convention4021function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {4022 return class extends Base {4023 constructor(...args: any[]) {4024 super(...args);4025 }40264027 async executeExtrinsic(4028 sender: IKeyringPair,4029 extrinsic: string,4030 params: any[],4031 expectSuccess?: boolean,4032 options: Partial<SignerOptions> | null = null,4033 ): Promise<ITransactionResult> {4034 const call = this.constructApiCall(extrinsic, params);4035 const result = await super.executeExtrinsic(4036 sender,4037 'api.tx.sudo.sudo',4038 [call],4039 expectSuccess,4040 options,4041 );40424043 if(result.status === 'Fail') return result;40444045 const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;4046 if(data.isErr) {4047 if(data.asErr.isModule) {4048 const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;4049 const metaError = super.getApi()?.registry.findMetaError(error);4050 throw new Error(`${metaError.section}.${metaError.name}`);4051 } else if(data.asErr.isToken) {4052 throw new Error(`Token: ${data.asErr.asToken}`);4053 }4054 // May be [object Object] in case of unhandled non-unit enum4055 throw new Error(`Misc: ${data.asErr.toHuman()}`);4056 }4057 return result;4058 }4059 };4060}40614062export class UniqueBaseCollection {4063 helper: UniqueHelper;4064 collectionId: number;40654066 constructor(collectionId: number, uniqueHelper: UniqueHelper) {4067 this.collectionId = collectionId;4068 this.helper = uniqueHelper;4069 }40704071 async getData() {4072 return await this.helper.collection.getData(this.collectionId);4073 }40744075 async getLastTokenId() {4076 return await this.helper.collection.getLastTokenId(this.collectionId);4077 }40784079 async doesTokenExist(tokenId: number) {4080 return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);4081 }40824083 async getAdmins() {4084 return await this.helper.collection.getAdmins(this.collectionId);4085 }40864087 async getAllowList() {4088 return await this.helper.collection.getAllowList(this.collectionId);4089 }40904091 async getEffectiveLimits() {4092 return await this.helper.collection.getEffectiveLimits(this.collectionId);4093 }40944095 async getProperties(propertyKeys?: string[] | null) {4096 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);4097 }40984099 async getPropertiesConsumedSpace() {4100 return await this.helper.collection.getPropertiesConsumedSpace(this.collectionId);4101 }41024103 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {4104 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);4105 }41064107 async getOptions() {4108 return await this.helper.collection.getCollectionOptions(this.collectionId);4109 }41104111 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {4112 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);4113 }41144115 async confirmSponsorship(signer: TSigner) {4116 return await this.helper.collection.confirmSponsorship(signer, this.collectionId);4117 }41184119 async removeSponsor(signer: TSigner) {4120 return await this.helper.collection.removeSponsor(signer, this.collectionId);4121 }41224123 async setLimits(signer: TSigner, limits: ICollectionLimits) {4124 return await this.helper.collection.setLimits(signer, this.collectionId, limits);4125 }41264127 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {4128 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);4129 }41304131 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {4132 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);4133 }41344135 async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {4136 return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);4137 }41384139 async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {4140 return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);4141 }41424143 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {4144 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);4145 }41464147 async setProperties(signer: TSigner, properties: IProperty[]) {4148 return await this.helper.collection.setProperties(signer, this.collectionId, properties);4149 }41504151 async deleteProperties(signer: TSigner, propertyKeys: string[]) {4152 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);4153 }41544155 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {4156 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);4157 }41584159 async enableNesting(signer: TSigner, permissions: INestingPermissions) {4160 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);4161 }41624163 async disableNesting(signer: TSigner) {4164 return await this.helper.collection.disableNesting(signer, this.collectionId);4165 }41664167 async burn(signer: TSigner) {4168 return await this.helper.collection.burn(signer, this.collectionId);4169 }41704171 scheduleAt<T extends UniqueHelper>(4172 executionBlockNumber: number,4173 options: ISchedulerOptions = {},4174 ) {4175 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4176 return new UniqueBaseCollection(this.collectionId, scheduledHelper);4177 }41784179 scheduleAfter<T extends UniqueHelper>(4180 blocksBeforeExecution: number,4181 options: ISchedulerOptions = {},4182 ) {4183 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4184 return new UniqueBaseCollection(this.collectionId, scheduledHelper);4185 }41864187 getSudo<T extends UniqueHelper>() {4188 return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());4189 }4190}419141924193export class UniqueNFTCollection extends UniqueBaseCollection {4194 getTokenObject(tokenId: number) {4195 return new UniqueNFToken(tokenId, this);4196 }41974198 async getTokensByAddress(addressObj: ICrossAccountId) {4199 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);4200 }42014202 async getToken(tokenId: number, blockHashAt?: string) {4203 return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);4204 }42054206 async getTokenOwner(tokenId: number, blockHashAt?: string) {4207 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);4208 }42094210 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {4211 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);4212 }42134214 async getTokenChildren(tokenId: number, blockHashAt?: string) {4215 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);4216 }42174218 async getPropertyPermissions(propertyKeys: string[] | null = null) {4219 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);4220 }42214222 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {4223 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);4224 }42254226 async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {4227 const api = this.helper.getApi();4228 const props = (await api.query.nonfungible.tokenProperties(this.collectionId, tokenId)).toJSON();42294230 return (props! as any).consumedSpace;4231 }42324233 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {4234 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);4235 }42364237 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4238 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);4239 }42404241 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {4242 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);4243 }42444245 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {4246 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);4247 }42484249 async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {4250 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});4251 }42524253 async mintMultipleTokens(signer: TSigner, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]) {4254 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);4255 }42564257 async burnToken(signer: TSigner, tokenId: number) {4258 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);4259 }42604261 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {4262 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);4263 }42644265 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {4266 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);4267 }42684269 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {4270 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);4271 }42724273 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {4274 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);4275 }42764277 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {4278 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);4279 }42804281 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4282 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);4283 }42844285 scheduleAt<T extends UniqueHelper>(4286 executionBlockNumber: number,4287 options: ISchedulerOptions = {},4288 ) {4289 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4290 return new UniqueNFTCollection(this.collectionId, scheduledHelper);4291 }42924293 scheduleAfter<T extends UniqueHelper>(4294 blocksBeforeExecution: number,4295 options: ISchedulerOptions = {},4296 ) {4297 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4298 return new UniqueNFTCollection(this.collectionId, scheduledHelper);4299 }43004301 getSudo<T extends UniqueHelper>() {4302 return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());4303 }4304}430543064307export class UniqueRFTCollection extends UniqueBaseCollection {4308 getTokenObject(tokenId: number) {4309 return new UniqueRFToken(tokenId, this);4310 }43114312 async getToken(tokenId: number, blockHashAt?: string) {4313 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);4314 }43154316 async getTokenOwner(tokenId: number, blockHashAt?: string) {4317 return await this.helper.rft.getTokenOwner(this.collectionId, tokenId, blockHashAt);4318 }43194320 async getTokensByAddress(addressObj: ICrossAccountId) {4321 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);4322 }43234324 async getTop10TokenOwners(tokenId: number) {4325 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);4326 }43274328 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {4329 return await this.helper.rft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);4330 }43314332 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {4333 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);4334 }43354336 async getTokenTotalPieces(tokenId: number) {4337 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);4338 }43394340 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4341 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);4342 }43434344 async getPropertyPermissions(propertyKeys: string[] | null = null) {4345 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);4346 }43474348 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {4349 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);4350 }43514352 async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {4353 const api = this.helper.getApi();4354 const props = (await api.query.refungible.tokenProperties(this.collectionId, tokenId)).toJSON();43554356 return (props! as any).consumedSpace;4357 }43584359 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount = 1n) {4360 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);4361 }43624363 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4364 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);4365 }43664367 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {4368 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);4369 }43704371 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {4372 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);4373 }43744375 async mintToken(signer: TSigner, pieces = 1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {4376 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});4377 }43784379 async mintMultipleTokens(signer: TSigner, tokens: { pieces: bigint, owner: ICrossAccountId, properties?: IProperty[] }[]) {4380 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);4381 }43824383 async burnToken(signer: TSigner, tokenId: number, amount = 1n) {4384 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);4385 }43864387 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n) {4388 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);4389 }43904391 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {4392 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);4393 }43944395 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {4396 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);4397 }43984399 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {4400 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);4401 }44024403 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {4404 return await this.helper.rft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);4405 }44064407 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4408 return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);4409 }44104411 scheduleAt<T extends UniqueHelper>(4412 executionBlockNumber: number,4413 options: ISchedulerOptions = {},4414 ) {4415 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4416 return new UniqueRFTCollection(this.collectionId, scheduledHelper);4417 }44184419 scheduleAfter<T extends UniqueHelper>(4420 blocksBeforeExecution: number,4421 options: ISchedulerOptions = {},4422 ) {4423 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4424 return new UniqueRFTCollection(this.collectionId, scheduledHelper);4425 }44264427 getSudo<T extends UniqueHelper>() {4428 return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());4429 }4430}443144324433export class UniqueFTCollection extends UniqueBaseCollection {4434 async getBalance(addressObj: ICrossAccountId) {4435 return await this.helper.ft.getBalance(this.collectionId, addressObj);4436 }44374438 async getTotalPieces() {4439 return await this.helper.ft.getTotalPieces(this.collectionId);4440 }44414442 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4443 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);4444 }44454446 async getTop10Owners() {4447 return await this.helper.ft.getTop10Owners(this.collectionId);4448 }44494450 async mint(signer: TSigner, amount = 1n, owner: ICrossAccountId = {Substrate: signer.address}) {4451 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);4452 }44534454 async mintWithOneOwner(signer: TSigner, tokens: { value: bigint }[], owner: ICrossAccountId = {Substrate: signer.address}) {4455 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);4456 }44574458 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4459 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);4460 }44614462 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4463 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);4464 }44654466 async burnTokens(signer: TSigner, amount = 1n) {4467 return await this.helper.ft.burnTokens(signer, this.collectionId, amount);4468 }44694470 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {4471 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);4472 }44734474 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4475 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);4476 }44774478 scheduleAt<T extends UniqueHelper>(4479 executionBlockNumber: number,4480 options: ISchedulerOptions = {},4481 ) {4482 const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);4483 return new UniqueFTCollection(this.collectionId, scheduledHelper);4484 }44854486 scheduleAfter<T extends UniqueHelper>(4487 blocksBeforeExecution: number,4488 options: ISchedulerOptions = {},4489 ) {4490 const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);4491 return new UniqueFTCollection(this.collectionId, scheduledHelper);4492 }44934494 getSudo<T extends UniqueHelper>() {4495 return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());4496 }4497}449844994500export class UniqueBaseToken {4501 collection: UniqueNFTCollection | UniqueRFTCollection;4502 collectionId: number;4503 tokenId: number;45044505 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {4506 this.collection = collection;4507 this.collectionId = collection.collectionId;4508 this.tokenId = tokenId;4509 }45104511 async getNextSponsored(addressObj: ICrossAccountId) {4512 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);4513 }45144515 async getProperties(propertyKeys?: string[] | null) {4516 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);4517 }45184519 async getTokenPropertiesConsumedSpace() {4520 return await this.collection.getTokenPropertiesConsumedSpace(this.tokenId);4521 }45224523 async setProperties(signer: TSigner, properties: IProperty[]) {4524 return await this.collection.setTokenProperties(signer, this.tokenId, properties);4525 }45264527 async deleteProperties(signer: TSigner, propertyKeys: string[]) {4528 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);4529 }45304531 async doesExist() {4532 return await this.collection.doesTokenExist(this.tokenId);4533 }45344535 nestingAccount() {4536 return this.collection.helper.util.getTokenAccount(this);4537 }45384539 scheduleAt<T extends UniqueHelper>(4540 executionBlockNumber: number,4541 options: ISchedulerOptions = {},4542 ) {4543 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4544 return new UniqueBaseToken(this.tokenId, scheduledCollection);4545 }45464547 scheduleAfter<T extends UniqueHelper>(4548 blocksBeforeExecution: number,4549 options: ISchedulerOptions = {},4550 ) {4551 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4552 return new UniqueBaseToken(this.tokenId, scheduledCollection);4553 }45544555 getSudo<T extends UniqueHelper>() {4556 return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());4557 }4558}455945604561export class UniqueNFToken extends UniqueBaseToken {4562 collection: UniqueNFTCollection;45634564 constructor(tokenId: number, collection: UniqueNFTCollection) {4565 super(tokenId, collection);4566 this.collection = collection;4567 }45684569 async getData(blockHashAt?: string) {4570 return await this.collection.getToken(this.tokenId, blockHashAt);4571 }45724573 async getOwner(blockHashAt?: string) {4574 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);4575 }45764577 async getTopmostOwner(blockHashAt?: string) {4578 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);4579 }45804581 async getChildren(blockHashAt?: string) {4582 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);4583 }45844585 async nest(signer: TSigner, toTokenObj: IToken) {4586 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);4587 }45884589 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4590 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);4591 }45924593 async transfer(signer: TSigner, addressObj: ICrossAccountId) {4594 return await this.collection.transferToken(signer, this.tokenId, addressObj);4595 }45964597 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {4598 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);4599 }46004601 async approve(signer: TSigner, toAddressObj: ICrossAccountId) {4602 return await this.collection.approveToken(signer, this.tokenId, toAddressObj);4603 }46044605 async isApproved(toAddressObj: ICrossAccountId) {4606 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);4607 }46084609 async burn(signer: TSigner) {4610 return await this.collection.burnToken(signer, this.tokenId);4611 }46124613 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {4614 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);4615 }46164617 scheduleAt<T extends UniqueHelper>(4618 executionBlockNumber: number,4619 options: ISchedulerOptions = {},4620 ) {4621 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4622 return new UniqueNFToken(this.tokenId, scheduledCollection);4623 }46244625 scheduleAfter<T extends UniqueHelper>(4626 blocksBeforeExecution: number,4627 options: ISchedulerOptions = {},4628 ) {4629 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4630 return new UniqueNFToken(this.tokenId, scheduledCollection);4631 }46324633 getSudo<T extends UniqueHelper>() {4634 return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());4635 }4636}46374638export class UniqueRFToken extends UniqueBaseToken {4639 collection: UniqueRFTCollection;46404641 constructor(tokenId: number, collection: UniqueRFTCollection) {4642 super(tokenId, collection);4643 this.collection = collection;4644 }46454646 async getData(blockHashAt?: string) {4647 return await this.collection.getToken(this.tokenId, blockHashAt);4648 }46494650 async getOwner(blockHashAt?: string) {4651 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);4652 }46534654 async getTop10Owners() {4655 return await this.collection.getTop10TokenOwners(this.tokenId);4656 }46574658 async getTopmostOwner(blockHashAt?: string) {4659 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);4660 }46614662 async nest(signer: TSigner, toTokenObj: IToken) {4663 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);4664 }46654666 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {4667 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);4668 }46694670 async getBalance(addressObj: ICrossAccountId) {4671 return await this.collection.getTokenBalance(this.tokenId, addressObj);4672 }46734674 async getTotalPieces() {4675 return await this.collection.getTokenTotalPieces(this.tokenId);4676 }46774678 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {4679 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);4680 }46814682 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount = 1n) {4683 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);4684 }46854686 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {4687 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);4688 }46894690 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {4691 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);4692 }46934694 async repartition(signer: TSigner, amount: bigint) {4695 return await this.collection.repartitionToken(signer, this.tokenId, amount);4696 }46974698 async burn(signer: TSigner, amount = 1n) {4699 return await this.collection.burnToken(signer, this.tokenId, amount);4700 }47014702 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {4703 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);4704 }47054706 scheduleAt<T extends UniqueHelper>(4707 executionBlockNumber: number,4708 options: ISchedulerOptions = {},4709 ) {4710 const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);4711 return new UniqueRFToken(this.tokenId, scheduledCollection);4712 }47134714 scheduleAfter<T extends UniqueHelper>(4715 blocksBeforeExecution: number,4716 options: ISchedulerOptions = {},4717 ) {4718 const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);4719 return new UniqueRFToken(this.tokenId, scheduledCollection);4720 }47214722 getSudo<T extends UniqueHelper>() {4723 return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());4724 }4725}