@neo-one/client-core-esnext-esm
Version:
NEO•ONE client core types implementation.
359 lines (357 loc) • 17.6 kB
JavaScript
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