
ACCOUNT
-
Address: {{walletService.account.address}}
+
Address: {{walletService.accountAddress}}
Balance (GO): {{walletService.accountBalance | number}}
@@ -18,16 +19,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -62,16 +53,19 @@
Transaction Hash:
Block Hash:
Contract Address:
Gas Used:
diff --git a/front/src/app/scenes/wallet-account/wallet-account.component.ts b/front/src/app/scenes/wallet-account/wallet-account.component.ts
index d15d1343..63a8247a 100644
--- a/front/src/app/scenes/wallet-account/wallet-account.component.ts
+++ b/front/src/app/scenes/wallet-account/wallet-account.component.ts
@@ -1,5 +1,8 @@
/*CORE*/
import {Component, OnDestroy, OnInit} from '@angular/core';
+import {Router} from '@angular/router';
+import {Subscription} from 'rxjs';
+import {flatMap} from 'rxjs/operators';
/*SERVICES*/
import {WalletService} from '../../services/wallet.service';
import {MetaService} from '../../services/meta.service';
@@ -8,8 +11,7 @@ import {CommonService} from '../../services/common.service';
import {Address} from '../../models/address.model';
/*UTILS*/
import {META_TITLES} from '../../utils/constants';
-import {AutoUnsubscribe} from "../../decorators/auto-unsubscribe";
-import {Subscription} from "rxjs";
+import {AutoUnsubscribe} from '../../decorators/auto-unsubscribe';
@Component({
selector: 'app-wallet-account',
@@ -18,26 +20,34 @@ import {Subscription} from "rxjs";
})
@AutoUnsubscribe('_subsArr$')
export class WalletAccountComponent implements OnInit, OnDestroy {
- addr: Address;
+ accountAddr: Address;
private _subsArr$: Subscription[] = [];
constructor(
public walletService: WalletService,
private _metaService: MetaService,
private _commonService: CommonService,
+ private _router: Router,
) {
}
ngOnInit(): void {
this._metaService.setTitle(META_TITLES.WALLET.title);
- this._subsArr$.push(
- this._commonService.getAddress(this.walletService.account.address).subscribe((addr => {
- this.addr = addr;
- }))
- );
+ this._subsArr$.push(this.walletService.logged$.pipe(
+ flatMap(() => this._commonService.getAddress(this.walletService.accountAddress)),
+ ).subscribe((addr: Address) => {
+ this.accountAddr = addr;
+ }));
}
+ closeWallet(): void {
+ // wallet service close account will be called in ngOnDestroy
+ this._router.navigate(['wallet']);
+ }
+
+
ngOnDestroy(): void {
this.walletService.resetProcessing();
+ this.walletService.closeAccount();
}
}
diff --git a/front/src/app/scenes/wallet-create/wallet-create.component.html b/front/src/app/scenes/wallet-create/wallet-create.component.html
index 78adacb3..a488105f 100644
--- a/front/src/app/scenes/wallet-create/wallet-create.component.html
+++ b/front/src/app/scenes/wallet-create/wallet-create.component.html
@@ -4,7 +4,7 @@

Create Account
-
NOTE: Once you leave this page, you cannot recover the address or private key.
+
NOTE: Once you leaonCopyve this page, you cannot recover the address or private key.
Please copy this somewhere safe!
diff --git a/front/src/app/scenes/wallet-create/wallet-create.component.ts b/front/src/app/scenes/wallet-create/wallet-create.component.ts
index 9c73c0ea..bccaec73 100644
--- a/front/src/app/scenes/wallet-create/wallet-create.component.ts
+++ b/front/src/app/scenes/wallet-create/wallet-create.component.ts
@@ -31,7 +31,7 @@ export class WalletCreateComponent implements OnInit {
private _toastrService: ToastrService,
private _clipboardService: ClipboardService,
) {
- this._clipboardService.configure({ cleanUpAfterCopy: true });
+ this._clipboardService.configure({cleanUpAfterCopy: true});
}
ngOnInit(): void {
@@ -42,10 +42,8 @@ export class WalletCreateComponent implements OnInit {
}
useWallet(): void {
- this._walletService.openAccount(this.account.privateKey).subscribe((ok: boolean) => {
- if (ok) {
- this._router.navigate(['/wallet/account']);
- }
+ this._walletService.openAccount(this.account.privateKey).subscribe(() => {
+ this._router.navigate(['/wallet/account']);
});
}
diff --git a/front/src/app/scenes/wallet-main/wallet-main.component.html b/front/src/app/scenes/wallet-main/wallet-main.component.html
index a9429ee8..10394589 100644
--- a/front/src/app/scenes/wallet-main/wallet-main.component.html
+++ b/front/src/app/scenes/wallet-main/wallet-main.component.html
@@ -1,20 +1,23 @@
-
-
-
-
-
- Please enter your private key to proceed
-
+
+
+
+
+
+

+ Wallet
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
diff --git a/front/src/app/scenes/wallet-main/wallet-main.component.ts b/front/src/app/scenes/wallet-main/wallet-main.component.ts
index 1e6c371e..1dea2ab3 100644
--- a/front/src/app/scenes/wallet-main/wallet-main.component.ts
+++ b/front/src/app/scenes/wallet-main/wallet-main.component.ts
@@ -10,6 +10,8 @@ import {WalletService} from '../../services/wallet.service';
import {PasswordField} from '../../models/password-field.model';
/*UTILS*/
import {META_TITLES} from '../../utils/constants';
+import {LayoutService} from '../../services/layout.service';
+import {filter, flatMap} from 'rxjs/operators';
@Component({
selector: 'app-wallet-main',
@@ -42,19 +44,42 @@ export class WalletMainComponent implements OnInit {
private _fb: FormBuilder,
private _toastrService: ToastrService,
private _router: Router,
+ private _layoutService: LayoutService,
) {
}
ngOnInit() {
+ /*this._layoutService.onLoading();*/
this._metaService.setTitle(META_TITLES.WALLET.title);
+ /*this.walletService.metamaskConfigured$.pipe(
+ filter((v: boolean) => {
+ if (!v) {
+ this._layoutService.offLoading();
+ }
+ return v;
+ }),
+ flatMap(() => this.walletService.openAccount()),
+ ).subscribe(() => {
+ this._layoutService.offLoading();
+ this._router.navigate(['/wallet/account']);
+ }, (err) => {
+ this._toastrService.danger(err);
+ this._layoutService.offLoading();
+ });*/
}
- onPrivateKeySubmit() {
- const privateKey: string = this.privateKeyForm.get('privateKey').value;
- this.walletService.openAccount(privateKey).subscribe((ok: boolean) => {
- if (ok) {
- this._router.navigate(['/wallet/account']);
+ onSubmit(metamask: boolean = false) {
+ let privateKey: string = null;
+ if (!metamask) {
+ privateKey = this.privateKeyForm.get('privateKey').value;
+ if (!privateKey) {
+ this._toastrService.danger('Please enter private key');
+ return;
}
- });
+ }
+ this.walletService.openAccount(privateKey).subscribe(
+ () => this._router.navigate(['/wallet/account']),
+ (err) => this._toastrService.danger(err),
+ );
}
}
diff --git a/front/src/app/services/common.service.ts b/front/src/app/services/common.service.ts
index 3d9d77ee..75850631 100644
--- a/front/src/app/services/common.service.ts
+++ b/front/src/app/services/common.service.ts
@@ -17,11 +17,11 @@ import {Stats} from '../models/stats.model';
import {Contract} from '../models/contract.model';
import {SignerData, SignerStat} from '../models/signer-stats';
import {SignerNode} from '../models/signer-node';
+import {AbiItem} from 'web3-utils';
/*UTILS*/
import {ContractAbi, ContractEventsAbi, ContractAbiByID, AbiItemIDed} from '../utils/types';
import {FunctionName} from '../utils/enums';
import {objIsEmpty} from '../utils/functions';
-import {AbiItem} from 'web3-utils';
@Injectable()
export class CommonService implements Resolve
{
@@ -36,7 +36,7 @@ export class CommonService implements Resolve {
});
}
return this._rpcProvider$.pipe(
- filter(v => !!v),
+ filter(v => !!v),
take(1),
);
}
@@ -47,7 +47,7 @@ export class CommonService implements Resolve {
this.initAbi();
}
return this._abi$.pipe(
- filter(v => v !== null),
+ filter(v => v !== null),
take(1),
);
}
@@ -58,7 +58,7 @@ export class CommonService implements Resolve {
this.initAbi();
}
return this._abiByID$.pipe(
- filter(v => v !== null),
+ filter(v => v !== null),
take(1),
);
}
@@ -72,7 +72,7 @@ export class CommonService implements Resolve {
});
}
return this._eventsAbi$.pipe(
- filter(v => v !== null),
+ filter(v => v !== null),
take(1),
);
}
diff --git a/front/src/app/services/layout.service.ts b/front/src/app/services/layout.service.ts
index a18610a3..f5ac7ded 100644
--- a/front/src/app/services/layout.service.ts
+++ b/front/src/app/services/layout.service.ts
@@ -11,7 +11,7 @@ import {ThemeColor} from '../utils/enums';
providedIn: 'root',
})
export class LayoutService {
- isPageLoading: BehaviorSubject = new BehaviorSubject(false);
+ isPageLoading$: BehaviorSubject = new BehaviorSubject(false);
themeColor$: BehaviorSubject = new BehaviorSubject(ThemeColor.LIGHT);
themeSettings: ThemeSettings;
mobileMenuState: BehaviorSubject = new BehaviorSubject(false);
@@ -30,14 +30,14 @@ export class LayoutService {
}
toggleLoading() {
- this.isPageLoading.next(!this.isPageLoading.value);
+ this.isPageLoading$.next(!this.isPageLoading$.value);
}
onLoading() {
- this.isPageLoading.next(true);
+ this.isPageLoading$.next(true);
}
offLoading() {
- this.isPageLoading.next(false);
+ this.isPageLoading$.next(false);
}
}
diff --git a/front/src/app/services/wallet.service.ts b/front/src/app/services/wallet.service.ts
index 1bd61c9f..8507c43f 100644
--- a/front/src/app/services/wallet.service.ts
+++ b/front/src/app/services/wallet.service.ts
@@ -1,14 +1,15 @@
/*CORE*/
import {Injectable} from '@angular/core';
import {Router} from '@angular/router';
-import {BehaviorSubject, forkJoin, Observable, of} from 'rxjs';
-import {concatMap, filter, map, take, finalize, catchError, mergeMap} from 'rxjs/operators';
+import {BehaviorSubject, forkJoin, Observable, of, throwError} from 'rxjs';
+import {concatMap, filter, map, mergeMap, take, tap} from 'rxjs/operators';
import {fromPromise} from 'rxjs/internal-compatibility';
/*WEB3*/
import Web3 from 'web3';
import {SignedTransaction, Transaction as Web3Tx, TransactionConfig, TransactionReceipt} from 'web3-core';
import {Account} from 'web3-eth-accounts';
-import {AbiItem, fromWei, toWei, isAddress} from 'web3-utils';
+import {Contract as Web3Contract} from 'web3-eth-contract';
+import {AbiItem, fromWei, isAddress, toWei} from 'web3-utils';
/*SERVICES*/
import {ToastrService} from '../modules/toastr/toastr.service';
import {CommonService} from './common.service';
@@ -17,77 +18,184 @@ import {Transaction} from '../models/transaction.model';
/*UTILS*/
import {objIsEmpty} from '../utils/functions';
+interface IWallet {
+ w3: Web3;
+
+ send(tx: TransactionConfig): Observable;
+
+ call(tx: TransactionConfig): Observable;
+
+ logIn(privateKey: string): Observable;
+
+ logOut(): void;
+}
+
+abstract class Wallet {
+ w3: Web3;
+
+ constructor(w3: Web3) {
+ this.w3 = w3;
+ }
+
+ call(tx: TransactionConfig): Observable {
+ return fromPromise(this.w3.eth.call(tx));
+ }
+
+ logOut(): void {
+
+ }
+}
+
+class MetamaskStrategy extends Wallet implements IWallet {
+
+ send(tx: TransactionConfig): Observable {
+ return fromPromise(this.w3.eth.sendTransaction(tx));
+ }
+
+ logIn(): Observable {
+ return fromPromise((window as any).ethereum.enable()).pipe(
+ map((accounts: string[]) => {
+ return accounts[0];
+ }),
+ );
+ }
+}
+
+class PrivateKeyStrategy extends Wallet implements IWallet {
+ private account: Account;
+
+ send(tx: TransactionConfig): Observable {
+ return fromPromise(this.w3.eth.accounts.signTransaction(tx, this.account.privateKey)).pipe(
+ mergeMap((signedTx: SignedTransaction) => fromPromise(this.w3.eth.sendSignedTransaction(signedTx.rawTransaction))),
+ );
+ }
+
+ logIn(privateKey: string): Observable {
+ if (privateKey.length === 64 && privateKey.indexOf('0x') !== 0) {
+ privateKey = '0x' + privateKey;
+ }
+ if (privateKey.length === 66) {
+ let account: Account;
+ try {
+ account = this.w3.eth.accounts.privateKeyToAccount(privateKey);
+ } catch (e) {
+ return throwError(e);
+ }
+ this.account = account;
+ return of(this.account.address);
+ }
+ return throwError('Given private key is not valid');
+ }
+
+ logOut(): void {
+ this.account = null;
+ }
+}
+
@Injectable()
export class WalletService {
isProcessing = false;
// ACCOUNT INFO
- account: Account;
+ accountAddress: string;
accountBalance: string;
receipt: TransactionReceipt;
- private _web3Callable$: BehaviorSubject = new BehaviorSubject(null);
- private _web3Payable$: BehaviorSubject = new BehaviorSubject(null);
+ private _metamaskIntalled$: BehaviorSubject = new BehaviorSubject(false);
+
+ get metamaskIntalled$(): Observable {
+ return this.ready$.pipe(
+ mergeMap(() => this._metamaskIntalled$),
+ filter(v => v !== null),
+ );
+ }
+
+ get metamaskConfigured$(): Observable {
+ return this.ready$.pipe(
+ mergeMap(() => this._metamaskConfigured$),
+ filter(v => v !== null),
+ );
+ }
+
+ private _metamaskConfigured$: BehaviorSubject = new BehaviorSubject(null);
- get w3Call(): Observable {
- return this._web3Callable$.pipe(
- filter(v => !!v),
- take(1),
+ private _logged$: BehaviorSubject = new BehaviorSubject(false);
+
+ get logged$(): Observable {
+ return this.ready$.pipe(
+ mergeMap(() => this._logged$),
);
}
- get w3Pay(): Observable {
- return this._web3Payable$.pipe(
- filter(v => !!v),
- take(1),
+// used for paid
+ private _walletContext: IWallet;
+
+ // used for interaction with chain, only free methods
+ private _w3: Web3;
+ private _metamaskw3: Web3;
+
+ get w3$(): Observable {
+ return this.ready$.pipe(
+ map(() => this._w3),
+ );
+ }
+
+ private _ready$: BehaviorSubject = new BehaviorSubject(false);
+ get ready$(): Observable {
+ return this._ready$.pipe(
+ filter(v => !!v),
+ take(1),
);
}
constructor(
private _toastrService: ToastrService,
private _commonService: CommonService,
- private _router: Router,
) {
- this._commonService.rpcProvider$
- .pipe(
- filter(value => !!value),
- )
- .subscribe((rpcProvider: string) => {
- const metaMaskProvider = new Web3(Web3.givenProvider, null, {transactionConfirmationBlocks: 1,});
- const web3Provider = new Web3(new Web3.providers.HttpProvider(rpcProvider), null, {transactionConfirmationBlocks: 1,});
- this._web3Callable$.next(web3Provider);
- if (!metaMaskProvider.currentProvider) {
- this._web3Payable$.error('Metamask is not enabled');
+ this._commonService.rpcProvider$.subscribe((rpcProvider: string) => {
+ this.initProvider(rpcProvider);
+ });
+ }
+
+ initProvider(rpcProvider: string): void {
+ const metaMaskProvider = new Web3(Web3.givenProvider, null, {transactionConfirmationBlocks: 1});
+ const web3Provider = new Web3(new Web3.providers.HttpProvider(rpcProvider), null, {transactionConfirmationBlocks: 1});
+ this._w3 = web3Provider;
+ if (!metaMaskProvider.currentProvider) {
+ this._metamaskConfigured$.next(false);
+ this._ready$.next(true);
+ return;
+ }
+ web3Provider.eth.net.getId((web3err, web3NetID) => {
+ if (web3err) {
+ this._toastrService.danger('Metamask is enabled but can\'t get Gochain network id');
+ this._metamaskIntalled$.next(true);
+ this._metamaskConfigured$.next(false);
+ this._ready$.next(true);
+ return;
+ }
+ metaMaskProvider.eth.net.getId((metamaskErr, metamask3NetID) => {
+ if (metamaskErr) {
+ this._toastrService.danger('Metamask is enabled but can\'t get network id from Metamask');
+ this._metamaskIntalled$.next(true);
+ this._metamaskConfigured$.next(false);
+ this._ready$.next(true);
return;
}
- web3Provider.eth.net.getId((err, web3NetID) => {
- if (err) {
- this._toastrService.danger('Metamask is enabled but can\'t get network id');
- this._web3Payable$.error(`Failed to get network id: ${err}`);
- return;
- }
- metaMaskProvider.eth.net.getId((err, metamask3NetID) => {
- if (err) {
- this._toastrService.danger('Metamask is enabled but can\'t get network id from Metamask');
- this._web3Payable$.error(`Failed to Metamask network id: ${err}`);
- return;
- }
- if (web3NetID !== metamask3NetID) {
- this._toastrService.danger('Metamask is enabled but networks are different');
- this._web3Payable$.error(`Metamask network ID (${metamask3NetID}) doesn't match expected (${web3NetID})`);
- return;
- }
- this._web3Payable$.next(web3Provider);
- });
- });
+ if (web3NetID !== metamask3NetID) {
+ this._toastrService.warning('Metamask is enabled but networks are different');
+ this._metamaskIntalled$.next(true);
+ this._metamaskConfigured$.next(false);
+ this._ready$.next(true);
+ return;
+ }
+ this._metamaskw3 = metaMaskProvider;
+ this._metamaskIntalled$.next(true);
+ this._metamaskConfigured$.next(true);
+ this._ready$.next(true);
});
- }
-
- sendSignedTx(signed: SignedTransaction): Observable {
- return this.w3Call.pipe(concatMap((web3: Web3) => {
- return fromPromise(web3.eth.sendSignedTransaction(signed.rawTransaction));
- }))
+ });
}
/**
@@ -96,21 +204,22 @@ export class WalletService {
* @param abi
* @param params
*/
- call(addr: string, abi: AbiItem, params: any[]): Observable