UNPKG

@neo-one/client-core-esnext-esm

Version:

NEO•ONE client core types implementation.

359 lines (357 loc) 17.6 kB
import { AsyncIterableX } from '@reactivex/ix-esnext-esm/asynciterable/asynciterablex'; import { flatMap } from '@reactivex/ix-esnext-esm/asynciterable/pipe/flatmap'; import { toObservable } from '@reactivex/ix-esnext-esm/asynciterable/toobservable'; import _ from 'lodash'; import { BehaviorSubject, combineLatest, Observable, ReplaySubject } from 'rxjs'; import { distinctUntilChanged, filter, map, multicast, refCount, switchMap, take } from 'rxjs/operators'; import { AsyncParallelHook } from 'tapable'; import * as args from './args'; import { DeleteUserAccountUnsupportedError, UnknownAccountError, UnknownNetworkError, UpdateUserAccountUnsupportedError, } from './errors'; import { createSmartContract } from './sc'; export class Client { constructor(providersIn) { this.reset$ = new BehaviorSubject(undefined); this.hooks = { beforeRelay: new AsyncParallelHook(['beforeRelay']), relayError: new AsyncParallelHook(['error']), afterRelay: new AsyncParallelHook(['transaction']), beforeConfirmed: new AsyncParallelHook(['transaction']), confirmedError: new AsyncParallelHook(['transaction', 'error']), afterConfirmed: new AsyncParallelHook(['transaction', 'receipt']), afterCall: new AsyncParallelHook(['receipt']), callError: new AsyncParallelHook(['error']), }; const providersArray = Object.values(providersIn); const providerIn = providersArray.find((provider) => provider.getCurrentUserAccount() !== undefined) || providersArray[0]; if (providerIn === undefined) { throw new Error('At least one provider is required'); } this.providers$ = new BehaviorSubject(providersIn); this.selectedProvider$ = new BehaviorSubject(providerIn); this.currentUserAccount$ = this.selectedProvider$.pipe(switchMap((provider) => provider.currentUserAccount$)); this.userAccounts$ = this.providers$.pipe(switchMap((providers) => combineLatest(Object.values(providers).map((provider) => provider.userAccounts$))), map((accountss) => accountss.reduce((acc, accounts) => acc.concat(accounts), []))); this.networks$ = this.providers$.pipe(switchMap((providers) => combineLatest(Object.values(providers).map((provider) => provider.networks$))), map((networkss) => [...new Set(networkss.reduce((acc, networks) => acc.concat(networks), []))])); this.currentNetworkInternal$ = new BehaviorSubject(providerIn.getNetworks()[0]); combineLatest([this.currentUserAccount$, this.selectedProvider$]) .pipe(map(([currentAccount, provider]) => { if (currentAccount !== undefined) { return currentAccount.id.network; } const mainNetwork = provider.getNetworks().find((network) => network === 'main'); return mainNetwork === undefined ? provider.getNetworks()[0] : mainNetwork; })) .subscribe(this.currentNetworkInternal$); this.currentNetwork$ = this.currentNetworkInternal$.pipe(distinctUntilChanged()); if (this.getCurrentUserAccount() === undefined) { this.userAccounts$ .pipe(filter((accounts) => accounts.length > 0), take(1)) .toPromise() .then(async (accounts) => { const account = accounts[0]; if (this.getCurrentUserAccount() === undefined && account !== undefined) { await this.selectUserAccount(account.id); } }) .catch(() => { }); } this.block$ = this.reset$.pipe(switchMap(() => this.currentNetwork$.pipe(switchMap((network) => new Observable((observer) => toObservable(this.getNetworkProvider(network).iterBlocks(network)).subscribe(observer)).pipe(map((block) => ({ block, network })))))), multicast(() => new ReplaySubject(1)), refCount()); this.accountState$ = combineLatest([this.currentUserAccount$, this.block$]).pipe(switchMap(async ([currentUserAccount]) => { if (currentUserAccount === undefined) { return undefined; } const account = await this.getNetworkProvider(currentUserAccount.id.network).getAccount(currentUserAccount.id.network, currentUserAccount.id.address); return { currentUserAccount, account }; }), distinctUntilChanged((a, b) => _.isEqual(a, b)), multicast(() => new ReplaySubject(1)), refCount()); } get providers() { return this.providers$.getValue(); } getUserAccount(idIn) { const id = args.assertUserAccountID('id', idIn); const provider = this.getProvider({ from: id }); const account = provider .getUserAccounts() .find((acct) => acct.id.network === id.network && acct.id.address === id.address); if (account === undefined) { throw new UnknownAccountError(id.address); } return account; } async selectUserAccount(idIn) { const id = args.assertNullableUserAccountID('id', idIn); const provider = this.getProvider({ from: id }); await provider.selectUserAccount(id); this.selectedProvider$.next(provider); } async selectNetwork(network) { args.assertString('network', network); const provider = this.getNetworkProvider(network); const account = provider.getCurrentUserAccount(); if (account === undefined) { const accounts = provider.getUserAccounts(); if (accounts.length > 0) { await provider.selectUserAccount(accounts[0].id); } } this.selectedProvider$.next(provider); } async getSupportedFeatures(idIn) { const id = args.assertUserAccountID('id', idIn); const provider = this.getProvider({ from: id }); return { delete: provider.deleteUserAccount !== undefined, updateName: provider.updateUserAccountName !== undefined, }; } async deleteUserAccount(idIn) { const id = args.assertUserAccountID('id', idIn); const provider = this.getProvider({ from: id }); if (provider.deleteUserAccount === undefined) { throw new DeleteUserAccountUnsupportedError(id); } await provider.deleteUserAccount(id); } async updateUserAccountName(options) { const { id, name } = args.assertUpdateAccountNameOptions('options', options); const provider = this.getProvider({ from: id }); if (provider.updateUserAccountName === undefined) { throw new UpdateUserAccountUnsupportedError(id); } await provider.updateUserAccountName({ id, name }); } getCurrentUserAccount() { return this.selectedProvider$.getValue().getCurrentUserAccount(); } getCurrentNetwork() { return this.currentNetworkInternal$.getValue(); } getUserAccounts() { return Object.values(this.providers).reduce((acc, provider) => acc.concat(provider.getUserAccounts()), []); } getNetworks() { const providers = Object.values(this.providers); return [...new Set(providers.reduce((acc, provider) => acc.concat(provider.getNetworks()), []))]; } smartContract(definition) { return createSmartContract({ definition: args.assertSmartContractDefinition('definition', definition), client: this, }); } async transfer(...argsIn) { const { transfers, options } = this.getTransfersOptions(argsIn); await this.applyBeforeRelayHook(options); return this.addTransactionHooks(this.getProvider(options).transfer(transfers, options)); } async claim(optionsIn) { const options = args.assertTransactionOptions('options', optionsIn); await this.applyBeforeRelayHook(options); return this.addTransactionHooks(this.getProvider(options).claim(options)); } async getAccount(idIn) { const id = args.assertUserAccountID('id', idIn); return this.getNetworkProvider(id.network).getAccount(id.network, id.address); } __iterActionsRaw(network, optionsIn) { args.assertString('network', network); const options = args.assertNullableIterOptions('iterOptions', optionsIn); const provider = this.getNetworkProvider(network); if (provider.iterActionsRaw !== undefined) { return provider.iterActionsRaw(network, options); } return AsyncIterableX.from(provider.iterBlocks(network, options)).pipe(flatMap(async (block) => { const actions = _.flatten(block.transactions.map((transaction) => { if (transaction.type === 'InvocationTransaction') { return [...transaction.invocationData.actions]; } return []; })); return AsyncIterableX.of(...actions); })); } async __invoke(contract, method, params, paramsZipped, verify, optionsIn, sourceMaps = {}) { args.assertAddress('contract', contract); args.assertString('method', method); args.assertArray('params', params).forEach((param) => args.assertNullableScriptBuilderParam('params.param', param)); paramsZipped.forEach(([tupleString, tupleParam]) => [ args.assertString('tupleString', tupleString), args.assertNullableParam('tupleParam', tupleParam), ]); args.assertArray('paramsZipped', paramsZipped); args.assertBoolean('verify', verify); const options = args.assertInvokeSendUnsafeReceiveTransactionOptions('options', optionsIn); args.assertSourceMaps('sourceMaps', sourceMaps); await this.applyBeforeRelayHook(options); return this.addTransactionHooks(this.getProvider(options).invoke(contract, method, params, paramsZipped, verify, options, sourceMaps)); } async __invokeSend(contract, method, params, paramsZipped, transfer, optionsIn, sourceMaps = {}) { args.assertAddress('contract', contract); args.assertString('method', method); args.assertArray('params', params).forEach((param) => args.assertNullableScriptBuilderParam('params.param', param)); paramsZipped.forEach(([tupleString, tupleParam]) => [ args.assertString('tupleString', tupleString), args.assertNullableParam('tupleParam', tupleParam), ]); const options = args.assertTransactionOptions('options', optionsIn); args.assertTransfer('transfer', transfer); args.assertSourceMaps('sourceMaps', sourceMaps); await this.applyBeforeRelayHook(options); return this.addTransactionHooks(this.getProvider(options).invokeSend(contract, method, params, paramsZipped, transfer, options, sourceMaps)); } async __invokeCompleteSend(contract, method, params, paramsZipped, hash, optionsIn, sourceMaps = {}) { args.assertAddress('contract', contract); args.assertString('method', method); args.assertArray('params', params).forEach((param) => args.assertNullableScriptBuilderParam('params.param', param)); paramsZipped.forEach(([tupleString, tupleParam]) => [ args.assertString('tupleString', tupleString), args.assertNullableParam('tupleParam', tupleParam), ]); args.assertHash256('hash', hash); const options = args.assertTransactionOptions('options', optionsIn); args.assertSourceMaps('sourceMaps', sourceMaps); await this.applyBeforeRelayHook(options); return this.addTransactionHooks(this.getProvider(options).invokeCompleteSend(contract, method, params, paramsZipped, hash, options, sourceMaps)); } async __invokeRefundAssets(contract, method, params, paramsZipped, hash, optionsIn, sourceMaps = {}) { args.assertAddress('contract', contract); args.assertString('method', method); args.assertArray('params', params).forEach((param) => args.assertNullableScriptBuilderParam('params.param', param)); paramsZipped.forEach(([tupleString, tupleParam]) => [ args.assertString('tupleString', tupleString), args.assertNullableParam('tupleParam', tupleParam), ]); args.assertHash256('hash', hash); const options = args.assertTransactionOptions('options', optionsIn); args.assertSourceMaps('sourceMaps', sourceMaps); await this.applyBeforeRelayHook(options); return this.addTransactionHooks(this.getProvider(options).invokeRefundAssets(contract, method, params, paramsZipped, hash, options, sourceMaps)); } async __invokeClaim(contract, method, params, paramsZipped, optionsIn, sourceMaps = {}) { args.assertAddress('contract', contract); args.assertString('method', method); args.assertArray('params', params).forEach((param) => args.assertNullableScriptBuilderParam('params.param', param)); paramsZipped.forEach(([tupleString, tupleParam]) => [ args.assertString('tupleString', tupleString), args.assertNullableParam('tupleParam', tupleParam), ]); const options = args.assertTransactionOptions('options', optionsIn); args.assertSourceMaps('sourceMaps', sourceMaps); await this.applyBeforeRelayHook(options); return this.addTransactionHooks(this.getProvider(options).invokeClaim(contract, method, params, paramsZipped, options, sourceMaps)); } async __call(network, contract, method, params) { try { args.assertString('network', network); args.assertAddress('contract', contract); args.assertString('method', method); args .assertArray('params', params) .forEach((param) => args.assertNullableScriptBuilderParam('params.param', param)); const receipt = await this.getNetworkProvider(network).call(network, contract, method, params); await this.hooks.afterCall.promise(receipt); return receipt; } catch (error) { await this.hooks.callError.promise(error); throw error; } } reset() { this.reset$.next(undefined); } getProvider(options = {}) { const { from } = args.assertTransactionOptions('options', options); if (from === undefined) { return this.selectedProvider$.getValue(); } const providers = Object.values(this.providers); const accountProvider = providers.find((provider) => provider .getUserAccounts() .some((account) => account.id.network === from.network && account.id.address === from.address)); if (accountProvider === undefined) { throw new UnknownAccountError(from.address); } return accountProvider; } getNetworkProvider(network) { args.assertString('network', network); const providers = Object.values(this.providers); const accountProvider = providers.find((provider) => provider.getNetworks().some((providerNetwork) => providerNetwork === network)); if (accountProvider === undefined) { throw new UnknownNetworkError(network); } return accountProvider; } async applyBeforeRelayHook(options) { try { await this.hooks.beforeRelay.promise(options); } catch { } } async addTransactionHooks(res) { return res .then(async (result) => { try { await this.hooks.afterRelay.promise(result.transaction); } catch { } return Object.assign({}, result, { confirmed: async (options) => { try { await this.hooks.beforeConfirmed.promise(result.transaction); } catch { } try { const receipt = await result.confirmed(options); try { await this.hooks.afterConfirmed.promise(result.transaction, receipt); } catch { } return receipt; } catch (error) { try { await this.hooks.confirmedError.promise(result.transaction, error); } catch { } throw error; } }, }); }) .catch(async (error) => { await this.hooks.relayError.promise(error); throw error; }); } getTransfersOptions(argsIn) { let transfers; let options; if (argsIn.length >= 3) { transfers = [ { amount: argsIn[0], asset: argsIn[1], to: argsIn[2], }, ]; options = argsIn[3]; } else { transfers = argsIn[0]; options = argsIn[1]; } return { transfers: args.assertTransfers('transfers', transfers), options: args.assertTransactionOptions('options', options), }; } } //# sourceMappingURL=Client.js.map