difftreelog
test balances.transfer => balances.transferKeepAlive
in: master
4 files changed
js-packages/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/js-packages/playgrounds/unique.dev.ts
+++ b/js-packages/playgrounds/unique.dev.ts
@@ -696,7 +696,7 @@
const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
accounts.push(recipient);
if(balance !== 0n) {
- const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);
+ const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, balance * tokenNominal]);
transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));
nonce++;
}
@@ -749,7 +749,7 @@
const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
accounts.push(recipient);
if(withBalance !== 0n) {
- const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);
+ const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, withBalance * tokenNominal]);
transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));
nonce++;
}
js-packages/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 type {SignerOptions} from '@polkadot/api/types';10import type {AugmentedSubmittables} from '@polkadot/api-base/types/submittable';11import type {ApiInterfaceEvents} from '@polkadot/api/types';12import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';13import type {IKeyringPair} from '@polkadot/types/types';14import {hexToU8a} from '@polkadot/util/hex';15import {u8aConcat} from '@polkadot/util/u8a';16import type {17 IApiListeners,18 IBlock,19 IEvent,20 IChainProperties,21 ICollectionCreationOptions,22 ICollectionLimits,23 ICollectionPermissions,24 ICrossAccountId,25 ICrossAccountIdLower,26 ILogger,27 INestingPermissions,28 IProperty,29 IStakingInfo,30 ISubstrateBalance,31 IToken,32 ITokenPropertyPermission,33 ITransactionResult,34 IUniqueHelperLog,35 TApiAllowedListeners,36 TEthereumAccount,37 TSigner,38 TSubstrateAccount,39 TNetworks,40 IEthCrossAccountId,41} from './types.js';42import type {RuntimeDispatchInfo} from '@polkadot/types/interfaces';4344export class CrossAccountId {45 account: ICrossAccountId;4647 constructor(account: ICrossAccountId) {48 this.account = account;49 }5051 static fromKeyring(account: IKeyringPair, domain: 'Substrate' | 'Ethereum' = 'Substrate') {52 switch (domain) {53 case 'Substrate': return new CrossAccountId({Substrate: account.address});54 case 'Ethereum': return new CrossAccountId({Substrate: account.address}).toEthereum();55 }56 }5758 static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId {59 if('substrate' in address) return new CrossAccountId({Substrate: address.substrate});60 else return new CrossAccountId({Ethereum: address.ethereum});61 }6263 static normalizeSubstrateAddress(address: {Substrate: TSubstrateAccount}, ss58Format = 42): TSubstrateAccount {64 return encodeAddress(decodeAddress(address.Substrate), ss58Format);65 }6667 static withNormalizedSubstrate(address: ICrossAccountId, ss58Format = 42): ICrossAccountId {68 if('Substrate' in address) return {Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)};69 return address;70 }7172 withNormalizedSubstrate(ss58Format = 42): CrossAccountId {73 if('Substrate' in this.account) this.account = CrossAccountId.withNormalizedSubstrate(this.account, ss58Format);74 return this;75 }7677 static translateSubToEth(address: TSubstrateAccount): TEthereumAccount {78 return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(address), i => i.toString(16).padStart(2, '0')).join(''));79 }8081 toEthereum(): CrossAccountId {82 this.account = CrossAccountId.toEthereum(this.account);83 return this;84 }8586 static toEthereum(account: ICrossAccountId): ICrossAccountId {87 if('Substrate' in account) return {Ethereum: CrossAccountId.translateSubToEth(account.Substrate)};88 return account;89 }9091 static translateEthToSub(address: TEthereumAccount, ss58Format?: number): TSubstrateAccount {92 return evmToAddress(address, ss58Format);93 }9495 toSubstrate(ss58Format?: number): CrossAccountId {96 this.account = CrossAccountId.toSubstrate(this.account, ss58Format);97 return this;98 }99100 static toSubstrate(account: ICrossAccountId, ss58Format?: number): ICrossAccountId {101 if('Ethereum' in account) return {Substrate: CrossAccountId.translateEthToSub(account.Ethereum, ss58Format)};102 return account;103 }104105 toLowerCase(): CrossAccountId {106 this.account = CrossAccountId.toLowerCase(this.account);107 return this;108 }109110 static toLowerCase(account: ICrossAccountId) {111 if('Substrate' in account) return {Substrate: account.Substrate.toLowerCase()};112 if('Ethereum' in account) return {Ethereum: account.Ethereum.toLowerCase()};113 return account;114 }115116 toICrossAccountId(): ICrossAccountId {117 return this.account;118 }119}120121const nesting = {122 toChecksumAddress(address: string): string {123 if(typeof address === 'undefined') return '';124125 if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);126127 address = address.toLowerCase().replace(/^0x/i, '');128 const addressHash = keccakAsHex(address).replace(/^0x/i, '');129 const checksumAddress = ['0x'];130131 for(let i = 0; i < address.length; i++) {132 // If ith character is 8 to f then make it uppercase133 if(parseInt(addressHash[i], 16) > 7) {134 checksumAddress.push(address[i].toUpperCase());135 } else {136 checksumAddress.push(address[i]);137 }138 }139 return checksumAddress.join('');140 },141 tokenIdToAddress(collectionId: number, tokenId: number) {142 return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8,'0')}${tokenId.toString(16).padStart(8,'0')}`);143 },144};145146class UniqueUtil {147 static transactionStatus = {148 NOT_READY: 'NotReady',149 FAIL: 'Fail',150 SUCCESS: 'Success',151 };152153 static chainLogType = {154 EXTRINSIC: 'extrinsic',155 RPC: 'rpc',156 };157158 static getTokenAccount(token: IToken): ICrossAccountId {159 return {Ethereum: this.getTokenAddress(token)};160 }161162 static getTokenAddress(token: IToken): string {163 return nesting.tokenIdToAddress(token.collectionId, token.tokenId);164 }165166 static getDefaultLogger(): ILogger {167 return {168 log(msg: any, level = 'INFO') {169 console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));170 },171 level: {172 ERROR: 'ERROR',173 WARNING: 'WARNING',174 INFO: 'INFO',175 },176 };177 }178179 static vec2str(arr: string[] | number[]) {180 return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');181 }182183 static str2vec(string: string) {184 if(typeof string !== 'string') return string;185 return Array.from(string).map(x => x.charCodeAt(0));186 }187188 static fromSeed(seed: string, ss58Format = 42) {189 const keyring = new Keyring({type: 'sr25519', ss58Format});190 return keyring.addFromUri(seed);191 }192193 static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number {194 if(creationResult.status !== this.transactionStatus.SUCCESS) {195 throw Error('Unable to create collection!');196 }197198 let collectionId = null;199 creationResult.result.events.forEach(({event: {data, method, section}}) => {200 if((section === 'common') && (method === 'CollectionCreated')) {201 collectionId = parseInt(data[0].toString(), 10);202 }203 });204205 if(collectionId === null) {206 throw Error('No CollectionCreated event was found!');207 }208209 return collectionId;210 }211212 static extractTokensFromCreationResult(creationResult: ITransactionResult): {213 success: boolean,214 tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],215 } {216 if(creationResult.status !== this.transactionStatus.SUCCESS) {217 throw Error('Unable to create tokens!');218 }219 let success = false;220 const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];221 creationResult.result.events.forEach(({event: {data, method, section}}) => {222 if(method === 'ExtrinsicSuccess') {223 success = true;224 } else if((section === 'common') && (method === 'ItemCreated')) {225 tokens.push({226 collectionId: parseInt(data[0].toString(), 10),227 tokenId: parseInt(data[1].toString(), 10),228 owner: data[2].toHuman(),229 amount: data[3].toBigInt(),230 });231 }232 });233 return {success, tokens};234 }235236 static extractTokensFromBurnResult(burnResult: ITransactionResult): {237 success: boolean,238 tokens: { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[],239 } {240 if(burnResult.status !== this.transactionStatus.SUCCESS) {241 throw Error('Unable to burn tokens!');242 }243 let success = false;244 const tokens = [] as { collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint }[];245 burnResult.result.events.forEach(({event: {data, method, section}}: any) => {246 if(method === 'ExtrinsicSuccess') {247 success = true;248 } else if((section === 'common') && (method === 'ItemDestroyed')) {249 tokens.push({250 collectionId: parseInt(data[0].toString(), 10),251 tokenId: parseInt(data[1].toString(), 10),252 owner: data[2].toHuman(),253 amount: data[3].toBigInt(),254 });255 }256 });257 return {success, tokens};258 }259260 static findCollectionInEvents(events: { event: IEvent }[], collectionId: number, expectedSection: string, expectedMethod: string): boolean {261 let eventId = null;262 events.forEach(({event: {data, method, section}}) => {263 if((section === expectedSection) && (method === expectedMethod)) {264 eventId = parseInt(data[0].toString(), 10);265 }266 });267268 if(eventId === null) {269 throw Error(`No ${expectedMethod} event was found!`);270 }271 return eventId === collectionId;272 }273274 static isTokenTransferSuccess(events: { event: IEvent }[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {275 const normalizeAddress = (address: string | ICrossAccountId) => {276 if(typeof address === 'string') return address;277 if('Substrate' in address) return CrossAccountId.withNormalizedSubstrate(address);278 if('Ethereum' in address) return CrossAccountId.toLowerCase(address);279 return address;280 };281 let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;282 events.forEach(({event: {data, method, section}}) => {283 if((section === 'common') && (method === 'Transfer')) {284 transfer = {285 collectionId: data[0].toJSON(),286 tokenId: data[1].toJSON(),287 from: normalizeAddress(data[2].toHuman()),288 to: normalizeAddress(data[3].toHuman()),289 amount: BigInt(data[4].toJSON()),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 static extractIndex(index: any): [number, number] | string {316 if(index.toRawType() === '[u8;2]') return [index[0], index[1]];317 return index.toJSON();318 }319320 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 static toHuman(data: any) {335 return data && data.toHuman ? data.toHuman() : `${data}`;336 }337338 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 =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;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: any) => {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 if(!wsEndpoint) throw new Error('wsEndpoint was not set');500 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});501 await api.isReady;502503 const network = await this.detectNetwork(api);504505 await api.disconnect();506507 return network;508 }509510 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{511 api: ApiPromise;512 network: TNetworks;513 }> {514 if(typeof network === 'undefined' || network === null) network = 'opal';515 if(!wsEndpoint) throw new Error('wsEndpoint was not set');516 const supportedRPC = {517 opal: {518 unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,519 },520 quartz: {521 unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,522 },523 unique: {524 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,525 },526 rococo: {},527 westend: {},528 moonbeam: {},529 moonriver: {},530 acala: {},531 karura: {},532 westmint: {},533 };534 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);535 const rpc = supportedRPC[network] as any;536537 // TODO: investigate how to replace rpc in runtime538 // api._rpcCore.addUserInterfaces(rpc);539540 const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});541542 await api.isReadyOrError;543544 if(typeof listeners === 'undefined') listeners = {};545 for(const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {546 if(!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;547 api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);548 }549550 return {api, network};551 }552553 getTransactionStatus(data: { events: { event: IEvent }[], status: any }) {554 const {events, status} = data;555 if(status.isReady) {556 return this.transactionStatus.NOT_READY;557 }558 if(status.isBroadcast) {559 return this.transactionStatus.NOT_READY;560 }561 if(status.isInBlock || status.isFinalized) {562 const errors = events.filter(e => e.event.method === 'ExtrinsicFailed');563 if(errors.length > 0) {564 return this.transactionStatus.FAIL;565 }566 if(events.filter(e => e.event.method === 'ExtrinsicSuccess').length > 0) {567 return this.transactionStatus.SUCCESS;568 }569 }570571 return this.transactionStatus.FAIL;572 }573574 signTransaction(sender: TSigner, transaction: any, options: Partial<SignerOptions> | null = null, label = 'transaction') {575 const sign = (callback: any) => {576 if(options !== null) return transaction.signAndSend(sender, options, callback);577 return transaction.signAndSend(sender, callback);578 };579 // eslint-disable-next-line no-async-promise-executor580 return new Promise(async (resolve, reject) => {581 try {582 const unsub = await sign((result: any) => {583 const status = this.getTransactionStatus(result);584585 if(status === this.transactionStatus.SUCCESS) {586 this.logger.log(`${label} successful`);587 unsub();588 resolve({result, status, blockHash: result.status.asInBlock.toHuman()});589 } else if(status === this.transactionStatus.FAIL) {590 let moduleError = null;591592 if(result.hasOwnProperty('dispatchError')) {593 const dispatchError = result['dispatchError'];594595 if(dispatchError) {596 if(dispatchError.isModule) {597 const modErr = dispatchError.asModule;598 const errorMeta = dispatchError.registry.findMetaError(modErr);599600 moduleError = `${errorMeta.section}.${errorMeta.name}`;601 } else if(dispatchError.isToken) {602 moduleError = `Token: ${dispatchError.asToken}`;603 } else {604 // May be [object Object] in case of unhandled non-unit enum605 moduleError = `Misc: ${dispatchError.toHuman()}`;606 }607 } else {608 this.logger.log(result, this.logger.level.ERROR);609 }610 }611612 this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);613 unsub();614 reject({status, moduleError, result});615 }616 });617 } catch (e) {618 this.logger.log(e, this.logger.level.ERROR);619 reject(e);620 }621 });622 }623624 async signTransactionWithoutSending(signer: TSigner, tx: any) {625 const api = this.getApi();626 const signingInfo = await api.derive.tx.signingInfo(signer.address);627628 tx.sign(signer, {629 blockHash: api.genesisHash,630 genesisHash: api.genesisHash,631 runtimeVersion: api.runtimeVersion,632 nonce: signingInfo.nonce,633 });634635 return tx.toHex();636 }637638 async getPaymentInfo(signer: TSigner, tx: any, len: number | null) {639 const api = this.getApi();640 const signingInfo = await api.derive.tx.signingInfo(signer.address);641642 // We need to sign the tx because643 // unsigned transactions does not have an inclusion fee644 tx.sign(signer, {645 blockHash: api.genesisHash,646 genesisHash: api.genesisHash,647 runtimeVersion: api.runtimeVersion,648 nonce: signingInfo.nonce,649 });650651 if(len === null) {652 return (await this.callRpc('api.rpc.payment.queryInfo', [tx.toHex()])) as RuntimeDispatchInfo;653 } else {654 return (await api.call.transactionPaymentApi.queryInfo(tx, len)) as RuntimeDispatchInfo;655 }656 }657658 constructApiCall(apiCall: string, params: any[]) {659 if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);660 let call = this.getApi() as any;661 for(const part of apiCall.slice(4).split('.')) {662 call = call[part];663 if(!call) {664 const advice = part.includes('_') ? ' Looks like it needs to be converted to camel case.' : '';665 throw Error(`Function ${part} of api call ${apiCall} not found.${advice}`);666 }667 }668 return call(...params);669 }670671 encodeApiCall(apiCall: string, params: any[]) {672 return this.constructApiCall(apiCall, params).method.toHex();673 }674675 async executeExtrinsic<676 E extends string,677 V extends (678 ...args: any) => any = ForceFunction<679 Get2<680 AugmentedSubmittables<'promise'>,681 E, (...args: any) => Invalid682 >683 >684 >(685 sender: TSigner,686 extrinsic: `api.tx.${E}`,687 params: Parameters<V>,688 expectSuccess = true,689 options: Partial<SignerOptions> | null = null,/*, failureMessage='expected success'*/690 ): Promise<ITransactionResult> {691 if(this.api === null) throw Error('API not initialized');692693 const startTime = (new Date()).getTime();694 let result: ITransactionResult;695 let events: IEvent[] = [];696 try {697 result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), options, extrinsic) as ITransactionResult;698 events = this.eventHelper.extractEvents(result.result.events);699 const errorEvent = events.find((event) => event.method == 'ExecutedFailed' || event.method == 'CreatedFailed');700 if(errorEvent)701 throw Error(errorEvent.method + ': ' + extrinsic);702 }703 catch (e) {704 if(!(e as object).hasOwnProperty('status')) throw e;705 result = e as ITransactionResult;706 }707708 const endTime = (new Date()).getTime();709710 const log = {711 executedAt: endTime,712 executionTime: endTime - startTime,713 type: this.chainLogType.EXTRINSIC,714 status: result.status,715 call: extrinsic,716 signer: this.getSignerAddress(sender),717 params,718 } as IUniqueHelperLog;719720 let errorMessage = '';721722 if(result.status !== this.transactionStatus.SUCCESS) {723 if(result.moduleError) {724 errorMessage = typeof result.moduleError === 'string'725 ? result.moduleError726 : `${Object.keys(result.moduleError)[0]}: ${Object.values(result.moduleError)[0]}`;727 log.moduleError = errorMessage;728 }729 else if(result.result.dispatchError) log.dispatchError = result.result.dispatchError;730 }731 if(events.length > 0) log.events = events;732733 this.chainLog.push(log);734735 if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) {736 if(result.moduleError) throw Error(`${errorMessage}`);737 else if(result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError));738 }739 return result as any;740 }741 executeExtrinsicUncheckedWeight<742 E extends string,743 V extends (744 ...args: any) => any = ForceFunction<745 Get2<746 AugmentedSubmittables<'promise'>,747 E, (...args: any) => Invalid748 >749 >750 >(751 _sender: TSigner,752 _extrinsic: `api.tx.${E}`,753 _params: Parameters<V>,754 _expectSuccess = true,755 _options: Partial<SignerOptions> | null = null,/*, failureMessage='expected success'*/756 ): Promise<ITransactionResult> {757 throw new Error('executeExtrinsicUncheckedWeight only supported in sudo');758 }759760 async callRpc761 // TODO: make it strongly typed, or use api.query/api.rpc directly762 // <763 // K extends 'rpc' | 'query',764 // E extends string,765 // V extends (...args: any) => any = ForceFunction<766 // Get2<767 // K extends 'rpc' ? DecoratedRpc<'promise', RpcInterface> : QueryableStorage<'promise'>,768 // E, (...args: any) => Invalid<'not found'>769 // >770 // >,771 // P = Parameters<V>,772 // >773 (rpc: string, params?: any[]): Promise<any> {774775 if(typeof params === 'undefined') params = [] as any;776 if(this.api === null) throw Error('API not initialized');777 if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);778779 const startTime = (new Date()).getTime();780 let result;781 let error = null;782 const log = {783 type: this.chainLogType.RPC,784 call: rpc,785 params,786 } as any as IUniqueHelperLog;787788 try {789 result = await this.constructApiCall(rpc, params as any);790 }791 catch (e) {792 error = e;793 }794795 const endTime = (new Date()).getTime();796797 log.executedAt = endTime;798 log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';799 log.executionTime = endTime - startTime;800801 this.chainLog.push(log);802803 if(error !== null) throw error;804805 return result;806 }807808 getSignerAddress(signer: IKeyringPair | string): string {809 if(typeof signer === 'string') return signer;810 return signer.address;811 }812813 fetchAllPalletNames(): string[] {814 if(this.api === null) throw Error('API not initialized');815 return this.api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()).sort();816 }817818 fetchMissingPalletNames(requiredPallets: readonly string[]): string[] {819 const palletNames = this.fetchAllPalletNames();820 return requiredPallets.filter(p => !palletNames.includes(p));821 }822}823824825export class HelperGroup<T extends ChainHelperBase> {826 helper: T;827828 constructor(uniqueHelper: T) {829 this.helper = uniqueHelper;830 }831}832833834class CollectionGroup extends HelperGroup<UniqueHelper> {835 /**836 * Get number of blocks when sponsored transaction is available.837 *838 * @param collectionId ID of collection839 * @param tokenId ID of token840 * @param addressObj address for which the sponsorship is checked841 * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});842 * @returns number of blocks or null if sponsorship hasn't been set843 */844 async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {845 return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();846 }847848 /**849 * Get the number of created collections.850 *851 * @returns number of created collections852 */853 async getTotalCount(): Promise<number> {854 return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();855 }856857 /**858 * Get information about the collection with additional data,859 * including the number of tokens it contains, its administrators,860 * the normalized address of the collection's owner, and decoded name and description.861 *862 * @param collectionId ID of collection863 * @example await getData(2)864 * @returns collection information object865 */866 async getData(collectionId: number): Promise<{867 id: number;868 name: string;869 description: string;870 tokensCount: number;871 admins: CrossAccountId[];872 normalizedOwner: TSubstrateAccount;873 raw: any874 } | null> {875 const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);876 const humanCollection = collection.toHuman(), collectionData = {877 id: collectionId, name: null, description: null, tokensCount: 0, admins: [],878 raw: humanCollection,879 } as any, jsonCollection = collection.toJSON();880 if(humanCollection === null) return null;881 collectionData.raw.limits = jsonCollection.limits;882 collectionData.raw.permissions = jsonCollection.permissions;883 collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);884 for(const key of ['name', 'description']) {885 collectionData[key] = this.helper.util.vec2str(humanCollection[key]);886 }887888 collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode))889 ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId)890 : 0;891 collectionData.admins = await this.getAdmins(collectionId);892893 return collectionData;894 }895896 /**897 * Get the addresses of the collection's administrators, optionally normalized.898 *899 * @param collectionId ID of collection900 * @param normalize whether to normalize the addresses to the default ss58 format901 * @example await getAdmins(1)902 * @returns array of administrators903 */904 async getAdmins(collectionId: number, normalize = false): Promise<ICrossAccountId[]> {905 const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman() as ICrossAccountId[];906907 return normalize908 ? admins.map(address => CrossAccountId.withNormalizedSubstrate(address))909 : admins;910 }911912 /**913 * Get the addresses added to the collection allow-list, optionally normalized.914 * @param collectionId ID of collection915 * @param normalize whether to normalize the addresses to the default ss58 format916 * @example await getAllowList(1)917 * @returns array of allow-listed addresses918 */919 async getAllowList(collectionId: number, normalize = false): Promise<ICrossAccountId[]> {920 const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman() as ICrossAccountId[];921 return normalize922 ? allowListed.map(address => CrossAccountId.withNormalizedSubstrate(address))923 : allowListed;924 }925926 /**927 * Get the effective limits of the collection instead of null for default values928 *929 * @param collectionId ID of collection930 * @example await getEffectiveLimits(2)931 * @returns object of collection limits932 */933 async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {934 return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();935 }936937 /**938 * Burns the collection if the signer has sufficient permissions and collection is empty.939 *940 * @param signer keyring of signer941 * @param collectionId ID of collection942 * @example await helper.collection.burn(aliceKeyring, 3);943 * @returns ```true``` if extrinsic success, otherwise ```false```944 */945 async burn(signer: TSigner, collectionId: number): Promise<boolean> {946 const result = await this.helper.executeExtrinsic(947 signer,948 'api.tx.unique.destroyCollection', [collectionId],949 true,950 );951952 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed');953 }954955 /**956 * Sets the sponsor for the collection (Requires the Substrate address). Needs confirmation by the sponsor.957 *958 * @param signer keyring of signer959 * @param collectionId ID of collection960 * @param sponsorAddress Sponsor substrate address961 * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")962 * @returns ```true``` if extrinsic success, otherwise ```false```963 */964 async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount): Promise<boolean> {965 const result = await this.helper.executeExtrinsic(966 signer,967 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],968 true,969 );970971 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorSet');972 }973974 /**975 * Confirms consent to sponsor the collection on behalf of the signer.976 *977 * @param signer keyring of signer978 * @param collectionId ID of collection979 * @example confirmSponsorship(aliceKeyring, 10)980 * @returns ```true``` if extrinsic success, otherwise ```false```981 */982 async confirmSponsorship(signer: TSigner, collectionId: number): Promise<boolean> {983 const result = await this.helper.executeExtrinsic(984 signer,985 'api.tx.unique.confirmSponsorship', [collectionId],986 true,987 );988989 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'SponsorshipConfirmed');990 }991992 /**993 * Removes the sponsor of a collection, regardless if it consented or not.994 *995 * @param signer keyring of signer996 * @param collectionId ID of collection997 * @example removeSponsor(aliceKeyring, 10)998 * @returns ```true``` if extrinsic success, otherwise ```false```999 */1000 async removeSponsor(signer: TSigner, collectionId: number): Promise<boolean> {1001 const result = await this.helper.executeExtrinsic(1002 signer,1003 'api.tx.unique.removeCollectionSponsor', [collectionId],1004 true,1005 );10061007 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorRemoved');1008 }10091010 /**1011 * Sets the limits of the collection. At least one limit must be specified for a correct call.1012 *1013 * @param signer keyring of signer1014 * @param collectionId ID of collection1015 * @param limits collection limits object1016 * @example1017 * await setLimits(1018 * aliceKeyring,1019 * 10,1020 * {1021 * sponsorTransferTimeout: 0,1022 * ownerCanDestroy: false1023 * }1024 * )1025 * @returns ```true``` if extrinsic success, otherwise ```false```1026 */1027 async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits): Promise<boolean> {1028 const result = await this.helper.executeExtrinsic(1029 signer,1030 'api.tx.unique.setCollectionLimits', [collectionId, limits],1031 true,1032 );10331034 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionLimitSet');1035 }10361037 /**1038 * Changes the owner of the collection to the new Substrate address.1039 *1040 * @param signer keyring of signer1041 * @param collectionId ID of collection1042 * @param ownerAddress substrate address of new owner1043 * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")1044 * @returns ```true``` if extrinsic success, otherwise ```false```1045 */1046 async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount): Promise<boolean> {1047 const result = await this.helper.executeExtrinsic(1048 signer,1049 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],1050 true,1051 );10521053 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionOwnerChanged');1054 }10551056 /**1057 * Adds a collection administrator.1058 *1059 * @param signer keyring of signer1060 * @param collectionId ID of collection1061 * @param adminAddressObj Administrator address (substrate or ethereum)1062 * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1063 * @returns ```true``` if extrinsic success, otherwise ```false```1064 */1065 async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1066 const result = await this.helper.executeExtrinsic(1067 signer,1068 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],1069 true,1070 );10711072 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminAdded');1073 }10741075 /**1076 * Removes a collection administrator.1077 *1078 * @param signer keyring of signer1079 * @param collectionId ID of collection1080 * @param adminAddressObj Administrator address (substrate or ethereum)1081 * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})1082 * @returns ```true``` if extrinsic success, otherwise ```false```1083 */1084 async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId): Promise<boolean> {1085 const result = await this.helper.executeExtrinsic(1086 signer,1087 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],1088 true,1089 );10901091 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminRemoved');1092 }10931094 /**1095 * Check if user is in allow list.1096 *1097 * @param collectionId ID of collection1098 * @param user Account to check1099 * @example await getAdmins(1)1100 * @returns is user in allow list1101 */1102 async allowed(collectionId: number, user: ICrossAccountId): Promise<boolean> {1103 return (await this.helper.callRpc('api.rpc.unique.allowed', [collectionId, user])).toJSON();1104 }11051106 /**1107 * Adds an address to allow list1108 * @param signer keyring of signer1109 * @param collectionId ID of collection1110 * @param addressObj address to add to the allow list1111 * @returns ```true``` if extrinsic success, otherwise ```false```1112 */1113 async addToAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1114 const result = await this.helper.executeExtrinsic(1115 signer,1116 'api.tx.unique.addToAllowList', [collectionId, addressObj],1117 true,1118 );11191120 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressAdded');1121 }11221123 /**1124 * Removes an address from allow list1125 *1126 * @param signer keyring of signer1127 * @param collectionId ID of collection1128 * @param addressObj address to remove from the allow list1129 * @returns ```true``` if extrinsic success, otherwise ```false```1130 */1131 async removeFromAllowList(signer: TSigner, collectionId: number, addressObj: ICrossAccountId): Promise<boolean> {1132 const result = await this.helper.executeExtrinsic(1133 signer,1134 'api.tx.unique.removeFromAllowList', [collectionId, addressObj],1135 true,1136 );11371138 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressRemoved');1139 }11401141 /**1142 * Sets onchain permissions for selected collection.1143 *1144 * @param signer keyring of signer1145 * @param collectionId ID of collection1146 * @param permissions collection permissions object1147 * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});1148 * @returns ```true``` if extrinsic success, otherwise ```false```1149 */1150 async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions): Promise<boolean> {1151 const result = await this.helper.executeExtrinsic(1152 signer,1153 'api.tx.unique.setCollectionPermissions', [collectionId, permissions],1154 true,1155 );11561157 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPermissionSet');1158 }11591160 /**1161 * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.1162 *1163 * @param signer keyring of signer1164 * @param collectionId ID of collection1165 * @param permissions nesting permissions object1166 * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});1167 * @returns ```true``` if extrinsic success, otherwise ```false```1168 */1169 async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions): Promise<boolean> {1170 return await this.setPermissions(signer, collectionId, {nesting: permissions});1171 }11721173 /**1174 * Disables nesting for selected collection.1175 *1176 * @param signer keyring of signer1177 * @param collectionId ID of collection1178 * @example disableNesting(aliceKeyring, 10);1179 * @returns ```true``` if extrinsic success, otherwise ```false```1180 */1181 async disableNesting(signer: TSigner, collectionId: number): Promise<boolean> {1182 return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}});1183 }11841185 /**1186 * Sets onchain properties to the collection.1187 *1188 * @param signer keyring of signer1189 * @param collectionId ID of collection1190 * @param properties array of property objects1191 * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);1192 * @returns ```true``` if extrinsic success, otherwise ```false```1193 */1194 async setProperties(signer: TSigner, collectionId: number, properties: IProperty[]): Promise<boolean> {1195 const result = await this.helper.executeExtrinsic(1196 signer,1197 'api.tx.unique.setCollectionProperties', [collectionId, properties],1198 true,1199 );12001201 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet');1202 }12031204 /**1205 * Get collection properties.1206 *1207 * @param collectionId ID of collection1208 * @param propertyKeys optionally filter the returned properties to only these keys1209 * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']);1210 * @returns array of key-value pairs1211 */1212 async getProperties(collectionId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1213 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1214 }12151216 async getPropertiesConsumedSpace(collectionId: number): Promise<number> {1217 const api = this.helper.getApi();1218 const props = (await api.query.common.collectionProperties(collectionId)).toJSON();12191220 return (props! as any).consumedSpace;1221 }12221223 async getCollectionOptions(collectionId: number) {1224 return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1225 }12261227 /**1228 * Deletes onchain properties from the collection.1229 *1230 * @param signer keyring of signer1231 * @param collectionId ID of collection1232 * @param propertyKeys array of property keys to delete1233 * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);1234 * @returns ```true``` if extrinsic success, otherwise ```false```1235 */1236 async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[]): Promise<boolean> {1237 const result = await this.helper.executeExtrinsic(1238 signer,1239 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],1240 true,1241 );12421243 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted');1244 }12451246 /**1247 * Changes the owner of the token.1248 *1249 * @param signer keyring of signer1250 * @param collectionId ID of collection1251 * @param tokenId ID of token1252 * @param addressObj address of a new owner1253 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1254 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1255 * @returns true if the token success, otherwise false1256 */1257 async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1258 const result = await this.helper.executeExtrinsic(1259 signer,1260 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],1261 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1262 );12631264 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);1265 }12661267 /**1268 *1269 * Change ownership of a token(s) on behalf of the owner.1270 *1271 * @param signer keyring of signer1272 * @param collectionId ID of collection1273 * @param tokenId ID of token1274 * @param fromAddressObj address on behalf of which the token will be sent1275 * @param toAddressObj new token owner1276 * @param amount amount of tokens to be transfered. For NFT must be set to 1n1277 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})1278 * @returns true if the token success, otherwise false1279 */1280 async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1281 const result = await this.helper.executeExtrinsic(1282 signer,1283 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1284 true, // `Unable to transfer token #${tokenId} from collection #${collectionId}`,1285 );1286 return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1287 }12881289 /**1290 *1291 * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.1292 *1293 * @param signer keyring of signer1294 * @param collectionId ID of collection1295 * @param tokenId ID of token1296 * @param amount amount of tokens to be burned. For NFT must be set to 1n1297 * @example burnToken(aliceKeyring, 10, 5);1298 * @returns ```true``` if the extrinsic is successful, otherwise ```false```1299 */1300 async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {1301 const burnResult = await this.helper.executeExtrinsic(1302 signer,1303 'api.tx.unique.burnItem', [collectionId, tokenId, amount],1304 true, // `Unable to burn token for ${label}`,1305 );1306 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1307 if(burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');1308 return burnedTokens.success;1309 }13101311 /**1312 * Destroys a concrete instance of NFT on behalf of the owner1313 *1314 * @param signer keyring of signer1315 * @param collectionId ID of collection1316 * @param tokenId ID of token1317 * @param fromAddressObj address on behalf of which the token will be burnt1318 * @param amount amount of tokens to be burned. For NFT must be set to 1n1319 * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})1320 * @returns ```true``` if extrinsic success, otherwise ```false```1321 */1322 async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1323 const burnResult = await this.helper.executeExtrinsic(1324 signer,1325 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],1326 true, // `Unable to burn token from for ${label}`,1327 );1328 const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult);1329 return burnedTokens.success && burnedTokens.tokens.length > 0;1330 }13311332 /**1333 * Set, change, or remove approved address to transfer the ownership of the NFT.1334 *1335 * @param signer keyring of signer1336 * @param collectionId ID of collection1337 * @param tokenId ID of token1338 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1339 * @param amount amount of token to be approved. For NFT must be set to 1n1340 * @returns ```true``` if extrinsic success, otherwise ```false```1341 */1342 async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1343 const approveResult = await this.helper.executeExtrinsic(1344 signer,1345 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],1346 true, // `Unable to approve token for ${label}`,1347 );13481349 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1350 }13511352 /**1353 * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1354 *1355 * @param signer keyring of signer1356 * @param collectionId ID of collection1357 * @param tokenId ID of token1358 * @param fromAddressObj Signer's Ethereum address containing her tokens1359 * @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 approveTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {1364 const approveResult = await this.helper.executeExtrinsic(1365 signer,1366 'api.tx.unique.approveFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],1367 true, // `Unable to approve token for ${label}`,1368 );13691370 return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved');1371 }13721373 /**1374 * Set, change, or remove approved address to transfer the ownership of the NFT from eth mirror.1375 *1376 * @param signer keyring of signer1377 * @param collectionId ID of collection1378 * @param tokenId ID of token1379 * @param toAddressObj Substrate or Ethereum address which gets approved use of the signer's tokens1380 * @param amount amount of token to be approved. For NFT must be set to 1n1381 * @returns ```true``` if extrinsic success, otherwise ```false```1382 */1383 async approveTokenFromEth(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1384 const ethMirror = CrossAccountId.fromKeyring(signer).toEthereum().toICrossAccountId();1385 return await this.approveTokenFrom(signer, collectionId, tokenId, ethMirror, toAddressObj, amount);1386 }13871388 /**1389 * Get the amount of token pieces approved to transfer or burn. Normally 0.1390 *1391 * @param collectionId ID of collection1392 * @param tokenId ID of token1393 * @param toAccountObj address which is approved to use token pieces1394 * @param fromAccountObj address which may have allowed the use of its owned tokens1395 * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})1396 * @returns number of approved to transfer pieces1397 */1398 async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {1399 return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();1400 }14011402 /**1403 * Get the last created token ID in a collection1404 *1405 * @param collectionId ID of collection1406 * @example getLastTokenId(10);1407 * @returns id of the last created token1408 */1409 async getLastTokenId(collectionId: number): Promise<number> {1410 return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();1411 }14121413 /**1414 * Check if token exists1415 *1416 * @param collectionId ID of collection1417 * @param tokenId ID of token1418 * @example doesTokenExist(10, 20);1419 * @returns true if the token exists, otherwise false1420 */1421 async doesTokenExist(collectionId: number, tokenId: number): Promise<boolean> {1422 return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();1423 }1424}14251426class NFTnRFT extends CollectionGroup {1427 /**1428 * Get tokens owned by account1429 *1430 * @param collectionId ID of collection1431 * @param addressObj tokens owner1432 * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})1433 * @returns array of token ids owned by account1434 */1435 async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {1436 return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();1437 }14381439 /**1440 * Get token data1441 *1442 * @param collectionId ID of collection1443 * @param tokenId ID of token1444 * @param propertyKeys optionally filter the token properties to only these keys1445 * @param blockHashAt optionally query the data at some block with this hash1446 * @example getToken(10, 5);1447 * @returns human readable token data1448 */1449 async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{1450 properties: IProperty[];1451 owner: ICrossAccountId;1452 normalizedOwner: ICrossAccountId;1453 } | null> {1454 let args;1455 if(typeof blockHashAt === 'undefined') {1456 args = [collectionId, tokenId];1457 }1458 else {1459 if(propertyKeys.length == 0) {1460 const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1461 if(!collection) return null;1462 propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);1463 }1464 args = [collectionId, tokenId, propertyKeys, blockHashAt];1465 }1466 const tokenData = (await this.helper.callRpc('api.rpc.unique.tokenData', args)).toHuman();1467 if(tokenData === null || tokenData.owner === null) return null;1468 tokenData.normalizedOwner = CrossAccountId.withNormalizedSubstrate(tokenData.owner);1469 return tokenData;1470 }14711472 /**1473 * Get token's owner1474 * @param collectionId ID of collection1475 * @param tokenId ID of token1476 * @param blockHashAt optionally query the data at the block with this hash1477 * @example getTokenOwner(10, 5);1478 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}1479 */1480 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {1481 let owner;1482 if(typeof blockHashAt === 'undefined') {1483 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);1484 } else {1485 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);1486 }1487 return CrossAccountId.fromLowerCaseKeys(owner.toJSON()).toICrossAccountId();1488 }14891490 /**1491 * Recursively find the address that owns the token1492 * @param collectionId ID of collection1493 * @param tokenId ID of token1494 * @param blockHashAt1495 * @example getTokenTopmostOwner(10, 5);1496 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}1497 */1498 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {1499 let owner;1500 if(typeof blockHashAt === 'undefined') {1501 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);1502 } else {1503 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);1504 }15051506 if(owner === null) return null;15071508 return owner.toHuman();1509 }15101511 /**1512 * Nest one token into another1513 * @param signer keyring of signer1514 * @param tokenObj token to be nested1515 * @param rootTokenObj token to be parent1516 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});1517 * @returns ```true``` if extrinsic success, otherwise ```false```1518 */1519 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {1520 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1521 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);1522 if(!result) {1523 throw Error('Unable to nest token!');1524 }1525 return result;1526 }15271528 /**1529 * Remove token from nested state1530 * @param signer keyring of signer1531 * @param tokenObj token to unnest1532 * @param rootTokenObj parent of a token1533 * @param toAddressObj address of a new token owner1534 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});1535 * @returns ```true``` if extrinsic success, otherwise ```false```1536 */1537 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {1538 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);1539 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);1540 if(!result) {1541 throw Error('Unable to unnest token!');1542 }1543 return result;1544 }15451546 /**1547 * Set permissions to change token properties1548 *1549 * @param signer keyring of signer1550 * @param collectionId ID of collection1551 * @param permissions permissions to change a property by the collection admin or token owner1552 * @example setTokenPropertyPermissions(1553 * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]1554 * )1555 * @returns true if extrinsic success otherwise false1556 */1557 async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[]): Promise<boolean> {1558 const result = await this.helper.executeExtrinsic(1559 signer,1560 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],1561 true,1562 );15631564 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet');1565 }15661567 /**1568 * Get token property permissions.1569 *1570 * @param collectionId ID of collection1571 * @param propertyKeys optionally filter the returned property permissions to only these keys1572 * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']);1573 * @returns array of key-permission pairs1574 */1575 async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise<ITokenPropertyPermission[]> {1576 return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman();1577 }15781579 /**1580 * Set token properties1581 *1582 * @param signer keyring of signer1583 * @param collectionId ID of collection1584 * @param tokenId ID of token1585 * @param properties key-value pairs of metadata which to add to a token. Keys must be permitted in the collection1586 * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])1587 * @returns ```true``` if extrinsic success, otherwise ```false```1588 */1589 async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[]): Promise<boolean> {1590 const result = await this.helper.executeExtrinsic(1591 signer,1592 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],1593 true,1594 );15951596 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet');1597 }15981599 /**1600 * Get properties, metadata assigned to a token.1601 *1602 * @param collectionId ID of collection1603 * @param tokenId ID of token1604 * @param propertyKeys optionally filter the returned properties to only these keys1605 * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']);1606 * @returns array of key-value pairs1607 */1608 async getTokenProperties(collectionId: number, tokenId: number, propertyKeys?: string[] | null): Promise<IProperty[]> {1609 return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, propertyKeys])).toHuman();1610 }16111612 /**1613 * Delete the provided properties of a token1614 * @param signer keyring of signer1615 * @param collectionId ID of collection1616 * @param tokenId ID of token1617 * @param propertyKeys property keys to be deleted1618 * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])1619 * @returns ```true``` if extrinsic success, otherwise ```false```1620 */1621 async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[]): Promise<boolean> {1622 const result = await this.helper.executeExtrinsic(1623 signer,1624 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],1625 true,1626 );16271628 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted');1629 }16301631 /**1632 * Mint new collection1633 *1634 * @param signer keyring of signer1635 * @param collectionOptions basic collection options and properties1636 * @param mode NFT or RFT type of a collection1637 * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")1638 * @returns object of the created collection1639 */1640 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise<UniqueBaseCollection> {1641 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object1642 collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};1643 for(const key of ['name', 'description', 'tokenPrefix']) {1644 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);1645 }16461647 let flags = 0;1648 // convert CollectionFlags to number and join them in one number1649 if(collectionOptions.flags) {1650 for(let i = 0; i < collectionOptions.flags.length; i++){1651 const flag = collectionOptions.flags[i];1652 flags = flags | flag;1653 }1654 }1655 collectionOptions.flags = [flags];16561657 const creationResult = await this.helper.executeExtrinsic(1658 signer,1659 'api.tx.unique.createCollectionEx', [collectionOptions],1660 true, // errorLabel,1661 );1662 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));1663 }16641665 getCollectionObject(_collectionId: number): any {1666 return null;1667 }16681669 getTokenObject(_collectionId: number, _tokenId: number): any {1670 return null;1671 }16721673 /**1674 * Tells whether the given `owner` approves the `operator`.1675 * @param collectionId ID of collection1676 * @param owner owner address1677 * @param operator operator addrees1678 * @returns true if operator is enabled1679 */1680 async allowanceForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {1681 return (await this.helper.callRpc('api.rpc.unique.allowanceForAll', [collectionId, owner, operator])).toJSON();1682 }16831684 /** Sets or unsets the approval of a given operator.1685 * The `operator` is allowed to transfer all tokens of the `caller` on their behalf.1686 * @param operator Operator1687 * @param approved Should operator status be granted or revoked?1688 * @returns ```true``` if extrinsic success, otherwise ```false```1689 */1690 async setAllowanceForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {1691 const result = await this.helper.executeExtrinsic(1692 signer,1693 'api.tx.unique.setAllowanceForAll', [collectionId, operator, approved],1694 true,1695 );1696 return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');1697 }1698}169917001701class NFTGroup extends NFTnRFT {1702 /**1703 * Get collection object1704 * @param collectionId ID of collection1705 * @example getCollectionObject(2);1706 * @returns instance of UniqueNFTCollection1707 */1708 override getCollectionObject(collectionId: number): UniqueNFTCollection {1709 return new UniqueNFTCollection(collectionId, this.helper);1710 }17111712 /**1713 * Get token object1714 * @param collectionId ID of collection1715 * @param tokenId ID of token1716 * @example getTokenObject(10, 5);1717 * @returns instance of UniqueNFTToken1718 */1719 override getTokenObject(collectionId: number, tokenId: number): UniqueNFToken {1720 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1721 }17221723 /**1724 * Is token approved to transfer1725 * @param collectionId ID of collection1726 * @param tokenId ID of token1727 * @param toAccountObj address to be approved1728 * @returns ```true``` if extrinsic success, otherwise ```false```1729 */1730 async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {1731 return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, (await this.getTokenOwner(collectionId, tokenId)))) === 1n;1732 }17331734 /**1735 * Changes the owner of the token.1736 *1737 * @param signer keyring of signer1738 * @param collectionId ID of collection1739 * @param tokenId ID of token1740 * @param addressObj address of a new owner1741 * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1742 * @returns ```true``` if extrinsic success, otherwise ```false```1743 */1744 override async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {1745 return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);1746 }17471748 /**1749 *1750 * Change ownership of a NFT on behalf of the owner.1751 *1752 * @param signer keyring of signer1753 * @param collectionId ID of collection1754 * @param tokenId ID of token1755 * @param fromAddressObj address on behalf of which the token will be sent1756 * @param toAddressObj new token owner1757 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})1758 * @returns ```true``` if extrinsic success, otherwise ```false```1759 */1760 override async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {1761 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1762 }17631764 /**1765 * Get tokens nested in the provided token1766 * @param collectionId ID of collection1767 * @param tokenId ID of token1768 * @param blockHashAt optionally query the data at the block with this hash1769 * @example getTokenChildren(10, 5);1770 * @returns tokens whose depth of nesting is <= 51771 */1772 async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {1773 let children;1774 if(typeof blockHashAt === 'undefined') {1775 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);1776 } else {1777 children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);1778 }17791780 return children.toJSON().map((x: any) => ({collectionId: x.collection, tokenId: x.token}));1781 }17821783 /**1784 * Mint new collection1785 * @param signer keyring of signer1786 * @param collectionOptions Collection options1787 * @example1788 * mintCollection(aliceKeyring, {1789 * name: 'New',1790 * description: 'New collection',1791 * tokenPrefix: 'NEW',1792 * })1793 * @returns object of the created collection1794 */1795 override async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueNFTCollection> {1796 return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection;1797 }17981799 /**1800 * Mint new token1801 * @param signer keyring of signer1802 * @param data token data1803 * @returns created token object1804 */1805 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise<UniqueNFToken> {1806 const creationResult = await this.helper.executeExtrinsic(1807 signer,1808 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1809 NFT: {1810 properties: data.properties,1811 },1812 }],1813 true,1814 );1815 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);1816 if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');1817 if(createdTokens.tokens.length < 1) throw Error('No tokens minted');1818 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);1819 }18201821 /**1822 * Mint multiple NFT tokens1823 * @param signer keyring of signer1824 * @param collectionId ID of collection1825 * @param tokens array of tokens with owner and properties1826 * @example1827 * mintMultipleTokens(aliceKeyring, 10, [{1828 * owner: {Substrate: "5DyN4Y92vZCjv38fg..."},1829 * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],1830 * },{1831 * owner: {Ethereum: "0x9F0583DbB855d..."},1832 * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],1833 * }]);1834 * @returns ```true``` if extrinsic success, otherwise ```false```1835 */1836 async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1837 const creationResult = await this.helper.executeExtrinsic(1838 signer,1839 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],1840 true,1841 );1842 const collection = this.getCollectionObject(collectionId);1843 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1844 }18451846 /**1847 * Mint multiple NFT tokens with one owner1848 * @param signer keyring of signer1849 * @param collectionId ID of collection1850 * @param owner tokens owner1851 * @param tokens array of tokens with owner and properties1852 * @example1853 * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{1854 * properties: [{1855 * key: "gender",1856 * value: "female",1857 * },{1858 * key: "age",1859 * value: "33",1860 * }],1861 * }]);1862 * @returns array of newly created tokens1863 */1864 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { properties?: IProperty[] }[]): Promise<UniqueNFToken[]> {1865 const rawTokens = [];1866 for(const token of tokens) {1867 const raw = {NFT: {properties: token.properties}};1868 rawTokens.push(raw);1869 }1870 const creationResult = await this.helper.executeExtrinsic(1871 signer,1872 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],1873 true,1874 );1875 const collection = this.getCollectionObject(collectionId);1876 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));1877 }18781879 /**1880 * Set, change, or remove approved address to transfer the ownership of the NFT.1881 *1882 * @param signer keyring of signer1883 * @param collectionId ID of collection1884 * @param tokenId ID of token1885 * @param toAddressObj address to approve1886 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})1887 * @returns ```true``` if extrinsic success, otherwise ```false```1888 */1889 override approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {1890 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);1891 }1892}189318941895class RFTGroup extends NFTnRFT {1896 /**1897 * Get collection object1898 * @param collectionId ID of collection1899 * @example getCollectionObject(2);1900 * @returns instance of UniqueRFTCollection1901 */1902 override getCollectionObject(collectionId: number): UniqueRFTCollection {1903 return new UniqueRFTCollection(collectionId, this.helper);1904 }19051906 /**1907 * Get token object1908 * @param collectionId ID of collection1909 * @param tokenId ID of token1910 * @example getTokenObject(10, 5);1911 * @returns instance of UniqueNFTToken1912 */1913 override getTokenObject(collectionId: number, tokenId: number): UniqueRFToken {1914 return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId));1915 }19161917 /**1918 * Get top 10 token owners with the largest number of pieces1919 * @param collectionId ID of collection1920 * @param tokenId ID of token1921 * @example getTokenTop10Owners(10, 5);1922 * @returns array of top 10 owners1923 */1924 async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {1925 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys).map((a: CrossAccountId) => a.toICrossAccountId());1926 }19271928 /**1929 * Get number of pieces owned by address1930 * @param collectionId ID of collection1931 * @param tokenId ID of token1932 * @param addressObj address token owner1933 * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});1934 * @returns number of pieces ownerd by address1935 */1936 async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {1937 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();1938 }19391940 /**1941 * Transfer pieces of token to another address1942 * @param signer keyring of signer1943 * @param collectionId ID of collection1944 * @param tokenId ID of token1945 * @param addressObj address of a new owner1946 * @param amount number of pieces to be transfered1947 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)1948 * @returns ```true``` if extrinsic success, otherwise ```false```1949 */1950 override async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1951 return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);1952 }19531954 /**1955 * Change ownership of some pieces of RFT on behalf of the owner.1956 * @param signer keyring of signer1957 * @param collectionId ID of collection1958 * @param tokenId ID of token1959 * @param fromAddressObj address on behalf of which the token will be sent1960 * @param toAddressObj new token owner1961 * @param amount number of pieces to be transfered1962 * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)1963 * @returns ```true``` if extrinsic success, otherwise ```false```1964 */1965 override async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {1966 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);1967 }19681969 /**1970 * Mint new collection1971 * @param signer keyring of signer1972 * @param collectionOptions Collection options1973 * @example1974 * mintCollection(aliceKeyring, {1975 * name: 'New',1976 * description: 'New collection',1977 * tokenPrefix: 'NEW',1978 * })1979 * @returns object of the created collection1980 */1981 override async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise<UniqueRFTCollection> {1982 return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection;1983 }19841985 /**1986 * Mint new token1987 * @param signer keyring of signer1988 * @param data token data1989 * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});1990 * @returns created token object1991 */1992 async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise<UniqueRFToken> {1993 const creationResult = await this.helper.executeExtrinsic(1994 signer,1995 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {1996 ReFungible: {1997 pieces: data.pieces,1998 properties: data.properties,1999 },2000 }],2001 true,2002 );2003 const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult);2004 if(createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');2005 if(createdTokens.tokens.length < 1) throw Error('No tokens minted');2006 return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);2007 }20082009 mintMultipleTokens(_signer: TSigner, _collectionId: number, _tokens: { owner: ICrossAccountId, pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {2010 throw Error('Not implemented');2011 // const creationResult = await this.helper.executeExtrinsic(2012 // signer,2013 // 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],2014 // true, // `Unable to mint RFT tokens for ${label}`,2015 // );2016 // const collection = this.getCollectionObject(collectionId);2017 // return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2018 }20192020 /**2021 * Mint multiple RFT tokens with one owner2022 * @param signer keyring of signer2023 * @param collectionId ID of collection2024 * @param owner tokens owner2025 * @param tokens array of tokens with properties and pieces2026 * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);2027 * @returns array of newly created RFT tokens2028 */2029 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: { pieces: bigint, properties?: IProperty[] }[]): Promise<UniqueRFToken[]> {2030 const rawTokens = [];2031 for(const token of tokens) {2032 const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};2033 rawTokens.push(raw);2034 }2035 const creationResult = await this.helper.executeExtrinsic(2036 signer,2037 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2038 true,2039 );2040 const collection = this.getCollectionObject(collectionId);2041 return this.helper.util.extractTokensFromCreationResult(creationResult).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));2042 }20432044 /**2045 * Destroys a concrete instance of RFT.2046 * @param signer keyring of signer2047 * @param collectionId ID of collection2048 * @param tokenId ID of token2049 * @param amount number of pieces to be burnt2050 * @example burnToken(aliceKeyring, 10, 5);2051 * @returns ```true``` if the extrinsic is successful, otherwise ```false```2052 */2053 override async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount = 1n): Promise<boolean> {2054 return await super.burnToken(signer, collectionId, tokenId, amount);2055 }20562057 /**2058 * Destroys a concrete instance of RFT on behalf of the owner.2059 * @param signer keyring of signer2060 * @param collectionId ID of collection2061 * @param tokenId ID of token2062 * @param fromAddressObj address on behalf of which the token will be burnt2063 * @param amount number of pieces to be burnt2064 * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n)2065 * @returns ```true``` if extrinsic success, otherwise ```false```2066 */2067 override async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2068 return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount);2069 }20702071 /**2072 * Set, change, or remove approved address to transfer the ownership of the RFT.2073 *2074 * @param signer keyring of signer2075 * @param collectionId ID of collection2076 * @param tokenId ID of token2077 * @param toAddressObj address to approve2078 * @param amount number of pieces to be approved2079 * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);2080 * @returns true if the token success, otherwise false2081 */2082 override approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {2083 return super.approveToken(signer, collectionId, tokenId, toAddressObj, amount);2084 }20852086 /**2087 * Get total number of pieces2088 * @param collectionId ID of collection2089 * @param tokenId ID of token2090 * @example getTokenTotalPieces(10, 5);2091 * @returns number of pieces2092 */2093 async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {2094 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();2095 }20962097 /**2098 * Change number of token pieces. Signer must be the owner of all token pieces.2099 * @param signer keyring of signer2100 * @param collectionId ID of collection2101 * @param tokenId ID of token2102 * @param amount new number of pieces2103 * @example repartitionToken(aliceKeyring, 10, 5, 12345n);2104 * @returns true if the repartion was success, otherwise false2105 */2106 async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint): Promise<boolean> {2107 const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);2108 const repartitionResult = await this.helper.executeExtrinsic(2109 signer,2110 'api.tx.unique.repartition', [collectionId, tokenId, amount],2111 true,2112 );2113 if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated');2114 return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed');2115 }2116}211721182119class FTGroup extends CollectionGroup {2120 /**2121 * Get collection object2122 * @param collectionId ID of collection2123 * @example getCollectionObject(2);2124 * @returns instance of UniqueFTCollection2125 */2126 getCollectionObject(collectionId: number): UniqueFTCollection {2127 return new UniqueFTCollection(collectionId, this.helper);2128 }21292130 /**2131 * Mint new fungible collection2132 * @param signer keyring of signer2133 * @param collectionOptions Collection options2134 * @param decimalPoints number of token decimals2135 * @example2136 * mintCollection(aliceKeyring, {2137 * name: 'New',2138 * description: 'New collection',2139 * tokenPrefix: 'NEW',2140 * }, 18)2141 * @returns newly created fungible collection2142 */2143 async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise<UniqueFTCollection> {2144 collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object2145 if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');2146 collectionOptions.mode = {fungible: decimalPoints};2147 for(const key of ['name', 'description', 'tokenPrefix']) {2148 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);2149 }2150 const creationResult = await this.helper.executeExtrinsic(2151 signer,2152 'api.tx.unique.createCollectionEx', [collectionOptions],2153 true,2154 );2155 return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult));2156 }21572158 /**2159 * Mint tokens2160 * @param signer keyring of signer2161 * @param collectionId ID of collection2162 * @param owner address owner of new tokens2163 * @param amount amount of tokens to be meanted2164 * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);2165 * @returns ```true``` if extrinsic success, otherwise ```false```2166 */2167 async mintTokens(signer: TSigner, collectionId: number, amount: bigint, owner: ICrossAccountId | string): Promise<boolean> {2168 const creationResult = await this.helper.executeExtrinsic(2169 signer,2170 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {2171 Fungible: {2172 value: amount,2173 },2174 }],2175 true, // `Unable to mint fungible tokens for ${label}`,2176 );2177 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2178 }21792180 /**2181 * Mint multiple Fungible tokens with one owner2182 * @param signer keyring of signer2183 * @param collectionId ID of collection2184 * @param owner tokens owner2185 * @param tokens array of tokens with properties and pieces2186 * @returns ```true``` if extrinsic success, otherwise ```false```2187 */2188 async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, tokens: { value: bigint }[], owner: ICrossAccountId): Promise<boolean> {2189 const rawTokens = [];2190 for(const token of tokens) {2191 const raw = {Fungible: {Value: token.value}};2192 rawTokens.push(raw);2193 }2194 const creationResult = await this.helper.executeExtrinsic(2195 signer,2196 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],2197 true,2198 );2199 return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated');2200 }22012202 /**2203 * Get the top 10 owners with the largest balance for the Fungible collection2204 * @param collectionId ID of collection2205 * @example getTop10Owners(10);2206 * @returns array of ```ICrossAccountId```2207 */2208 async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {2209 return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys).map((a: CrossAccountId) => a.toICrossAccountId());2210 }22112212 /**2213 * Get account balance2214 * @param collectionId ID of collection2215 * @param addressObj address of owner2216 * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})2217 * @returns amount of fungible tokens owned by address2218 */2219 async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {2220 return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();2221 }22222223 /**2224 * Transfer tokens to address2225 * @param signer keyring of signer2226 * @param collectionId ID of collection2227 * @param toAddressObj address recipient2228 * @param amount amount of tokens to be sent2229 * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2230 * @returns ```true``` if extrinsic success, otherwise ```false```2231 */2232 async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2233 return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);2234 }22352236 /**2237 * Transfer some tokens on behalf of the owner.2238 * @param signer keyring of signer2239 * @param collectionId ID of collection2240 * @param fromAddressObj address on behalf of which tokens will be sent2241 * @param toAddressObj address where token to be sent2242 * @param amount number of tokens to be sent2243 * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);2244 * @returns ```true``` if extrinsic success, otherwise ```false```2245 */2246 async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {2247 return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);2248 }22492250 /**2251 * Destroy some amount of tokens2252 * @param signer keyring of signer2253 * @param collectionId ID of collection2254 * @param amount amount of tokens to be destroyed2255 * @example burnTokens(aliceKeyring, 10, 1000n);2256 * @returns ```true``` if extrinsic success, otherwise ```false```2257 */2258 async burnTokens(signer: IKeyringPair, collectionId: number, amount = 1n): Promise<boolean> {2259 return await super.burnToken(signer, collectionId, 0, amount);2260 }22612262 /**2263 * Burn some tokens on behalf of the owner.2264 * @param signer keyring of signer2265 * @param collectionId ID of collection2266 * @param fromAddressObj address on behalf of which tokens will be burnt2267 * @param amount amount of tokens to be burnt2268 * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);2269 * @returns ```true``` if extrinsic success, otherwise ```false```2270 */2271 async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount = 1n): Promise<boolean> {2272 return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount);2273 }22742275 /**2276 * Get total collection supply2277 * @param collectionId2278 * @returns2279 */2280 async getTotalPieces(collectionId: number): Promise<bigint> {2281 return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();2282 }22832284 /**2285 * Set, change, or remove approved address to transfer tokens.2286 *2287 * @param signer keyring of signer2288 * @param collectionId ID of collection2289 * @param toAddressObj address to be approved2290 * @param amount amount of tokens to be approved2291 * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)2292 * @returns ```true``` if extrinsic success, otherwise ```false```2293 */2294 approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount = 1n) {2295 return super.approveToken(signer, collectionId, 0, toAddressObj, amount);2296 }22972298 /**2299 * Get amount of fungible tokens approved to transfer2300 * @param collectionId ID of collection2301 * @param fromAddressObj owner of tokens2302 * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner2303 * @returns number of tokens approved for the transfer2304 */2305 getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {2306 return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);2307 }2308}230923102311class ChainGroup extends HelperGroup<ChainHelperBase> {2312 /**2313 * Get system properties of a chain2314 * @example getChainProperties();2315 * @returns ss58Format, token decimals, and token symbol2316 */2317 getChainProperties(): IChainProperties {2318 const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON();2319 return {2320 ss58Format: properties.ss58Format.toJSON(),2321 tokenDecimals: properties.tokenDecimals.toJSON(),2322 tokenSymbol: properties.tokenSymbol.toJSON(),2323 };2324 }23252326 /**2327 * Get chain header2328 * @example getLatestBlockNumber();2329 * @returns the number of the last block2330 */2331 async getLatestBlockNumber(): Promise<number> {2332 return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();2333 }23342335 /**2336 * Get block hash by block number2337 * @param blockNumber number of block2338 * @example getBlockHashByNumber(12345);2339 * @returns hash of a block2340 */2341 async getBlockHashByNumber(blockNumber: number): Promise<string | null> {2342 const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();2343 if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;2344 return blockHash;2345 }23462347 // TODO add docs2348 async getBlock(blockHashOrNumber: string | number): Promise<IBlock | null> {2349 const blockHash = typeof blockHashOrNumber === 'string' ? blockHashOrNumber : await this.getBlockHashByNumber(blockHashOrNumber);2350 if(!blockHash) return null;2351 return (await this.helper.callRpc('api.rpc.chain.getBlock', [blockHash])).toHuman().block;2352 }23532354 /**2355 * Get latest relay block2356 * @returns {number} relay block2357 */2358 async getRelayBlockNumber(): Promise<bigint> {2359 const blockNumber = (await this.helper.callRpc('api.query.parachainSystem.validationData')).toJSON().relayParentNumber;2360 return BigInt(blockNumber);2361 }23622363 /**2364 * Get account nonce2365 * @param address substrate address2366 * @example getNonce("5GrwvaEF5zXb26Fz...");2367 * @returns number, account's nonce2368 */2369 async getNonce(address: TSubstrateAccount): Promise<number> {2370 return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber();2371 }2372}23732374export class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2375 /**2376 * Get substrate address balance2377 * @param address substrate address2378 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2379 * @returns amount of tokens on address2380 */2381 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2382 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2383 }23842385 /**2386 * Transfer tokens to substrate address2387 * @param signer keyring of signer2388 * @param address substrate address of a recipient2389 * @param amount amount of tokens to be transfered2390 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2391 * @returns ```true``` if extrinsic success, otherwise ```false```2392 */2393 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2394 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transferKeepAlive', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/);23952396 let transfer = {from: null, to: null, amount: 0n} as any;2397 result.result.events.forEach(({event: {data, method, section}}: any) => {2398 if((section === 'balances') && (method === 'Transfer')) {2399 transfer = {2400 from: this.helper.address.normalizeSubstrate(data[0]),2401 to: this.helper.address.normalizeSubstrate(data[1]),2402 amount: BigInt(data[2]),2403 };2404 }2405 });2406 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from2407 && this.helper.address.normalizeSubstrate(address) === transfer.to2408 && BigInt(amount) === transfer.amount;2409 return isSuccess;2410 }24112412 /**2413 * Get full substrate balance including free, frozen, and reserved2414 * @param address substrate address2415 * @returns2416 */2417 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2418 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2419 return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};2420 }24212422 /**2423 * Get total issuance2424 * @returns2425 */2426 async getTotalIssuance(): Promise<bigint> {2427 const total = (await this.helper.callRpc('api.query.balances.totalIssuance', []));2428 return total.toBigInt();2429 }24302431 async getLocked(address: TSubstrateAccount): Promise<{ id: string, amount: bigint, reason: string }[]> {2432 const locks = (await this.helper.callRpc('api.query.balances.locks', [address])).toHuman();2433 return locks.map((lock: any) => ({id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}));2434 }2435 async getFrozen(address: TSubstrateAccount): Promise<{ id: string, amount: bigint }[]> {2436 const locks = (await this.helper.api!.query.balances.freezes(address)) as unknown as Array<any>;2437 return locks.map(lock => ({id: lock.id.toUtf8(), amount: lock.amount.toBigInt()}));2438 }2439}24402441export class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2442 /**2443 * Get ethereum address balance2444 * @param address ethereum address2445 * @example getEthereum("0x9F0583DbB855d...")2446 * @returns amount of tokens on address2447 */2448 async getEthereum(address: TEthereumAccount): Promise<bigint> {2449 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2450 }24512452 /**2453 * Transfer tokens to address2454 * @param signer keyring of signer2455 * @param address Ethereum address of a recipient2456 * @param amount amount of tokens to be transfered2457 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);2458 * @returns ```true``` if extrinsic success, otherwise ```false```2459 */2460 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {2461 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transferKeepAlive', [address, amount], true);24622463 let transfer = {from: null, to: null, amount: 0n} as any;2464 result.result.events.forEach(({event: {data, method, section}}: any) => {2465 if((section === 'balances') && (method === 'Transfer')) {2466 transfer = {2467 from: data[0].toString(),2468 to: data[1].toString(),2469 amount: BigInt(data[2]),2470 };2471 }2472 });2473 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from2474 && address === transfer.to2475 && BigInt(amount) === transfer.amount;2476 return isSuccess;2477 }2478}24792480class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {2481 subBalanceGroup: SubstrateBalanceGroup<T>;2482 ethBalanceGroup: EthereumBalanceGroup<T>;24832484 constructor(helper: T) {2485 super(helper);2486 this.subBalanceGroup = new SubstrateBalanceGroup(helper);2487 this.ethBalanceGroup = new EthereumBalanceGroup(helper);2488 }24892490 getCollectionCreationPrice(): bigint {2491 return 2n * this.getOneTokenNominal();2492 }2493 /**2494 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2495 * @example getOneTokenNominal()2496 * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.2497 */2498 getOneTokenNominal(): bigint {2499 const chainProperties = this.helper.chain.getChainProperties();2500 return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);2501 }25022503 /**2504 * Get substrate address balance2505 * @param address substrate address2506 * @example getSubstrate("5GrwvaEF5zXb26Fz...")2507 * @returns amount of tokens on address2508 */2509 getSubstrate(address: TSubstrateAccount): Promise<bigint> {2510 return this.subBalanceGroup.getSubstrate(address);2511 }25122513 /**2514 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved2515 * @param address substrate address2516 * @returns2517 */2518 getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2519 return this.subBalanceGroup.getSubstrateFull(address);2520 }25212522 /**2523 * Get total issuance2524 * @returns2525 */2526 getTotalIssuance(): Promise<bigint> {2527 return this.subBalanceGroup.getTotalIssuance();2528 }25292530 /**2531 * Get locked balances2532 * @param address substrate address2533 * @returns locked balances with reason via api.query.balances.locks2534 * @deprecated all the methods should switch to getFrozen2535 */2536 getLocked(address: TSubstrateAccount) {2537 return this.subBalanceGroup.getLocked(address);2538 }25392540 /**2541 * Get frozen balances2542 * @param address substrate address2543 * @returns frozen balances with id via api.query.balances.freezes2544 */2545 getFrozen(address: TSubstrateAccount) {2546 return this.subBalanceGroup.getFrozen(address);2547 }25482549 /**2550 * Get ethereum address balance2551 * @param address ethereum address2552 * @example getEthereum("0x9F0583DbB855d...")2553 * @returns amount of tokens on address2554 */2555 getEthereum(address: TEthereumAccount): Promise<bigint> {2556 return this.ethBalanceGroup.getEthereum(address);2557 }25582559 async setBalanceSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint) {2560 await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceSetBalance', [address, amount], true);2561 }25622563 /**2564 * Transfer tokens to substrate address2565 * @param signer keyring of signer2566 * @param address substrate address of a recipient2567 * @param amount amount of tokens to be transfered2568 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);2569 * @returns ```true``` if extrinsic success, otherwise ```false```2570 */2571 transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2572 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);2573 }25742575 async forceTransferToSubstrate(signer: TSigner, from: TSubstrateAccount, to: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2576 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.forceTransfer', [from, to, amount], true);25772578 let transfer = {from: null, to: null, amount: 0n} as any;2579 result.result.events.forEach(({event: {data, method, section}}: any) => {2580 if((section === 'balances') && (method === 'Transfer')) {2581 transfer = {2582 from: this.helper.address.normalizeSubstrate(data[0]),2583 to: this.helper.address.normalizeSubstrate(data[1]),2584 amount: BigInt(data[2]),2585 };2586 }2587 });2588 let isSuccess = this.helper.address.normalizeSubstrate(from) === transfer.from;2589 isSuccess = isSuccess && this.helper.address.normalizeSubstrate(to) === transfer.to;2590 isSuccess = isSuccess && BigInt(amount) === transfer.amount;2591 return isSuccess;2592 }25932594 /**2595 * Transfer tokens with the unlock period2596 * @param signer signers Keyring2597 * @param address Substrate address of recipient2598 * @param schedule Schedule params2599 * @example vestedTransfer(signer, recepient.address, 20000, 100, 10, 50 * nominal); // total amount of vested tokens will be 100 * 50 = 50002600 */2601 async vestedTransfer(signer: TSigner, address: TSubstrateAccount, schedule: { start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }): Promise<void> {2602 const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.vestedTransfer', [address, schedule]);2603 const event = result.result.events2604 .find((e: any) => e.event.section === 'vesting' &&2605 e.event.method === 'VestingScheduleAdded' &&2606 e.event.data[0].toHuman() === signer.address);2607 if(!event) throw Error('Cannot find transfer in events');2608 }26092610 /**2611 * Get schedule for recepient of vested transfer2612 * @param address Substrate address of recipient2613 * @returns2614 */2615 async getVestingSchedules(address: TSubstrateAccount): Promise<{ start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint }[]> {2616 const schedule = (await this.helper.callRpc('api.query.vesting.vestingSchedules', [address])).toJSON();2617 return schedule.map((schedule: any) => ({2618 start: BigInt(schedule.start),2619 period: BigInt(schedule.period),2620 periodCount: BigInt(schedule.periodCount),2621 perPeriod: BigInt(schedule.perPeriod),2622 }));2623 }26242625 /**2626 * Claim vested tokens2627 * @param signer signers Keyring2628 */2629 async claim(signer: TSigner) {2630 const result = await this.helper.executeExtrinsic(signer, 'api.tx.vesting.claim', []);2631 const event = result.result.events2632 .find((e: any) => e.event.section === 'vesting' &&2633 e.event.method === 'Claimed' &&2634 e.event.data[0].toHuman() === signer.address);2635 if(!event) throw Error('Cannot find claim in events');2636 }2637}26382639class AddressGroup extends HelperGroup<ChainHelperBase> {2640 /**2641 * Normalizes the address to the specified ss58 format, by default ```42```.2642 * @param address substrate address2643 * @param ss58Format format for address conversion, by default ```42```2644 * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY2645 * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation2646 */2647 normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {2648 return CrossAccountId.normalizeSubstrateAddress({Substrate: address}, ss58Format);2649 }26502651 /**2652 * Get address in the connected chain format2653 * @param address substrate address2654 * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network2655 * @returns address in chain format2656 */2657 normalizeSubstrateToChainFormat(address: TSubstrateAccount): TSubstrateAccount {2658 return this.normalizeSubstrate(address, this.helper.chain.getChainProperties().ss58Format);2659 }26602661 /**2662 * Get substrate mirror of an ethereum address2663 * @param ethAddress ethereum address2664 * @param toChainFormat false for normalized account2665 * @example ethToSubstrate('0x9F0583DbB855d...')2666 * @returns substrate mirror of a provided ethereum address2667 */2668 ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat = false): TSubstrateAccount {2669 return CrossAccountId.translateEthToSub(ethAddress, toChainFormat ? this.helper.chain.getChainProperties().ss58Format : undefined);2670 }26712672 /**2673 * Get ethereum mirror of a substrate address2674 * @param subAddress substrate account2675 * @example substrateToEth("5DnSF6RRjwteE3BrC...")2676 * @returns ethereum mirror of a provided substrate address2677 */2678 substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {2679 return CrossAccountId.translateSubToEth(subAddress);2680 }26812682 /**2683 * Encode key to substrate address2684 * @param key key for encoding address2685 * @param ss58Format prefix for encoding to the address of the corresponding network2686 * @returns encoded substrate address2687 */2688 encodeSubstrateAddress(key: Uint8Array | string | bigint, ss58Format = 42): string {2689 const u8a: Uint8Array = typeof key === 'string'2690 ? hexToU8a(key)2691 : typeof key === 'bigint'2692 ? hexToU8a(key.toString(16))2693 : key;26942695 if(ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {2696 throw new Error(`ss58Format is not valid, received ${typeofss58Format} "${ss58Format}"`);2697 }26982699 const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];2700 if(!allowedDecodedLengths.includes(u8a.length)) {2701 throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);2702 }27032704 const u8aPrefix = ss58Format < 642705 ? new Uint8Array([ss58Format])2706 : new Uint8Array([2707 ((ss58Format & 0xfc) >> 2) | 0x40,2708 (ss58Format >> 8) | ((ss58Format & 0x03) << 6),2709 ]);27102711 const input = u8aConcat(u8aPrefix, u8a);27122713 return base58Encode(u8aConcat(2714 input,2715 blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),2716 ));2717 }27182719 /**2720 * Restore substrate address from bigint representation2721 * @param number decimal representation of substrate address2722 * @returns substrate address2723 */2724 restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {2725 if(this.helper.api === null) {2726 throw 'Not connected';2727 }2728 const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();2729 if(res === undefined || res === null) {2730 throw 'Restore address error';2731 }2732 return res.toString();2733 }27342735 /**2736 * Convert etherium cross account id to substrate cross account id2737 * @param ethCrossAccount etherium cross account2738 * @returns substrate cross account id2739 */2740 convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {2741 if(ethCrossAccount.sub === '0') {2742 return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()};2743 }27442745 const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub));2746 return {Substrate: ss58};2747 }27482749 paraSiblingSovereignAccount(paraid: number) {2750 // We are getting a *sibling* parachain sovereign account,2751 // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c2752 const siblingPrefix = '0x7369626c';27532754 const encodedParaId = this.helper.getApi().createType('u32', paraid).toHex(true).substring(2);2755 const suffix = '000000000000000000000000000000000000000000000000';27562757 return siblingPrefix + encodedParaId + suffix;2758 }2759}276027612762class StakingGroup extends HelperGroup<UniqueHelper> {2763 /**2764 * Stake tokens for App Promotion2765 * @param signer keyring of signer2766 * @param amountToStake amount of tokens to stake2767 * @param label extra label for log2768 * @returns2769 */2770 async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise<boolean> {2771 if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`;2772 await this.helper.executeExtrinsic(2773 signer, 'api.tx.appPromotion.stake',2774 [amountToStake], true,2775 );2776 // TODO extract info from stakeResult2777 return true;2778 }27792780 /**2781 * Unstake all staked tokens2782 * @param signer keyring of signer2783 * @param amountToUnstake amount of tokens to unstake2784 * @param label extra label for log2785 * @returns block hash where unstake happened2786 */2787 async unstakeAll(signer: TSigner, label?: string): Promise<string> {2788 if(typeof label === 'undefined') label = `${signer.address}`;2789 const unstakeResult = await this.helper.executeExtrinsic(2790 signer, 'api.tx.appPromotion.unstakeAll',2791 [], true,2792 );2793 return unstakeResult.blockHash;2794 }27952796 /**2797 * Unstake the part of a staked tokens2798 * @param signer keyring of signer2799 * @param amount amount of tokens to unstake2800 * @param label extra label for log2801 * @returns block hash where unstake happened2802 */2803 async unstakePartial(signer: TSigner, amount: bigint, label?: string): Promise<string> {2804 if(typeof label === 'undefined') label = `${signer.address}`;2805 const unstakeResult = await this.helper.executeExtrinsic(2806 signer, 'api.tx.appPromotion.unstakePartial',2807 [amount], true,2808 );2809 return unstakeResult.blockHash;2810 }28112812 /**2813 * Get total number of active stakes2814 * @param address substrate address2815 * @returns {number}2816 */2817 async getStakesNumber(address: ICrossAccountId): Promise<number> {2818 if('Ethereum' in address) throw Error('only substrate address');2819 return (await this.helper.callRpc('api.query.appPromotion.stakesPerAccount', [address.Substrate])).toNumber();2820 }28212822 /**2823 * Get total staked amount for address2824 * @param address substrate or ethereum address2825 * @returns total staked amount2826 */2827 async getTotalStaked(address?: ICrossAccountId): Promise<bigint> {2828 if(address) return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked', [address])).toBigInt();2829 return (await this.helper.callRpc('api.rpc.appPromotion.totalStaked')).toBigInt();2830 }28312832 /**2833 * Get total staked per block2834 * @param address substrate or ethereum address2835 * @returns array of stakes. `block` – the number of the block in which the stake was made. `amount` - the number of tokens staked in the block2836 */2837 async getTotalStakedPerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2838 const rawTotalStakerdPerBlock = await this.helper.callRpc('api.rpc.appPromotion.totalStakedPerBlock', [address]);2839 return rawTotalStakerdPerBlock.map(([block, amount]: any[]) => ({2840 block: block.toBigInt(),2841 amount: amount.toBigInt(),2842 }));2843 }28442845 /**2846 * Get total pending unstake amount for address2847 * @param address substrate or ethereum address2848 * @returns total pending unstake amount2849 */2850 async getPendingUnstake(address: ICrossAccountId): Promise<bigint> {2851 return (await this.helper.callRpc('api.rpc.appPromotion.pendingUnstake', [address])).toBigInt();2852 }28532854 /**2855 * Get pending unstake amount per block for address2856 * @param address substrate or ethereum address2857 * @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 block2858 */2859 async getPendingUnstakePerBlock(address: ICrossAccountId): Promise<IStakingInfo[]> {2860 const rawUnstakedPerBlock = await this.helper.callRpc('api.rpc.appPromotion.pendingUnstakePerBlock', [address]);2861 const result = rawUnstakedPerBlock.map(([block, amount]: any[]) => ({2862 block: block.toBigInt(),2863 amount: amount.toBigInt(),2864 }));2865 return result;2866 }2867}286828692870class PreimageGroup extends HelperGroup<UniqueHelper> {2871 async getPreimageInfo(h256: string) {2872 return (await this.helper.callRpc('api.query.preimage.statusFor', [h256])).toJSON();2873 }28742875 /**2876 * Create a preimage from an API call.2877 * @param signer keyring of the signer.2878 * @param call an extrinsic call2879 * @example await notePreimageFromCall(preimageMaker,2880 * helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd])2881 * );2882 * @returns promise of extrinsic execution.2883 */2884 notePreimageFromCall(signer: TSigner, call: any, returnPreimageHash = false) {2885 return this.notePreimage(signer, call.method.toHex(), returnPreimageHash);2886 }28872888 /**2889 * Create a preimage with a hex or a byte array.2890 * @param signer keyring of the signer.2891 * @param bytes preimage encoded in hex or a byte array, e.g. an extrinsic call.2892 * @example await notePreimage(preimageMaker,2893 * helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex()2894 * );2895 * @returns promise of extrinsic execution.2896 */2897 async notePreimage(signer: TSigner, bytes: string | Uint8Array, returnPreimageHash = false) {2898 const promise = this.helper.executeExtrinsic(signer, 'api.tx.preimage.notePreimage', [bytes]);2899 if(returnPreimageHash) {2900 const result = await promise;2901 const events = result.result.events.filter((x: any) => x.event.method === 'Noted' && x.event.section === 'preimage');2902 const preimageHash = events[0].event.data[0].toHuman();2903 return preimageHash;2904 }2905 return promise;2906 }29072908 /**2909 * Delete an existing preimage and return the deposit.2910 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).2911 * @param h256 hash of the preimage.2912 * @returns promise of extrinsic execution.2913 */2914 unnotePreimage(signer: TSigner, h256: string) {2915 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unnotePreimage', [h256]);2916 }29172918 /**2919 * Request a preimage be uploaded to the chain without paying any fees or deposits.2920 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).2921 * @param h256 hash of the preimage.2922 * @returns promise of extrinsic execution.2923 */2924 requestPreimage(signer: TSigner, h256: string) {2925 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.requestPreimage', [h256]);2926 }29272928 /**2929 * Clear a previously made request for a preimage.2930 * @param signer keyring of the signer - either the owner or the preimage manager (sudo).2931 * @param h256 hash of the preimage.2932 * @returns promise of extrinsic execution.2933 */2934 unrequestPreimage(signer: TSigner, h256: string) {2935 return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]);2936 }2937}29382939class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {2940 async batch(signer: TSigner, txs: any[]) {2941 return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batch', [txs]);2942 }29432944 async batchAll(signer: TSigner, txs: any[]) {2945 return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batchAll', [txs]);2946 }29472948 batchAllCall(txs: any[]) {2949 return this.helper.constructApiCall('api.tx.utility.batchAll', [txs]);2950 }2951}29522953export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;2954export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;29552956export class UniqueHelper extends ChainHelperBase {2957 balance: BalanceGroup<UniqueHelper>;2958 collection: CollectionGroup;2959 nft: NFTGroup;2960 rft: RFTGroup;2961 ft: FTGroup;2962 staking: StakingGroup;2963 preimage: PreimageGroup;2964 utility: UtilityGroup<UniqueHelper>;29652966 constructor(logger?: ILogger, options: { [key: string]: any } = {}) {2967 super(logger, options.helperBase ?? UniqueHelper);29682969 this.balance = new BalanceGroup(this);2970 this.collection = new CollectionGroup(this);2971 this.nft = new NFTGroup(this);2972 this.rft = new RFTGroup(this);2973 this.ft = new FTGroup(this);2974 this.staking = new StakingGroup(this);2975 this.preimage = new PreimageGroup(this);2976 this.utility = new UtilityGroup(this);2977 }2978}29792980export class UniqueBaseCollection {2981 helper: UniqueHelper;2982 collectionId: number;29832984 constructor(collectionId: number, uniqueHelper: UniqueHelper) {2985 this.collectionId = collectionId;2986 this.helper = uniqueHelper;2987 }29882989 async getData() {2990 return await this.helper.collection.getData(this.collectionId);2991 }29922993 async getLastTokenId() {2994 return await this.helper.collection.getLastTokenId(this.collectionId);2995 }29962997 async doesTokenExist(tokenId: number) {2998 return await this.helper.collection.doesTokenExist(this.collectionId, tokenId);2999 }30003001 async getAdmins() {3002 return await this.helper.collection.getAdmins(this.collectionId);3003 }30043005 async getAllowList() {3006 return await this.helper.collection.getAllowList(this.collectionId);3007 }30083009 async getEffectiveLimits() {3010 return await this.helper.collection.getEffectiveLimits(this.collectionId);3011 }30123013 async getProperties(propertyKeys?: string[] | null) {3014 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3015 }30163017 async getPropertiesConsumedSpace() {3018 return await this.helper.collection.getPropertiesConsumedSpace(this.collectionId);3019 }30203021 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3022 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3023 }30243025 async getOptions() {3026 return await this.helper.collection.getCollectionOptions(this.collectionId);3027 }30283029 async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) {3030 return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress);3031 }30323033 async confirmSponsorship(signer: TSigner) {3034 return await this.helper.collection.confirmSponsorship(signer, this.collectionId);3035 }30363037 async removeSponsor(signer: TSigner) {3038 return await this.helper.collection.removeSponsor(signer, this.collectionId);3039 }30403041 async setLimits(signer: TSigner, limits: ICollectionLimits) {3042 return await this.helper.collection.setLimits(signer, this.collectionId, limits);3043 }30443045 async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount) {3046 return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress);3047 }30483049 async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3050 return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj);3051 }30523053 async addToAllowList(signer: TSigner, addressObj: ICrossAccountId) {3054 return await this.helper.collection.addToAllowList(signer, this.collectionId, addressObj);3055 }30563057 async removeFromAllowList(signer: TSigner, addressObj: ICrossAccountId) {3058 return await this.helper.collection.removeFromAllowList(signer, this.collectionId, addressObj);3059 }30603061 async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId) {3062 return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj);3063 }30643065 async setProperties(signer: TSigner, properties: IProperty[]) {3066 return await this.helper.collection.setProperties(signer, this.collectionId, properties);3067 }30683069 async deleteProperties(signer: TSigner, propertyKeys: string[]) {3070 return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys);3071 }30723073 async setPermissions(signer: TSigner, permissions: ICollectionPermissions) {3074 return await this.helper.collection.setPermissions(signer, this.collectionId, permissions);3075 }30763077 async enableNesting(signer: TSigner, permissions: INestingPermissions) {3078 return await this.helper.collection.enableNesting(signer, this.collectionId, permissions);3079 }30803081 async disableNesting(signer: TSigner) {3082 return await this.helper.collection.disableNesting(signer, this.collectionId);3083 }30843085 async burn(signer: TSigner) {3086 return await this.helper.collection.burn(signer, this.collectionId);3087 }3088}30893090export class UniqueNFTCollection extends UniqueBaseCollection {3091 getTokenObject(tokenId: number) {3092 return new UniqueNFToken(tokenId, this);3093 }30943095 async getTokensByAddress(addressObj: ICrossAccountId) {3096 return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);3097 }30983099 async getToken(tokenId: number, blockHashAt?: string) {3100 return await this.helper.nft.getToken(this.collectionId, tokenId, [], blockHashAt);3101 }31023103 async getTokenOwner(tokenId: number, blockHashAt?: string) {3104 return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3105 }31063107 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3108 return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3109 }31103111 async getTokenChildren(tokenId: number, blockHashAt?: string) {3112 return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);3113 }31143115 async getPropertyPermissions(propertyKeys: string[] | null = null) {3116 return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys);3117 }31183119 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3120 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3121 }31223123 async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {3124 const api = this.helper.getApi();3125 const props = (await api.query.nonfungible.tokenProperties(this.collectionId, tokenId)).toJSON() as any;31263127 return props?.consumedSpace ?? 0;3128 }31293130 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {3131 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);3132 }31333134 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3135 return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);3136 }31373138 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId) {3139 return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj);3140 }31413142 async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {3143 return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);3144 }31453146 async mintToken(signer: TSigner, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3147 return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties});3148 }31493150 async mintMultipleTokens(signer: TSigner, tokens: { owner: ICrossAccountId, properties?: IProperty[] }[]) {3151 return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens);3152 }31533154 async burnToken(signer: TSigner, tokenId: number) {3155 return await this.helper.nft.burnToken(signer, this.collectionId, tokenId);3156 }31573158 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) {3159 return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj);3160 }31613162 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3163 return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties);3164 }31653166 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3167 return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3168 }31693170 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3171 return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3172 }31733174 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3175 return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3176 }31773178 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3179 return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3180 }3181}31823183export class UniqueRFTCollection extends UniqueBaseCollection {3184 getTokenObject(tokenId: number) {3185 return new UniqueRFToken(tokenId, this);3186 }31873188 async getToken(tokenId: number, blockHashAt?: string) {3189 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3190 }31913192 async getTokenOwner(tokenId: number, blockHashAt?: string) {3193 return await this.helper.rft.getTokenOwner(this.collectionId, tokenId, blockHashAt);3194 }31953196 async getTokensByAddress(addressObj: ICrossAccountId) {3197 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3198 }31993200 async getTop10TokenOwners(tokenId: number) {3201 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3202 }32033204 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {3205 return await this.helper.rft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);3206 }32073208 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3209 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3210 }32113212 async getTokenTotalPieces(tokenId: number) {3213 return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);3214 }32153216 async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3217 return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);3218 }32193220 async getPropertyPermissions(propertyKeys: string[] | null = null) {3221 return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys);3222 }32233224 async getTokenProperties(tokenId: number, propertyKeys?: string[] | null) {3225 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3226 }32273228 async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {3229 const api = this.helper.getApi();3230 const props = (await api.query.refungible.tokenProperties(this.collectionId, tokenId)).toJSON() as any;32313232 return props?.consumedSpace ?? 0;3233 }32343235 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount = 1n) {3236 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3237 }32383239 async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {3240 return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);3241 }32423243 async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount = 1n) {3244 return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount);3245 }32463247 async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) {3248 return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount);3249 }32503251 async mintToken(signer: TSigner, pieces = 1n, owner: ICrossAccountId = {Substrate: signer.address}, properties?: IProperty[]) {3252 return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties});3253 }32543255 async mintMultipleTokens(signer: TSigner, tokens: { pieces: bigint, owner: ICrossAccountId, properties?: IProperty[] }[]) {3256 return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens);3257 }32583259 async burnToken(signer: TSigner, tokenId: number, amount = 1n) {3260 return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount);3261 }32623263 async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount = 1n) {3264 return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount);3265 }32663267 async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) {3268 return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties);3269 }32703271 async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[]) {3272 return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys);3273 }32743275 async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) {3276 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3277 }32783279 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {3280 return await this.helper.rft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);3281 }32823283 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3284 return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);3285 }3286}32873288export class UniqueFTCollection extends UniqueBaseCollection {3289 async getBalance(addressObj: ICrossAccountId) {3290 return await this.helper.ft.getBalance(this.collectionId, addressObj);3291 }32923293 async getTotalPieces() {3294 return await this.helper.ft.getTotalPieces(this.collectionId);3295 }32963297 async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3298 return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);3299 }33003301 async getTop10Owners() {3302 return await this.helper.ft.getTop10Owners(this.collectionId);3303 }33043305 async mint(signer: TSigner, amount = 1n, owner: ICrossAccountId = {Substrate: signer.address}) {3306 return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner);3307 }33083309 async mintWithOneOwner(signer: TSigner, tokens: { value: bigint }[], owner: ICrossAccountId = {Substrate: signer.address}) {3310 return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner);3311 }33123313 async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {3314 return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);3315 }33163317 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {3318 return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);3319 }33203321 async burnTokens(signer: TSigner, amount = 1n) {3322 return await this.helper.ft.burnTokens(signer, this.collectionId, amount);3323 }33243325 async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {3326 return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount);3327 }33283329 async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {3330 return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);3331 }3332}33333334export class UniqueBaseToken {3335 collection: UniqueNFTCollection | UniqueRFTCollection;3336 collectionId: number;3337 tokenId: number;33383339 constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {3340 this.collection = collection;3341 this.collectionId = collection.collectionId;3342 this.tokenId = tokenId;3343 }33443345 async getNextSponsored(addressObj: ICrossAccountId) {3346 return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);3347 }33483349 async getProperties(propertyKeys?: string[] | null) {3350 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3351 }33523353 async getTokenPropertiesConsumedSpace() {3354 return await this.collection.getTokenPropertiesConsumedSpace(this.tokenId);3355 }33563357 async setProperties(signer: TSigner, properties: IProperty[]) {3358 return await this.collection.setTokenProperties(signer, this.tokenId, properties);3359 }33603361 async deleteProperties(signer: TSigner, propertyKeys: string[]) {3362 return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys);3363 }33643365 async doesExist() {3366 return await this.collection.doesTokenExist(this.tokenId);3367 }33683369 nestingAccount(): ICrossAccountId {3370 return this.collection.helper.util.getTokenAccount(this);3371 }3372}33733374export class UniqueNFToken extends UniqueBaseToken {3375 declare collection: UniqueNFTCollection;33763377 constructor(tokenId: number, collection: UniqueNFTCollection) {3378 super(tokenId, collection);3379 this.collection = collection;3380 }33813382 async getData(blockHashAt?: string) {3383 return await this.collection.getToken(this.tokenId, blockHashAt);3384 }33853386 async getOwner(blockHashAt?: string) {3387 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3388 }33893390 async getTopmostOwner(blockHashAt?: string) {3391 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3392 }33933394 async getChildren(blockHashAt?: string) {3395 return await this.collection.getTokenChildren(this.tokenId, blockHashAt);3396 }33973398 async nest(signer: TSigner, toTokenObj: IToken) {3399 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3400 }34013402 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3403 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3404 }34053406 async transfer(signer: TSigner, addressObj: ICrossAccountId) {3407 return await this.collection.transferToken(signer, this.tokenId, addressObj);3408 }34093410 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {3411 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);3412 }34133414 async approve(signer: TSigner, toAddressObj: ICrossAccountId) {3415 return await this.collection.approveToken(signer, this.tokenId, toAddressObj);3416 }34173418 async isApproved(toAddressObj: ICrossAccountId) {3419 return await this.collection.isTokenApproved(this.tokenId, toAddressObj);3420 }34213422 async burn(signer: TSigner) {3423 return await this.collection.burnToken(signer, this.tokenId);3424 }34253426 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {3427 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);3428 }3429}34303431export class UniqueRFToken extends UniqueBaseToken {3432 declare collection: UniqueRFTCollection;34333434 constructor(tokenId: number, collection: UniqueRFTCollection) {3435 super(tokenId, collection);3436 this.collection = collection;3437 }34383439 async getData(blockHashAt?: string) {3440 return await this.collection.getToken(this.tokenId, blockHashAt);3441 }34423443 async getOwner(blockHashAt?: string) {3444 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);3445 }34463447 async getTop10Owners() {3448 return await this.collection.getTop10TokenOwners(this.tokenId);3449 }34503451 async getTopmostOwner(blockHashAt?: string) {3452 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);3453 }34543455 async nest(signer: TSigner, toTokenObj: IToken) {3456 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);3457 }34583459 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {3460 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);3461 }34623463 async getBalance(addressObj: ICrossAccountId) {3464 return await this.collection.getTokenBalance(this.tokenId, addressObj);3465 }34663467 async getTotalPieces() {3468 return await this.collection.getTokenTotalPieces(this.tokenId);3469 }34703471 async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {3472 return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);3473 }34743475 async transfer(signer: TSigner, addressObj: ICrossAccountId, amount = 1n) {3476 return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);3477 }34783479 async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount = 1n) {3480 return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);3481 }34823483 async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {3484 return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount);3485 }34863487 async repartition(signer: TSigner, amount: bigint) {3488 return await this.collection.repartitionToken(signer, this.tokenId, amount);3489 }34903491 async burn(signer: TSigner, amount = 1n) {3492 return await this.collection.burnToken(signer, this.tokenId, amount);3493 }34943495 async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {3496 return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);3497 }3498}js-packages/tests/scheduler.seqtest.tsdiffbeforeafterboth--- a/js-packages/tests/scheduler.seqtest.ts
+++ b/js-packages/tests/scheduler.seqtest.ts
@@ -507,7 +507,7 @@
for(let offset = 0; offset < numFilledBlocks; offset ++) {
for(let i = 0; i < maxScheduledPerBlock; i++) {
- const scheduledTx = helper.constructApiCall('api.tx.balances.transfer', [bob.address, 1n]);
+ const scheduledTx = helper.constructApiCall('api.tx.balances.transferKeepAlive', [bob.address, 1n]);
const when = firstExecutionBlockNumber + period + offset;
const mandatoryArgs = [when, null, null, scheduledTx];
@@ -710,7 +710,7 @@
// await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);
// // Grace zeroBalance with money, enough to cover future transactions
- // const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);
+ // const balanceTx = api.tx.balances.transferKeepAlive(zeroBalance.address, 1n * UNIQUE);
// await submitTransactionAsync(alice, balanceTx);
// // Mint a fresh NFT
@@ -722,7 +722,7 @@
// await scheduleTransferExpectSuccess(api, collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, scheduledId);
// // Get rid of the account's funds before the scheduled transaction takes place
- // const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);
+ // const balanceTx2 = api.tx.balances.transferKeepAlive(alice.address, UNIQUE * 68n / 100n);
// const events = await submitTransactionAsync(zeroBalance, balanceTx2);
// expect(getGenericResult(events).success).to.be.true;
// /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?
@@ -742,7 +742,7 @@
// await usingApi(async (api, privateKey) => {
// const zeroBalance = await findUnusedAddress(api, privateKey);
- // const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);
+ // const balanceTx = api.tx.balances.transferKeepAlive(zeroBalance.address, 1n * UNIQUE);
// await submitTransactionAsync(alice, balanceTx);
// await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);
js-packages/tests/util/globalSetup.tsdiffbeforeafterboth--- a/js-packages/tests/util/globalSetup.ts
+++ b/js-packages/tests/util/globalSetup.ts
@@ -81,7 +81,7 @@
if(aliceBalance < MINIMUM_DONOR_FUND * oneToken) {
tx.push(helper.executeExtrinsic(
alice,
- 'api.tx.balances.transfer',
+ 'api.tx.balances.transferKeepAlive',
[account.address, DONOR_FUNDING * oneToken],
true,
{nonce: nonce + balanceGrantedCounter++},