123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- import { types } from '@joystream/types/'
- import { ApiPromise, WsProvider, SubmittableResult } from '@polkadot/api'
- import { SubmittableExtrinsic, AugmentedEvent } from '@polkadot/api/types'
- import { KeyringPair } from '@polkadot/keyring/types'
- import { Balance } from '@polkadot/types/interfaces'
- import { formatBalance } from '@polkadot/util'
- import { IEvent } from '@polkadot/types/types'
- import { DispatchError } from '@polkadot/types/interfaces/system'
- import { LoggingService } from '../../logging'
- import { Logger } from 'winston'
- export class ExtrinsicFailedError extends Error {}
- export class RuntimeApi {
- private _api: ApiPromise
- private logger: Logger
- public isDevelopment = false
- private constructor(logging: LoggingService, originalApi: ApiPromise, isDevelopment: boolean) {
- this.isDevelopment = isDevelopment
- this.logger = logging.createLogger('SubstrateApi')
- this._api = originalApi
- }
- static async create(
- logging: LoggingService,
- apiUri: string,
- metadataCache?: Record<string, any>
- ): Promise<RuntimeApi> {
- const { api, chainType } = await RuntimeApi.initApi(apiUri, metadataCache)
- return new RuntimeApi(logging, api, chainType.isDevelopment || chainType.isLocal)
- }
- private static async initApi(apiUri: string, metadataCache?: Record<string, any>) {
- const wsProvider: WsProvider = new WsProvider(apiUri)
- const api = await ApiPromise.create({ provider: wsProvider, types, metadata: metadataCache })
- // Initializing some api params based on pioneer/packages/react-api/Api.tsx
- const [properties, chainType] = await Promise.all([api.rpc.system.properties(), api.rpc.system.chainType()])
- const tokenSymbol = properties.tokenSymbol.unwrap()[0].toString()
- const tokenDecimals = properties.tokenDecimals.unwrap()[0].toNumber()
- // formatBlanace config
- formatBalance.setDefaults({
- decimals: tokenDecimals,
- unit: tokenSymbol,
- })
- return { api, properties, chainType }
- }
- public get query(): ApiPromise['query'] {
- return this._api.query
- }
- public get tx(): ApiPromise['tx'] {
- return this._api.tx
- }
- public get consts(): ApiPromise['consts'] {
- return this._api.consts
- }
- public get derive(): ApiPromise['derive'] {
- return this._api.derive
- }
- public get createType(): ApiPromise['createType'] {
- return this._api.createType.bind(this._api)
- }
- public sudo(tx: SubmittableExtrinsic<'promise'>): SubmittableExtrinsic<'promise'> {
- return this._api.tx.sudo.sudo(tx)
- }
- public async estimateFee(account: KeyringPair, tx: SubmittableExtrinsic<'promise'>): Promise<Balance> {
- const paymentInfo = await tx.paymentInfo(account)
- return paymentInfo.partialFee
- }
- public findEvent<
- S extends keyof ApiPromise['events'] & string,
- M extends keyof ApiPromise['events'][S] & string,
- EventType = ApiPromise['events'][S][M] extends AugmentedEvent<'promise', infer T> ? IEvent<T> : never
- >(result: SubmittableResult, section: S, method: M): EventType | undefined {
- return result.findRecord(section, method)?.event as EventType | undefined
- }
- public getEvent<
- S extends keyof ApiPromise['events'] & string,
- M extends keyof ApiPromise['events'][S] & string,
- EventType = ApiPromise['events'][S][M] extends AugmentedEvent<'promise', infer T> ? IEvent<T> : never
- >(result: SubmittableResult, section: S, method: M): EventType {
- const event = this.findEvent(result, section, method)
- if (!event) {
- throw new Error(`Cannot find expected ${section}.${method} event in result: ${result.toHuman()}`)
- }
- return (event as unknown) as EventType
- }
- sendExtrinsic(keyPair: KeyringPair, tx: SubmittableExtrinsic<'promise'>): Promise<SubmittableResult> {
- this.logger.info(`Sending ${tx.method.section}.${tx.method.method} extrinsic from ${keyPair.address}`)
- return new Promise((resolve, reject) => {
- let unsubscribe: () => void
- tx.signAndSend(keyPair, {}, (result) => {
- // Implementation loosely based on /pioneer/packages/react-signer/src/Modal.tsx
- if (!result || !result.status) {
- return
- }
- if (result.status.isInBlock) {
- unsubscribe()
- result.events
- .filter(({ event }) => event.section === 'system')
- .forEach(({ event }) => {
- if (event.method === 'ExtrinsicFailed') {
- const dispatchError = event.data[0] as DispatchError
- let errorMsg = dispatchError.toString()
- if (dispatchError.isModule) {
- try {
- const { name, documentation } = this._api.registry.findMetaError(dispatchError.asModule)
- errorMsg = `${name} (${documentation})`
- } catch (e) {
- // This probably means we don't have this error in the metadata
- // In this case - continue (we'll just display dispatchError.toString())
- }
- }
- reject(new ExtrinsicFailedError(`Extrinsic execution error: ${errorMsg}`))
- } else if (event.method === 'ExtrinsicSuccess') {
- resolve(result)
- }
- })
- } else if (result.isError) {
- reject(new ExtrinsicFailedError('Extrinsic execution error!'))
- }
- })
- .then((unsubFunc) => (unsubscribe = unsubFunc))
- .catch((e) =>
- reject(new ExtrinsicFailedError(`Cannot send the extrinsic: ${e.message ? e.message : JSON.stringify(e)}`))
- )
- })
- }
- }
|