Merge branch 'master' into spencer/docs

# Conflicts:
#	package-lock.json
This commit is contained in:
Spencer Ofwiti 2021-06-14 14:16:21 +03:00
commit 7923c63aa8
18 changed files with 969 additions and 5325 deletions

5880
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -36,10 +36,10 @@
"@angular/platform-browser-dynamic": "~10.2.0", "@angular/platform-browser-dynamic": "~10.2.0",
"@angular/router": "~10.2.0", "@angular/router": "~10.2.0",
"@angular/service-worker": "~10.2.0", "@angular/service-worker": "~10.2.0",
"@cicnet/cic-client": "^0.1.6",
"@cicnet/schemas-data-validator": "*", "@cicnet/schemas-data-validator": "*",
"@popperjs/core": "^2.5.4", "@popperjs/core": "^2.5.4",
"bootstrap": "^4.5.3", "bootstrap": "^4.5.3",
"cic-client": "0.1.4",
"cic-client-meta": "0.0.7-alpha.6", "cic-client-meta": "0.0.7-alpha.6",
"ethers": "^5.0.31", "ethers": "^5.0.31",
"http-server": "^0.12.3", "http-server": "^0.12.3",

View File

@ -14,7 +14,6 @@ import { BehaviorSubject, Observable } from 'rxjs';
providedIn: 'root', providedIn: 'root',
}) })
export class AuthService { export class AuthService {
sessionToken: any;
mutableKeyStore: MutableKeyStore; mutableKeyStore: MutableKeyStore;
trustedUsers: Array<Staff> = []; trustedUsers: Array<Staff> = [];
private trustedUsersList: BehaviorSubject<Array<Staff>> = new BehaviorSubject<Array<Staff>>( private trustedUsersList: BehaviorSubject<Array<Staff>> = new BehaviorSubject<Array<Staff>>(
@ -32,41 +31,46 @@ export class AuthService {
async init(): Promise<void> { async init(): Promise<void> {
await this.mutableKeyStore.loadKeyring(); await this.mutableKeyStore.loadKeyring();
// TODO setting these together should be atomic
if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) {
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
}
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) { if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
await this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))); await this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY')));
} }
} }
getSessionToken(): string {
return sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
}
setSessionToken(token): void {
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), token);
}
setState(s): void { setState(s): void {
document.getElementById('state').innerHTML = s; document.getElementById('state').innerHTML = s;
} }
getWithToken(): Promise<boolean> { getWithToken(): Promise<boolean> {
return new Promise((resolve, reject) => {
const headers = { const headers = {
Authorization: 'Bearer ' + this.sessionToken, Authorization: 'Bearer ' + this.getSessionToken,
'Content-Type': 'application/json;charset=utf-8', 'Content-Type': 'application/json;charset=utf-8',
'x-cic-automerge': 'none', 'x-cic-automerge': 'none',
}; };
const options = { const options = {
headers, headers,
}; };
fetch(environment.cicMetaUrl, options).then((response) => { return fetch(environment.cicMetaUrl, options).then((response) => {
if (response.status === 401) { if (!response.ok) {
return reject(rejectBody(response)); this.loggingService.sendErrorLevelMessage('failed to get with auth token.', this, {
} error: '',
return resolve(true);
}); });
return false;
}
return true;
}); });
} }
// TODO rename to send signed challenge and set session. Also separate these responsibilities // TODO rename to send signed challenge and set session. Also separate these responsibilities
sendResponse(hobaResponseEncoded: any): Promise<boolean> { sendSignedChallenge(hobaResponseEncoded: any): Promise<any> {
return new Promise((resolve, reject) => {
const headers = { const headers = {
Authorization: 'HOBA ' + hobaResponseEncoded, Authorization: 'HOBA ' + hobaResponseEncoded,
'Content-Type': 'application/json;charset=utf-8', 'Content-Type': 'application/json;charset=utf-8',
@ -75,85 +79,51 @@ export class AuthService {
const options = { const options = {
headers, headers,
}; };
fetch(environment.cicMetaUrl, options).then((response) => { return fetch(environment.cicMetaUrl, options);
if (response.status === 401) {
return reject(rejectBody(response));
}
this.sessionToken = response.headers.get('Token');
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
this.setState('Click button to log in');
return resolve(true);
});
});
} }
getChallenge(): Promise<any> { getChallenge(): Promise<any> {
return new Promise((resolve, reject) => { return fetch(environment.cicMetaUrl).then((response) => {
fetch(environment.cicMetaUrl).then(async (response) => {
if (response.status === 401) { if (response.status === 401) {
const authHeader: string = response.headers.get('WWW-Authenticate'); const authHeader: string = response.headers.get('WWW-Authenticate');
return resolve(hobaParseChallengeHeader(authHeader)); return hobaParseChallengeHeader(authHeader);
} }
if (!response.ok) {
return reject(rejectBody(response));
}
});
}); });
} }
async login(): Promise<boolean> { async login(): Promise<boolean> {
if (this.sessionToken !== undefined) { if (this.getSessionToken()) {
try { sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
const response: boolean = await this.getWithToken();
return response === true;
} catch (e) {
this.loggingService.sendErrorLevelMessage('Login token failed', this, { error: e });
}
} else { } else {
try {
const o = await this.getChallenge(); const o = await this.getChallenge();
const response: boolean = await this.loginResponse(o);
return response === true;
} catch (e) {
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, { error: e });
}
}
return false;
}
async loginResponse(o: { challenge: string; realm: any }): Promise<any> {
return new Promise(async (resolve, reject) => {
try {
const r = await signChallenge( const r = await signChallenge(
o.challenge, o.challenge,
o.realm, o.realm,
environment.cicMetaUrl, environment.cicMetaUrl,
this.mutableKeyStore this.mutableKeyStore
); );
const response: boolean = await this.sendResponse(r);
resolve(response); const tokenResponse = await this.sendSignedChallenge(r).then((response) => {
} catch (error) { const token = response.headers.get('Token');
if (error instanceof HttpError) { if (token) {
if (error.status === 403) { return token;
this.errorDialogService.openDialog({
message: 'You are not authorized to use this system',
});
} else if (error.status === 401) {
this.errorDialogService.openDialog({
message:
'Unable to authenticate with the service. ' +
'Please speak with the staff at Grassroots ' +
'Economics for requesting access ' +
'staff@grassrootseconomics.net.',
});
} }
} else { if (response.status === 401) {
// TODO define this error throw new HttpError('You are not authorized to use this system', response.status);
this.errorDialogService.openDialog({ message: 'Incorrect key passphrase.' });
} }
resolve(false); if (!response.ok) {
throw new HttpError('Unknown error from authentication server', response.status);
} }
}); });
if (tokenResponse) {
this.setSessionToken(tokenResponse);
this.setState('Click button to log in');
return true;
}
return false;
}
} }
loginView(): void { loginView(): void {
@ -197,7 +167,6 @@ export class AuthService {
logout(): void { logout(): void {
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN')); sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
localStorage.removeItem(btoa('CICADA_PRIVATE_KEY')); localStorage.removeItem(btoa('CICADA_PRIVATE_KEY'));
this.sessionToken = undefined;
window.location.reload(); window.location.reload();
} }

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Settings } from '@app/_models'; import { Settings } from '@app/_models';
import { TransactionHelper } from 'cic-client'; import { TransactionHelper } from '@cicnet/cic-client';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { TransactionService } from '@app/_services/transaction.service'; import { TransactionService } from '@app/_services/transaction.service';
import { environment } from '@src/environments/environment'; import { environment } from '@src/environments/environment';
@ -39,10 +39,6 @@ export class BlockSyncService {
settings.txHelper.onconversion = async (transaction: any): Promise<any> => { settings.txHelper.onconversion = async (transaction: any): Promise<any> => {
window.dispatchEvent(this.newEvent(transaction, 'cic_convert')); window.dispatchEvent(this.newEvent(transaction, 'cic_convert'));
}; };
// settings.registry.onload = (addressReturned: string): void => {
// this.loggingService.sendInfoLevelMessage(`Loaded network contracts ${addressReturned}`);
// this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit);
// };
this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit); this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit);
} }

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { environment } from '@src/environments/environment'; import { environment } from '@src/environments/environment';
import { CICRegistry, FileGetter } from 'cic-client'; import { CICRegistry, FileGetter } from '@cicnet/cic-client';
import { HttpGetter } from '@app/_helpers'; import { HttpGetter } from '@app/_helpers';
import { Web3Service } from '@app/_services/web3.service'; import { Web3Service } from '@app/_services/web3.service';

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CICRegistry } from 'cic-client'; import { CICRegistry } from '@cicnet/cic-client';
import { TokenRegistry } from '@app/_eth'; import { TokenRegistry } from '@app/_eth';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { RegistryService } from '@app/_services/registry.service'; import { RegistryService } from '@app/_services/registry.service';
@ -12,21 +12,21 @@ import {BehaviorSubject, Observable, Subject} from 'rxjs';
export class TokenService { export class TokenService {
registry: CICRegistry; registry: CICRegistry;
tokenRegistry: TokenRegistry; tokenRegistry: TokenRegistry;
onload: (status: boolean) => void;
tokens: Array<Token> = []; tokens: Array<Token> = [];
private tokensList: BehaviorSubject<Array<Token>> = new BehaviorSubject<Array<Token>>(this.tokens); private tokensList: BehaviorSubject<Array<Token>> = new BehaviorSubject<Array<Token>>(
this.tokens
);
tokensSubject: Observable<Array<Token>> = this.tokensList.asObservable(); tokensSubject: Observable<Array<Token>> = this.tokensList.asObservable();
load: BehaviorSubject<any> = new BehaviorSubject<any>(false);
constructor(private httpClient: HttpClient) {} constructor(private httpClient: HttpClient) {}
async init(): Promise<void> { async init(): Promise<void> {
this.registry = await RegistryService.getRegistry(); this.registry = await RegistryService.getRegistry();
this.registry.onload = async (address: string): Promise<void> => {
this.tokenRegistry = new TokenRegistry( this.tokenRegistry = new TokenRegistry(
await this.registry.getContractAddressByName('TokenRegistry') await this.registry.getContractAddressByName('TokenRegistry')
); );
this.onload(this.tokenRegistry !== undefined); this.load.next(true);
};
} }
addToken(token: Token): void { addToken(token: Token): void {
@ -71,7 +71,17 @@ export class TokenService {
} }
async getTokenBalance(address: string): Promise<(address: string) => Promise<number>> { async getTokenBalance(address: string): Promise<(address: string) => Promise<number>> {
const sarafuToken = await this.registry.addToken(await this.tokenRegistry.entry(0)); const token = await this.registry.addToken(await this.tokenRegistry.entry(0));
return await sarafuToken.methods.balanceOf(address).call(); return await token.methods.balanceOf(address).call();
}
async getTokenName(): Promise<string> {
const token = await this.registry.addToken(await this.tokenRegistry.entry(0));
return await token.methods.name().call();
}
async getTokenSymbol(): Promise<string> {
const token = await this.registry.addToken(await this.tokenRegistry.entry(0));
return await token.methods.symbol().call();
} }
} }

View File

@ -14,7 +14,7 @@ import { AuthService } from '@app/_services/auth.service';
import { defaultAccount } from '@app/_models'; import { defaultAccount } from '@app/_models';
import { LoggingService } from '@app/_services/logging.service'; import { LoggingService } from '@app/_services/logging.service';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { CICRegistry } from 'cic-client'; import { CICRegistry } from '@cicnet/cic-client';
import { RegistryService } from '@app/_services/registry.service'; import { RegistryService } from '@app/_services/registry.service';
import Web3 from 'web3'; import Web3 from 'web3';
import { Web3Service } from '@app/_services/web3.service'; import { Web3Service } from '@app/_services/web3.service';
@ -148,7 +148,6 @@ export class TransactionService {
recipientAddress: string, recipientAddress: string,
value: number value: number
): Promise<any> { ): Promise<any> {
this.registry.onload = async (addressReturned: string): Promise<void> => {
const transferAuthAddress = await this.registry.getContractAddressByName( const transferAuthAddress = await this.registry.getContractAddressByName(
'TransferAuthorization' 'TransferAuthorization'
); );
@ -185,6 +184,5 @@ export class TransactionService {
this.loggingService.sendInfoLevelMessage(`Result: ${result}`); this.loggingService.sendInfoLevelMessage(`Result: ${result}`);
const transaction = await this.web3.eth.getTransaction(result.transactionHash); const transaction = await this.web3.eth.getTransaction(result.transactionHash);
this.loggingService.sendInfoLevelMessage(`Transaction: ${transaction}`); this.loggingService.sendInfoLevelMessage(`Transaction: ${transaction}`);
};
} }
} }

View File

@ -10,7 +10,7 @@ import { TokenService } from '@app/_services/token.service';
import { AccountIndex } from '@app/_eth'; import { AccountIndex } from '@app/_eth';
import { MutableKeyStore, PGPSigner, Signer } from '@app/_pgp'; import { MutableKeyStore, PGPSigner, Signer } from '@app/_pgp';
import { RegistryService } from '@app/_services/registry.service'; import { RegistryService } from '@app/_services/registry.service';
import { CICRegistry } from 'cic-client'; import { CICRegistry } from '@cicnet/cic-client';
import { AuthService } from '@app/_services/auth.service'; import { AuthService } from '@app/_services/auth.service';
import { personValidation, vcardValidation } from '@app/_helpers'; import { personValidation, vcardValidation } from '@app/_helpers';
import { add0x } from '@src/assets/js/ethtx/dist/hex'; import { add0x } from '@src/assets/js/ethtx/dist/hex';
@ -201,11 +201,13 @@ export class UserService {
const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
const accountInfo = account.m.data; const accountInfo = account.m.data;
await personValidation(accountInfo); await personValidation(accountInfo);
this.tokenService.onload = async (status: boolean): Promise<void> => { this.tokenService.load.subscribe(async (status: boolean) => {
if (status) {
accountInfo.balance = await this.tokenService.getTokenBalance( accountInfo.balance = await this.tokenService.getTokenBalance(
accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0] accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
); );
}; }
});
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard)); accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
await vcardValidation(accountInfo.vcard); await vcardValidation(accountInfo.vcard);
this.addAccount(accountInfo, limit); this.addAccount(accountInfo, limit);

View File

@ -2,6 +2,8 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CustomErrorStateMatcher } from '@app/_helpers'; import { CustomErrorStateMatcher } from '@app/_helpers';
import { AuthService } from '@app/_services'; import { AuthService } from '@app/_services';
import { ErrorDialogService } from '@app/_services/error-dialog.service';
import { LoggingService } from '@app/_services/logging.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@Component({ @Component({
@ -19,18 +21,17 @@ export class AuthComponent implements OnInit {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router private router: Router,
private errorDialogService: ErrorDialogService
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.keyForm = this.formBuilder.group({ this.keyForm = this.formBuilder.group({
key: ['', Validators.required], key: ['', Validators.required],
}); });
await this.authService.init(); if (this.authService.getPrivateKey()) {
// if (this.authService.privateKey !== undefined) { this.authService.loginView();
// const setKey = await this.authService.setKey(this.authService.privateKey); }
// }
// }
} }
get keyFormStub(): any { get keyFormStub(): any {
@ -49,19 +50,20 @@ export class AuthComponent implements OnInit {
this.loading = false; this.loading = false;
} }
login(): void { async login(): Promise<void> {
// TODO check if we have privatekey try {
// Send us to home if we have a private key const loginResult = await this.authService.login();
// talk to meta somehow if (loginResult) {
// in the error interceptor if 401/403 handle it
// if 200 go /home
if (this.authService.getPrivateKey()) {
this.router.navigate(['/home']); this.router.navigate(['/home']);
} }
} catch (HttpError) {
this.errorDialogService.openDialog({
message: HttpError.message,
});
}
} }
switchWindows(): void { switchWindows(): void {
this.authService.sessionToken = undefined;
const divOne: HTMLElement = document.getElementById('one'); const divOne: HTMLElement = document.getElementById('one');
const divTwo: HTMLElement = document.getElementById('two'); const divTwo: HTMLElement = document.getElementById('two');
this.toggleDisplay(divOne); this.toggleDisplay(divOne);

View File

@ -33,7 +33,7 @@
<h3> <h3>
<strong> {{account?.vcard?.fn[0].value}} </strong> <strong> {{account?.vcard?.fn[0].value}} </strong>
</h3> </h3>
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance | tokenRatio}} SRF</span> <span class="ml-auto"><strong>Balance:</strong> {{account?.balance | tokenRatio}} {{ tokenSymbol | uppercase }}</span>
<span class="ml-2"><strong>Created:</strong> {{account?.date_registered | unixDate}}</span> <span class="ml-2"><strong>Created:</strong> {{account?.date_registered | unixDate}}</span>
<span class="ml-2"><strong>Address:</strong> <span class="ml-2"><strong>Address:</strong>
<a href="{{bloxbergLink}}" target="_blank"> {{accountAddress}} </a> <a href="{{bloxbergLink}}" target="_blank"> {{accountAddress}} </a>
@ -218,7 +218,7 @@
<tbody> <tbody>
<tr> <tr>
<td>{{account?.vcard?.fn[0].value}}</td> <td>{{account?.vcard?.fn[0].value}}</td>
<td>{{account?.balance | tokenRatio}}</td> <td>{{account?.balance | tokenRatio}} {{ tokenSymbol | uppercase }}</td>
<td>{{account?.date_registered | unixDate}}</td> <td>{{account?.date_registered | unixDate}}</td>
<td> <td>
<span class="badge badge-success badge-pill"> <span class="badge badge-success badge-pill">
@ -274,8 +274,8 @@
<ng-container matColumnDef="value"> <ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Value </th> <th mat-header-cell *matHeaderCellDef mat-sort-header> Value </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span *ngIf="transaction.type == 'transaction'">{{transaction?.value | tokenRatio}}</span> <span *ngIf="transaction.type == 'transaction'">{{transaction?.value | tokenRatio}} {{ tokenSymbol | uppercase }}</span>
<span *ngIf="transaction.type == 'conversion'">{{transaction?.toValue | tokenRatio}}</span> <span *ngIf="transaction.type == 'conversion'">{{transaction?.toValue | tokenRatio}} {{ tokenSymbol | uppercase }}</span>
</td> </td>
</ng-container> </ng-container>
@ -348,7 +348,7 @@
<ng-container matColumnDef="balance"> <ng-container matColumnDef="balance">
<mat-header-cell *matHeaderCellDef mat-sort-header> BALANCE </mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header> BALANCE </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user?.balance}} </mat-cell> <mat-cell *matCellDef="let user"> {{user?.balance | tokenRatio}} {{tokenSymbol | uppercase}} </mat-cell>
</ng-container> </ng-container>
<ng-container matColumnDef="location"> <ng-container matColumnDef="location">

View File

@ -64,6 +64,7 @@ export class AccountDetailsComponent implements OnInit {
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher(); matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
submitted: boolean = false; submitted: boolean = false;
bloxbergLink: string; bloxbergLink: string;
tokenSymbol: string;
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@ -113,7 +114,7 @@ export class AccountDetailsComponent implements OnInit {
this.loggingService.sendInfoLevelMessage(this.account); this.loggingService.sendInfoLevelMessage(this.account);
const fullName = this.account.vcard?.fn[0].value.split(' '); const fullName = this.account.vcard?.fn[0].value.split(' ');
this.accountInfoForm.patchValue({ this.accountInfoForm.patchValue({
firstName: fullName[0], firstName: fullName[0].split(',')[0],
lastName: fullName.slice(1).join(' '), lastName: fullName.slice(1).join(' '),
phoneNumber: this.account.vcard?.tel[0].value, phoneNumber: this.account.vcard?.tel[0].value,
age: this.account.age, age: this.account.age,
@ -189,6 +190,11 @@ export class AccountDetailsComponent implements OnInit {
.getGenders() .getGenders()
.pipe(first()) .pipe(first())
.subscribe((res) => (this.genders = res)); .subscribe((res) => (this.genders = res));
this.tokenService.load.subscribe(async (status: boolean) => {
if (status) {
this.tokenSymbol = await this.tokenService.getTokenSymbol();
}
});
} }
doTransactionFilter(value: string): void { doTransactionFilter(value: string): void {
@ -220,7 +226,7 @@ export class AccountDetailsComponent implements OnInit {
} }
const accountKey = await this.userService.changeAccountInfo( const accountKey = await this.userService.changeAccountInfo(
this.accountAddress, this.accountAddress,
this.accountInfoFormStub.firstName.value + ' ' + this.accountInfoFormStub.lastName.value, this.accountInfoFormStub.firstName.value + ', ' + this.accountInfoFormStub.lastName.value,
this.accountInfoFormStub.phoneNumber.value, this.accountInfoFormStub.phoneNumber.value,
this.accountInfoFormStub.age.value, this.accountInfoFormStub.age.value,
this.accountInfoFormStub.type.value, this.accountInfoFormStub.type.value,

View File

@ -29,9 +29,11 @@ export class TokensComponent implements OnInit {
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
await this.tokenService.init(); await this.tokenService.init();
this.tokenService.onload = async (status: boolean): Promise<void> => { this.tokenService.load.subscribe(async (status: boolean) => {
if (status) {
await this.tokenService.getTokens(); await this.tokenService.getTokens();
}; }
});
this.tokenService.tokensSubject.subscribe((tokens) => { this.tokenService.tokensSubject.subscribe((tokens) => {
this.loggingService.sendInfoLevelMessage(tokens); this.loggingService.sendInfoLevelMessage(tokens);
this.dataSource = new MatTableDataSource(tokens); this.dataSource = new MatTableDataSource(tokens);

View File

@ -30,7 +30,7 @@
<button mat-raised-button color="primary" class="btn btn-outline-info" (click)="viewRecipient()">View Recipient</button> <button mat-raised-button color="primary" class="btn btn-outline-info" (click)="viewRecipient()">View Recipient</button>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span>Amount: SRF {{transaction.value | tokenRatio}}</span> <span>Amount: {{transaction.value | tokenRatio}} {{ tokenSymbol | uppercase }}</span>
</li> </li>
</ul> </ul>
<h4 class="mt-2">Token: </h4> <h4 class="mt-2">Token: </h4>
@ -43,10 +43,10 @@
</span> </span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span>Name: Sarafu Token</span> <span>Name: {{ tokenName }}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span>Symbol: SRF</span> <span>Symbol: {{ tokenSymbol | uppercase }}</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -109,10 +109,10 @@
<span>Name: {{transaction.sourceToken.name}}</span> <span>Name: {{transaction.sourceToken.name}}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span>Symbol: {{transaction.sourceToken.symbol}}</span> <span>Symbol: {{transaction.sourceToken.symbol | uppercase}}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span>Amount: {{transaction.sourceToken.symbol + ' ' + transaction.fromValue}}</span> <span>Amount: {{transaction.fromValue}} {{transaction.sourceToken.symbol | uppercase}}</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -130,10 +130,10 @@
<span>Name: {{transaction.destinationToken.name}}</span> <span>Name: {{transaction.destinationToken.name}}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span>Symbol: {{transaction.destinationToken.symbol}}</span> <span>Symbol: {{transaction.destinationToken.symbol | uppercase}}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<span>Amount: {{transaction.destinationToken.symbol + ' ' + transaction.toValue}}</span> <span>Amount: {{transaction.toValue}} {{transaction.destinationToken.symbol | uppercase}}</span>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -7,7 +7,7 @@ import {
Output, Output,
} from '@angular/core'; } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TransactionService } from '@app/_services'; import { TokenService, TransactionService } from '@app/_services';
import { copyToClipboard } from '@app/_helpers'; import { copyToClipboard } from '@app/_helpers';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { strip0x } from '@src/assets/js/ethtx/dist/hex'; import { strip0x } from '@src/assets/js/ethtx/dist/hex';
@ -26,15 +26,19 @@ export class TransactionDetailsComponent implements OnInit {
senderBloxbergLink: string; senderBloxbergLink: string;
recipientBloxbergLink: string; recipientBloxbergLink: string;
traderBloxbergLink: string; traderBloxbergLink: string;
tokenName: string;
tokenSymbol: string;
constructor( constructor(
private router: Router, private router: Router,
private transactionService: TransactionService, private transactionService: TransactionService,
private snackBar: MatSnackBar private snackBar: MatSnackBar,
private tokenService: TokenService
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
await this.transactionService.init(); await this.transactionService.init();
await this.tokenService.init();
if (this.transaction?.type === 'conversion') { if (this.transaction?.type === 'conversion') {
this.traderBloxbergLink = this.traderBloxbergLink =
'https://blockexplorer.bloxberg.org/address/' + this.transaction?.trader + '/transactions'; 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.trader + '/transactions';
@ -44,6 +48,12 @@ export class TransactionDetailsComponent implements OnInit {
this.recipientBloxbergLink = this.recipientBloxbergLink =
'https://blockexplorer.bloxberg.org/address/' + this.transaction?.to + '/transactions'; 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.to + '/transactions';
} }
this.tokenService.load.subscribe(async (status: boolean) => {
if (status) {
this.tokenSymbol = await this.tokenService.getTokenSymbol();
this.tokenName = await this.tokenService.getTokenName();
}
});
} }
async viewSender(): Promise<void> { async viewSender(): Promise<void> {

View File

@ -59,8 +59,8 @@
<ng-container matColumnDef="value"> <ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Value </th> <th mat-header-cell *matHeaderCellDef mat-sort-header> Value </th>
<td mat-cell *matCellDef="let transaction"> <td mat-cell *matCellDef="let transaction">
<span *ngIf="transaction.type == 'transaction'">{{transaction?.value | tokenRatio}}</span> <span *ngIf="transaction.type == 'transaction'">{{transaction?.value | tokenRatio}} {{ tokenSymbol | uppercase }}</span>
<span *ngIf="transaction.type == 'conversion'">{{transaction?.toValue | tokenRatio}}</span> <span *ngIf="transaction.type == 'conversion'">{{transaction?.toValue | tokenRatio}} {{ tokenSymbol | uppercase }}</span>
</td> </td>
</ng-container> </ng-container>

View File

@ -5,7 +5,7 @@ import {
OnInit, OnInit,
ViewChild, ViewChild,
} from '@angular/core'; } from '@angular/core';
import { BlockSyncService, TransactionService, UserService } from '@app/_services'; import { BlockSyncService, TokenService, TransactionService, UserService } from '@app/_services';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
@ -28,6 +28,7 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
transaction: Transaction; transaction: Transaction;
transactionsType: string = 'all'; transactionsType: string = 'all';
transactionsTypes: Array<string>; transactionsTypes: Array<string>;
tokenSymbol: string;
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
@ -35,7 +36,8 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
constructor( constructor(
private blockSyncService: BlockSyncService, private blockSyncService: BlockSyncService,
private transactionService: TransactionService, private transactionService: TransactionService,
private userService: UserService private userService: UserService,
private tokenService: TokenService
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
@ -46,6 +48,7 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
this.transactions = transactions; this.transactions = transactions;
}); });
await this.blockSyncService.init(); await this.blockSyncService.init();
await this.tokenService.init();
await this.transactionService.init(); await this.transactionService.init();
await this.userService.init(); await this.userService.init();
await this.blockSyncService.blockSync(); await this.blockSyncService.blockSync();
@ -53,6 +56,11 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
.getTransactionTypes() .getTransactionTypes()
.pipe(first()) .pipe(first())
.subscribe((res) => (this.transactionsTypes = res)); .subscribe((res) => (this.transactionsTypes = res));
this.tokenService.load.subscribe(async (status: boolean) => {
if (status) {
this.tokenSymbol = await this.tokenService.getTokenSymbol();
}
});
} }
viewTransaction(transaction): void { viewTransaction(transaction): void {

View File

@ -2,7 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'tokenRatio' }) @Pipe({ name: 'tokenRatio' })
export class TokenRatioPipe implements PipeTransform { export class TokenRatioPipe implements PipeTransform {
transform(value: any, ...args): any { transform(value: any = 0, ...args): any {
return Number(value) / Math.pow(10, 6); return Number(value) / Math.pow(10, 6);
} }
} }

View File

@ -50,6 +50,13 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [{ "internalType": "address", "name": "_holder", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "decimals", "name": "decimals",