git.delta.rocks / unique-network / refs/commits / ea2cca91b119

difftreelog

feat enrich playgrounds with xcm capability

Daniel Shiposha2022-10-10parent: #8d90cab.patch.diff
in: master

4 files changed

modifiedtests/src/util/playgrounds/index.tsdiffbeforeafterboth
7import {Context} from 'mocha';7import {Context} from 'mocha';
8import config from '../../config';8import config from '../../config';
9import '../../interfaces/augment-api-events';9import '../../interfaces/augment-api-events';
10import {ChainHelperBase} from './unique';
11import {ILogger} from './types';
10import {DevUniqueHelper, SilentLogger, SilentConsole} from './unique.dev';12import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper} from './unique.dev';
1113
12chai.use(chaiAsPromised);14chai.use(chaiAsPromised);
13export const expect = chai.expect;15export const expect = chai.expect;
1416
15export async function usingPlaygrounds(code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>, url: string = config.substrateUrl) {17async function usingPlaygroundsGeneral<T extends ChainHelperBase>(helperType: new(logger: ILogger) => T, url: string, code: (helper: T, privateKey: (seed: string) => IKeyringPair) => Promise<void>) {
16 const silentConsole = new SilentConsole();18 const silentConsole = new SilentConsole();
17 silentConsole.enable();19 silentConsole.enable();
1820
19 const helper = new DevUniqueHelper(new SilentLogger());21 const helper = new helperType(new SilentLogger());
2022
21 try {23 try {
22 await helper.connect(url);24 await helper.connect(url);
30 }32 }
31}33}
3234
35export const usingPlaygrounds = (code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>, url: string = config.substrateUrl) => {
36 return usingPlaygroundsGeneral<DevUniqueHelper>(DevUniqueHelper, url, code);
37};
38
39// TODO specific type
33usingPlaygrounds.atUrl = (url: string, code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {40export const usingStatemintPlaygrounds = async (url: string, code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
34 return usingPlaygrounds(code, url);41 return usingPlaygroundsGeneral<DevUniqueHelper>(DevUniqueHelper, url, code);
35};42};
43
44export const usingRelayPlaygrounds = async (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
45 return usingPlaygroundsGeneral<DevRelayHelper>(DevRelayHelper, url, code);
46};
47
48export const usingAcalaPlaygrounds = async (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
49 return usingPlaygroundsGeneral<DevAcalaHelper>(DevAcalaHelper, url, code);
50};
51
52export const usingKaruraPlaygrounds = async (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
53 return usingPlaygroundsGeneral<DevKaruraHelper>(DevAcalaHelper, url, code);
54};
55
56export const usingMoonbeamPlaygrounds = async (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
57 return usingPlaygroundsGeneral<DevMoonbeamHelper>(DevMoonbeamHelper, url, code);
58};
59
60export const usingMoonriverPlaygrounds = async (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
61 return usingPlaygroundsGeneral<DevMoonriverHelper>(DevMoonriverHelper, url, code);
62};
3663
37export enum Pallets {64export enum Pallets {
38 Inflation = 'inflation',65 Inflation = 'inflation',
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
179 minimalBalance?: bigint,179 minimalBalance?: bigint,
180}180}
181
182export interface MoonbeamAssetInfo {
183 location: any,
184 metadata: {
185 name: string,
186 symbol: string,
187 decimals: number,
188 isFrozen: boolean,
189 minimalBalance: bigint,
190 },
191 existentialDeposit: bigint,
192 isSufficient: boolean,
193 unitsPerSecond: bigint,
194 numAssetsWeightHint: number,
195}
196
197export interface AcalaAssetMetadata {
198 name: string,
199 symbol: string,
200 decimals: number,
201 minimalBalance: bigint,
202}
203
204export interface DemocracyStandardAccountVote {
205 balance: bigint,
206 vote: {
207 aye: boolean,
208 conviction: number,
209 },
210}
181211
182export type TSubstrateAccount = string;212export type TSubstrateAccount = string;
183export type TEthereumAccount = string;213export type TEthereumAccount = string;
184export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';214export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';
185export type TUniqueNetworks = 'opal' | 'quartz' | 'unique';215export type TUniqueNetworks = 'opal' | 'quartz' | 'unique';
216export type TSiblingNetworkds = 'moonbeam' | 'moonriver' | 'acala' | 'karura' | 'westmint';
217export type TRelayNetworks = 'rococo' | 'westend';
218export type TNetworks = TUniqueNetworks | TSiblingNetworkds | TRelayNetworks;
186export type TSigner = IKeyringPair; // | 'string'219export type TSigner = IKeyringPair; // | 'string'
187220
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
2// SPDX-License-Identifier: Apache-2.02// SPDX-License-Identifier: Apache-2.0
33
4import {mnemonicGenerate} from '@polkadot/util-crypto';4import {mnemonicGenerate} from '@polkadot/util-crypto';
5import {UniqueHelper} from './unique';5import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper} from './unique';
6import {ApiPromise, WsProvider} from '@polkadot/api';6import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
7import * as defs from '../../interfaces/definitions';7import * as defs from '../../interfaces/definitions';
8import {IKeyringPair} from '@polkadot/types/types';8import {IKeyringPair} from '@polkadot/types/types';
9import {EventRecord} from '@polkadot/types/interfaces';
9import {ICrossAccountId} from './types';10import {ICrossAccountId} from './types';
1011
11export class SilentLogger {12export class SilentLogger {
108 }108 }
109}109}
110
111export class DevRelayHelper extends RelayHelper {}
112
113export class DevMoonbeamHelper extends MoonbeamHelper {
114 account: MoonbeamAccountGroup;
115 wait: WaitGroup;
116
117 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
118 options.helperBase = options.helperBase ?? DevMoonbeamHelper;
119
120 super(logger, options);
121 this.account = new MoonbeamAccountGroup(this);
122 this.wait = new WaitGroup(this);
123 }
124}
125
126export class DevMoonriverHelper extends DevMoonbeamHelper {}
127
128export class DevAcalaHelper extends AcalaHelper {
129 wait: WaitGroup;
130
131 constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
132 options.helperBase = options.helperBase ?? DevAcalaHelper;
133
134 super(logger, options);
135 this.wait = new WaitGroup(this);
136 }
137}
138
139export class DevKaruraHelper extends DevAcalaHelper {}
110140
111class ArrangeGroup {141class ArrangeGroup {
112 helper: DevUniqueHelper;142 helper: DevUniqueHelper;
245 }275 }
246}276}
277
278class MoonbeamAccountGroup {
279 helper: MoonbeamHelper;
280
281 _alithAccount: IKeyringPair;
282 _baltatharAccount: IKeyringPair;
283 _dorothyAccount: IKeyringPair;
284
285 constructor(helper: MoonbeamHelper) {
286 this.helper = helper;
287
288 const keyring = new Keyring({type: 'ethereum'});
289 const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';
290 const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';
291 const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';
292
293 this._alithAccount = keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');
294 this._baltatharAccount = keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');
295 this._dorothyAccount = keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');
296 }
297
298 alithAccount() {
299 return this._alithAccount;
300 }
301
302 baltatharAccount() {
303 return this._baltatharAccount;
304 }
305
306 dorothyAccount() {
307 return this._dorothyAccount;
308 }
309}
247310
248class WaitGroup {311class WaitGroup {
249 helper: DevUniqueHelper;312 helper: ChainHelperBase;
250313
251 constructor(helper: DevUniqueHelper) {314 constructor(helper: ChainHelperBase) {
252 this.helper = helper;315 this.helper = helper;
253 }316 }
254317
297 });360 });
298 }361 }
362
363 async event(maxBlocksToWait: number, eventSection: string, eventMethod: string) {
364 // eslint-disable-next-line no-async-promise-executor
365 const promise = new Promise<EventRecord | null>(async (resolve) => {
366 const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {
367 const blockNumber = header.number.toHuman();
368 const blockHash = header.hash;
369 const eventIdStr = `${eventSection}.${eventMethod}`;
370 const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
371
372 console.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
373
374 const apiAt = await this.helper.getApi().at(blockHash);
375 const eventRecords = await apiAt.query.system.events();
376
377 const neededEvent = eventRecords.find(r => {
378 return r.event.section == eventSection && r.event.method == eventMethod;
379 });
380
381 if (neededEvent) {
382 unsubscribe();
383 resolve(neededEvent);
384 } else if (maxBlocksToWait > 0) {
385 maxBlocksToWait--;
386 } else {
387 console.log(`Event \`${eventIdStr}\` is NOT found`);
388
389 unsubscribe();
390 resolve(null);
391 }
392 });
393 });
394 return promise;
395 }
299}396}
300397
301class AdminGroup {398class AdminGroup {
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';9import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';
10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';10import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';
11import {IKeyringPair} from '@polkadot/types/types';11import {IKeyringPair} from '@polkadot/types/types';
12import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks, IForeignAssetMetadata} from './types';12import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata} from './types';
1313
14export class CrossAccountId implements ICrossAccountId {14export class CrossAccountId implements ICrossAccountId {
15 Substrate?: TSubstrateAccount;15 Substrate?: TSubstrateAccount;
308 }308 }
309}309}
310310
311class ChainHelperBase {311export class ChainHelperBase {
312 helperBase: any;
313
312 transactionStatus = UniqueUtil.transactionStatus;314 transactionStatus = UniqueUtil.transactionStatus;
313 chainLogType = UniqueUtil.chainLogType;315 chainLogType = UniqueUtil.chainLogType;
314 util: typeof UniqueUtil;316 util: typeof UniqueUtil;
315 eventHelper: typeof UniqueEventHelper;317 eventHelper: typeof UniqueEventHelper;
316 logger: ILogger;318 logger: ILogger;
317 api: ApiPromise | null;319 api: ApiPromise | null;
318 forcedNetwork: TUniqueNetworks | null;320 forcedNetwork: TNetworks | null;
319 network: TUniqueNetworks | null;321 network: TNetworks | null;
320 chainLog: IUniqueHelperLog[];322 chainLog: IUniqueHelperLog[];
321 children: ChainHelperBase[];323 children: ChainHelperBase[];
324 address: AddressGroup;
325 chain: ChainGroup;
322326
323 constructor(logger?: ILogger) {327 constructor(logger?: ILogger, helperBase?: any) {
328 this.helperBase = helperBase;
329
324 this.util = UniqueUtil;330 this.util = UniqueUtil;
325 this.eventHelper = UniqueEventHelper;331 this.eventHelper = UniqueEventHelper;
330 this.network = null;336 this.network = null;
331 this.chainLog = [];337 this.chainLog = [];
332 this.children = [];338 this.children = [];
339 this.address = new AddressGroup(this);
340 this.chain = new ChainGroup(this);
333 }341 }
334342
343 clone(helperCls: ChainHelperBaseConstructor, options: {[key: string]: any} = {}) {
344 Object.setPrototypeOf(helperCls.prototype, this);
345 const newHelper = new helperCls(this.logger, options);
346
347 newHelper.api = this.api;
348 newHelper.network = this.network;
349 newHelper.forceNetwork = this.forceNetwork;
350
351 this.children.push(newHelper);
352
353 return newHelper;
354 }
355
335 getApi(): ApiPromise {356 getApi(): ApiPromise {
336 if(this.api === null) throw Error('API not initialized');357 if(this.api === null) throw Error('API not initialized');
337 return this.api;358 return this.api;
341 this.chainLog = [];362 this.chainLog = [];
342 }363 }
343364
344 forceNetwork(value: TUniqueNetworks): void {365 forceNetwork(value: TNetworks): void {
345 this.forcedNetwork = value;366 this.forcedNetwork = value;
346 }367 }
347368
367 this.network = null;388 this.network = null;
368 }389 }
369390
370 static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {391 static async detectNetwork(api: ApiPromise): Promise<TNetworks> {
371 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;392 const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;
393 const xcmChains = ['rococo', 'westend', 'westmint', 'acala', 'karura', 'moonbeam', 'moonriver'];
394
395 if(xcmChains.indexOf(spec.specName) > -1) return spec.specName;
396
372 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;397 if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;
373 return 'opal';398 return 'opal';
374 }399 }
375400
376 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {401 static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TNetworks> {
377 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});402 const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});
378 await api.isReady;403 await api.isReady;
379404
384 return network;409 return network;
385 }410 }
386411
387 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{412 static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TNetworks | null): Promise<{
388 api: ApiPromise;413 api: ApiPromise;
389 network: TUniqueNetworks;414 network: TNetworks;
390 }> {415 }> {
416 console.log('createConnection network = ', network);
391 if(typeof network === 'undefined' || network === null) network = 'opal';417 if(typeof network === 'undefined' || network === null) network = 'opal';
392 const supportedRPC = {418 const supportedRPC = {
393 opal: {419 opal: {
399 unique: {425 unique: {
400 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,426 unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,
401 },427 },
428 rococo: {},
429 westend: {},
430 moonbeam: {},
431 moonriver: {},
432 acala: {},
433 karura: {},
434 westmint: {},
402 };435 };
403 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);436 if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);
404 const rpc = supportedRPC[network];437 const rpc = supportedRPC[network];
590}623}
591624
592625
593class HelperGroup {626class HelperGroup<T extends ChainHelperBase> {
594 helper: UniqueHelper;627 helper: T;
595628
596 constructor(uniqueHelper: UniqueHelper) {629 constructor(uniqueHelper: T) {
597 this.helper = uniqueHelper;630 this.helper = uniqueHelper;
598 }631 }
599}632}
600633
601634
602class CollectionGroup extends HelperGroup {635class CollectionGroup extends HelperGroup<UniqueHelper> {
603 /**636 /**
604 * Get number of blocks when sponsored transaction is available.637 * Get number of blocks when sponsored transaction is available.
605 *638 *
2000}2033}
20012034
20022035
2003class ChainGroup extends HelperGroup {2036class ChainGroup extends HelperGroup<ChainHelperBase> {
2004 /**2037 /**
2005 * Get system properties of a chain2038 * Get system properties of a chain
2006 * @example getChainProperties();2039 * @example getChainProperties();
2054 }2087 }
2055}2088}
20562089
2090class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2091 /**
2092 * Get substrate address balance
2093 * @param address substrate address
2094 * @example getSubstrate("5GrwvaEF5zXb26Fz...")
2095 * @returns amount of tokens on address
2096 */
2097 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {
2098 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();
2099 }
20572100
2058class BalanceGroup extends HelperGroup {2101 /**
2102 * Transfer tokens to substrate address
2103 * @param signer keyring of signer
2104 * @param address substrate address of a recipient
2105 * @param amount amount of tokens to be transfered
2106 * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);
2107 * @returns ```true``` if extrinsic success, otherwise ```false```
2108 */
2109 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {
2110 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/);
2111
2112 let transfer = {from: null, to: null, amount: 0n} as any;
2113 result.result.events.forEach(({event: {data, method, section}}) => {
2114 if ((section === 'balances') && (method === 'Transfer')) {
2115 transfer = {
2116 from: this.helper.address.normalizeSubstrate(data[0]),
2117 to: this.helper.address.normalizeSubstrate(data[1]),
2118 amount: BigInt(data[2]),
2119 };
2120 }
2121 });
2122 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from
2123 && this.helper.address.normalizeSubstrate(address) === transfer.to
2124 && BigInt(amount) === transfer.amount;
2125 return isSuccess;
2126 }
2127
2128 /**
2129 * Get full substrate balance including free, miscFrozen, feeFrozen, and reserved
2130 * @param address substrate address
2131 * @returns
2132 */
2133 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {
2134 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;
2135 return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
2136 }
2137}
2138
2139class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2140 /**
2141 * Get ethereum address balance
2142 * @param address ethereum address
2143 * @example getEthereum("0x9F0583DbB855d...")
2144 * @returns amount of tokens on address
2145 */
2146 async getEthereum(address: TEthereumAccount): Promise<bigint> {
2147 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();
2148 }
2149
2150 /**
2151 * Transfer tokens to address
2152 * @param signer keyring of signer
2153 * @param address Ethereum address of a recipient
2154 * @param amount amount of tokens to be transfered
2155 * @example transferToEthereum(alithKeyring, "0x9F0583DbB855d...", 100_000_000_000n);
2156 * @returns ```true``` if extrinsic success, otherwise ```false```
2157 */
2158 async transferToEthereum(signer: TSigner, address: TEthereumAccount, amount: bigint | string): Promise<boolean> {
2159 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true);
2160
2161 let transfer = {from: null, to: null, amount: 0n} as any;
2162 result.result.events.forEach(({event: {data, method, section}}) => {
2163 if ((section === 'balances') && (method === 'Transfer')) {
2164 transfer = {
2165 from: data[0].toString(),
2166 to: data[1].toString(),
2167 amount: BigInt(data[2]),
2168 };
2169 }
2170 });
2171 const isSuccess = (typeof signer === 'string' ? signer : signer.address) === transfer.from
2172 && address === transfer.to
2173 && BigInt(amount) === transfer.amount;
2174 return isSuccess;
2175 }
2176}
2177
2178class BalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2179 subBalanceGroup: SubstrateBalanceGroup<T>;
2180 ethBalanceGroup: EthereumBalanceGroup<T>;
2181
2182 constructor(helper: T) {
2183 super(helper);
2184 this.subBalanceGroup = new SubstrateBalanceGroup(helper);
2185 this.ethBalanceGroup = new EthereumBalanceGroup(helper);
2186 }
2187
2059 getCollectionCreationPrice(): bigint {2188 getCollectionCreationPrice(): bigint {
2060 return 2n * this.helper.balance.getOneTokenNominal();2189 return 2n * this.getOneTokenNominal();
2061 }2190 }
2062 /**2191 /**
2063 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).2192 * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ).
2076 * @returns amount of tokens on address2205 * @returns amount of tokens on address
2077 */2206 */
2078 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {2207 async getSubstrate(address: TSubstrateAccount): Promise<bigint> {
2079 return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();2208 return this.subBalanceGroup.getSubstrate(address);
2080 }2209 }
20812210
2082 /**2211 /**
2085 * @returns2214 * @returns
2086 */2215 */
2087 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {2216 async getSubstrateFull(address: TSubstrateAccount): Promise<ISubstrateBalance> {
2088 const accountInfo = (await this.helper.callRpc('api.query.system.account', [address])).data;2217 return this.subBalanceGroup.getSubstrateFull(address);
2089 return {free: accountInfo.free.toBigInt(), miscFrozen: accountInfo.miscFrozen.toBigInt(), feeFrozen: accountInfo.feeFrozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
2090 }2218 }
20912219
2092 /**2220 /**
2096 * @returns amount of tokens on address2224 * @returns amount of tokens on address
2097 */2225 */
2098 async getEthereum(address: TEthereumAccount): Promise<bigint> {2226 async getEthereum(address: TEthereumAccount): Promise<bigint> {
2099 return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();2227 return this.ethBalanceGroup.getEthereum(address);
2100 }2228 }
21012229
2102 /**2230 /**
2108 * @returns ```true``` if extrinsic success, otherwise ```false```2236 * @returns ```true``` if extrinsic success, otherwise ```false```
2109 */2237 */
2110 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {2238 async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {
2111 const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true/*, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`*/);2239 return this.subBalanceGroup.transferToSubstrate(signer, address, amount);
2112
2113 let transfer = {from: null, to: null, amount: 0n} as any;
2114 result.result.events.forEach(({event: {data, method, section}}) => {
2115 if ((section === 'balances') && (method === 'Transfer')) {
2116 transfer = {
2117 from: this.helper.address.normalizeSubstrate(data[0]),
2118 to: this.helper.address.normalizeSubstrate(data[1]),
2119 amount: BigInt(data[2]),
2120 };
2121 }
2122 });
2123 const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from
2124 && this.helper.address.normalizeSubstrate(address) === transfer.to
2125 && BigInt(amount) === transfer.amount;
2126 return isSuccess;
2127 }2240 }
2128}2241}
21292242
2130
2131class AddressGroup extends HelperGroup {2243class AddressGroup extends HelperGroup<ChainHelperBase> {
2132 /**2244 /**
2133 * Normalizes the address to the specified ss58 format, by default ```42```.2245 * Normalizes the address to the specified ss58 format, by default ```42```.
2134 * @param address substrate address2246 * @param address substrate address
2172 }2284 }
2173}2285}
21742286
2175class StakingGroup extends HelperGroup {2287class StakingGroup extends HelperGroup<UniqueHelper> {
2176 /**2288 /**
2177 * Stake tokens for App Promotion2289 * Stake tokens for App Promotion
2178 * @param signer keyring of signer2290 * @param signer keyring of signer
2258 }2370 }
2259}2371}
22602372
2261class SchedulerGroup extends HelperGroup {2373class SchedulerGroup extends HelperGroup<UniqueHelper> {
2262 constructor(helper: UniqueHelper) {2374 constructor(helper: UniqueHelper) {
2263 super(helper);2375 super(helper);
2264 }2376 }
2314 }2426 }
2315}2427}
23162428
2317class ForeignAssetsGroup extends HelperGroup {2429class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
2318 constructor(helper: UniqueHelper) {
2319 super(helper);
2320 }
2321
2322 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2430 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
2323 await this.helper.executeExtrinsic(2431 await this.helper.executeExtrinsic(
2338 }2446 }
2339}2447}
23402448
2449class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2450 palletName: string;
2451
2452 constructor(helper: T, palletName: string) {
2453 super(helper);
2454
2455 this.palletName = palletName;
2456 }
2457
2458 async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: number) {
2459 await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, {Limited: weightLimit}], true);
2460 }
2461}
2462
2463class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2464 async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: number) {
2465 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);
2466 }
2467
2468 async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: number) {
2469 await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);
2470 }
2471}
2472
2473class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
2474 async accounts(address: string, currencyId: any) {
2475 const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;
2476 return BigInt(free);
2477 }
2478}
2479
2480class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {
2481 async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {
2482 await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);
2483 }
2484}
2485
2486class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {
2487 makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {
2488 const apiPrefix = 'api.tx.assetManager.';
2489
2490 const registerTx = this.helper.constructApiCall(
2491 apiPrefix + 'registerForeignAsset',
2492 [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],
2493 );
2494
2495 const setUnitsTx = this.helper.constructApiCall(
2496 apiPrefix + 'setAssetUnitsPerSecond',
2497 [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],
2498 );
2499
2500 const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);
2501 const encodedProposal = batchCall?.method.toHex() || '';
2502 return encodedProposal;
2503 }
2504
2505 async assetTypeId(location: any) {
2506 return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);
2507 }
2508}
2509
2510class MoonbeamAssetsGroup extends HelperGroup<MoonbeamHelper> {
2511 async account(assetId: string, address: string) {
2512 const accountAsset = (
2513 await this.helper.callRpc('api.query.assets.account', [assetId, address])
2514 ).toJSON()! as any;
2515
2516 if (accountAsset !== null) {
2517 return BigInt(accountAsset['balance']);
2518 } else {
2519 return null;
2520 }
2521 }
2522}
2523
2524class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {
2525 async notePreimage(signer: TSigner, encodedProposal: string) {
2526 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.notePreimage', [encodedProposal], true);
2527 }
2528
2529 externalProposeMajority(proposalHash: string) {
2530 return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposalHash]);
2531 }
2532
2533 fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {
2534 return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
2535 }
2536
2537 async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
2538 await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
2539 }
2540}
2541
2542class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
2543 collective: string;
2544
2545 constructor(helper: MoonbeamHelper, collective: string) {
2546 super(helper);
2547
2548 this.collective = collective;
2549 }
2550
2551 async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {
2552 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);
2553 }
2554
2555 async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
2556 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);
2557 }
2558
2559 async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: number, lengthBound: number) {
2560 await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);
2561 }
2562
2563 async proposalCount() {
2564 return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));
2565 }
2566}
2567
2568export type ChainHelperBaseConstructor = new(...args: any[]) => ChainHelperBase;
2341export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;2569export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper;
23422570
2343export class UniqueHelper extends ChainHelperBase {2571export class UniqueHelper extends ChainHelperBase {
2344 helperBase: any;2572 balance: BalanceGroup<UniqueHelper>;
2345
2346 chain: ChainGroup;
2347 balance: BalanceGroup;
2348 address: AddressGroup;
2349 collection: CollectionGroup;2573 collection: CollectionGroup;
2350 nft: NFTGroup;2574 nft: NFTGroup;
2351 rft: RFTGroup;2575 rft: RFTGroup;
2352 ft: FTGroup;2576 ft: FTGroup;
2353 staking: StakingGroup;2577 staking: StakingGroup;
2354 scheduler: SchedulerGroup;2578 scheduler: SchedulerGroup;
2355 foreignAssets: ForeignAssetsGroup;2579 foreignAssets: ForeignAssetsGroup;
2580 xcm: XcmGroup<UniqueHelper>;
2581 xTokens: XTokensGroup<UniqueHelper>;
2582 tokens: TokensGroup<UniqueHelper>;
23562583
2357 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {2584 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2358 super(logger);2585 super(logger, options.helperBase ?? UniqueHelper);
23592586
2360 this.helperBase = options.helperBase ?? UniqueHelper;
2361
2362 this.chain = new ChainGroup(this);
2363 this.balance = new BalanceGroup(this);2587 this.balance = new BalanceGroup(this);
2364 this.address = new AddressGroup(this);2588 this.address = new AddressGroup(this);
2365 this.collection = new CollectionGroup(this);2589 this.collection = new CollectionGroup(this);
2369 this.staking = new StakingGroup(this);2593 this.staking = new StakingGroup(this);
2370 this.scheduler = new SchedulerGroup(this);2594 this.scheduler = new SchedulerGroup(this);
2371 this.foreignAssets = new ForeignAssetsGroup(this);2595 this.foreignAssets = new ForeignAssetsGroup(this);
2596 this.xcm = new XcmGroup(this, 'polkadotXcm');
2597 this.xTokens = new XTokensGroup(this);
2598 this.tokens = new TokensGroup(this);
2372 }2599 }
23732600
2374 clone(helperCls: UniqueHelperConstructor, options: {[key: string]: any} = {}) {2601 getSudo<T extends UniqueHelper>() {
2602 // eslint-disable-next-line @typescript-eslint/naming-convention
2375 Object.setPrototypeOf(helperCls.prototype, this);2603 const SudoHelperType = SudoHelper(this.helperBase);
2376 const newHelper = new helperCls(this.logger, options);2604 return this.clone(SudoHelperType) as T;
2605 }
2606}
23772607
2378 newHelper.api = this.api;2608export class XcmChainHelper extends ChainHelperBase {
2609 async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
2610 const wsProvider = new WsProvider(wsEndpoint);
2611 this.api = new ApiPromise({
2612 provider: wsProvider,
2613 });
2379 newHelper.network = this.network;2614 await this.api.isReadyOrError;
2380 newHelper.forceNetwork = this.forceNetwork;2615 this.network = await UniqueHelper.detectNetwork(this.api);
2616 }
2617}
23812618
2382 this.children.push(newHelper);2619export class RelayHelper extends XcmChainHelper {
2620 xcm: XcmGroup<RelayHelper>;
23832621
2384 return newHelper;2622 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2623 super(logger, options.helperBase ?? RelayHelper);
2624
2625 this.xcm = new XcmGroup(this, 'xcmPallet');
2385 }2626 }
2627}
23862628
2387 getSudo<T extends UniqueHelper>() {2629export class MoonbeamHelper extends XcmChainHelper {
2630 balance: EthereumBalanceGroup<MoonbeamHelper>;
2631 assetManager: MoonbeamAssetManagerGroup;
2632 assets: MoonbeamAssetsGroup;
2633 xTokens: XTokensGroup<MoonbeamHelper>;
2634 democracy: MoonbeamDemocracyGroup;
2635 collective: {
2636 council: MoonbeamCollectiveGroup,
2637 techCommittee: MoonbeamCollectiveGroup,
2638 };
2639
2640 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2641 super(logger, options.helperBase ?? MoonbeamHelper);
2642
2643 this.balance = new EthereumBalanceGroup(this);
2644 this.assetManager = new MoonbeamAssetManagerGroup(this);
2645 this.assets = new MoonbeamAssetsGroup(this);
2646 this.xTokens = new XTokensGroup(this);
2647 this.democracy = new MoonbeamDemocracyGroup(this);
2648 this.collective = {
2649 council: new MoonbeamCollectiveGroup(this, 'councilCollective'),
2650 techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),
2651 };
2652 }
2653}
2654
2655export class AcalaHelper extends XcmChainHelper {
2656 balance: SubstrateBalanceGroup<AcalaHelper>;
2657 assetRegistry: AcalaAssetRegistryGroup;
2658 xTokens: XTokensGroup<AcalaHelper>;
2659 tokens: TokensGroup<AcalaHelper>;
2660
2661 constructor(logger?: ILogger, options: {[key: string]: any} = {}) {
2662 super(logger, options.helperBase ?? AcalaHelper);
2663
2664 this.balance = new SubstrateBalanceGroup(this);
2665 this.assetRegistry = new AcalaAssetRegistryGroup(this);
2666 this.xTokens = new XTokensGroup(this);
2667 this.tokens = new TokensGroup(this);
2668 }
2669
2670 getSudo<T extends AcalaHelper>() {
2388 // eslint-disable-next-line @typescript-eslint/naming-convention2671 // eslint-disable-next-line @typescript-eslint/naming-convention
2389 const SudoHelperType = SudoUniqueHelper(this.helperBase);2672 const SudoHelperType = SudoHelper(this.helperBase);
2390 return this.clone(SudoHelperType) as T;2673 return this.clone(SudoHelperType) as T;
2391 }2674 }
2392}2675}
2437}2720}
24382721
2439// eslint-disable-next-line @typescript-eslint/naming-convention2722// eslint-disable-next-line @typescript-eslint/naming-convention
2440function SudoUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {2723function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {
2441 return class extends Base {2724 return class extends Base {
2442 constructor(...args: any[]) {2725 constructor(...args: any[]) {
2443 super(...args);2726 super(...args);