diff --git a/packages/insight/src/app/app-routing.module.ts b/packages/insight/src/app/app-routing.module.ts index ab2765dabff..469317c0773 100644 --- a/packages/insight/src/app/app-routing.module.ts +++ b/packages/insight/src/app/app-routing.module.ts @@ -6,6 +6,35 @@ import { availableChainsMatcher } from './guards/available-chain.guard'; +/** + * **Implementation note** + * In Insight, the router is the source-of-truth for most state in the app. When + * the app loads, the route is check to select the proper view and data to + * display. (`AvailableChainGuard` checks to see if the Chain referenced by the + * route is enabled by the connected `bitcore-node` instance.) + * + * When any link is clicked, the router should be updated, and all components + * derive their state by watching the `ActivatedRoute`. It's important to encode + * all important state into routing; we want end-users to always be able to + * copy/paste the link to whatever they're seeing. + * + * **TODO** + * TODO: address route: `CHAIN/address/:address` + * TODO: search routes (and search API to `bitcore-node`): + * `/CHAIN/search`: an empty-state search route (happens when the user taps the search icon) + * `/CHAIN/search/:query`: a full listing of matches for `query` (addresses, transactions, blocks) + * + * In `bitcore-node`, a "first bits" search is quite easy, and already indexed: + * `coins.find({address: {$gt: '1JasonD', $lt: '1JasonE'} })`. The same first + * bits search is also easy for transaction hashes and block hashes. + * + * E.g. `/BCH/search/1JasonD` – should be a search listing showing at least 1 + * address: `1JasonDm4iqi3TJwgpHKSJYfewJBtKewxP` and at least 4 transactions: + * `43bab209c68f3f7334e38a681b007127af5df0d169e998e9e0dd46cb7ab7f783` – mints to a matching address + * `9871be1cfff51d1180ed3069326f83927503e785a015b8ecd4054a8300068b78` - spends from a matching address + * `217cb4071f20a31599c4353becf39b72f15d5cb71851c00a12514be6568fbfbd` - mints to a matching address + * `628a4a10458c0385e845850fc3fb0a4219baa743dad3b091ec7c9324615bab6d` - spends from a matching address + */ const routes: Routes = [ { path: '', diff --git a/packages/insight/src/app/app.component.ts b/packages/insight/src/app/app.component.ts index c4da0722c98..ce3e1aae08a 100644 --- a/packages/insight/src/app/app.component.ts +++ b/packages/insight/src/app/app.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { Title } from '@angular/platform-browser'; import { Platform } from '@ionic/angular'; import { ConfigService } from './services/config/config.service'; diff --git a/packages/insight/src/app/services/network/network.service.ts b/packages/insight/src/app/services/network/network.service.ts index 638e5971a39..8c95eb3cefa 100644 --- a/packages/insight/src/app/services/network/network.service.ts +++ b/packages/insight/src/app/services/network/network.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, fromEvent } from 'rxjs'; -// TODO: network connected/disconnected notification @Injectable({ providedIn: 'root' }) diff --git a/packages/insight/src/app/shared/output/output.component.html b/packages/insight/src/app/shared/output/output.component.html index 4ed94fddb48..3e2e9e08017 100644 --- a/packages/insight/src/app/shared/output/output.component.html +++ b/packages/insight/src/app/shared/output/output.component.html @@ -11,22 +11,35 @@ -
- Not summary - something else -
+

+ TODO: this should be the most linked-to view in Insight. It is the ideal + place for most wallets to link to when showing users their previous + "transactions". For most wallets (including Copay/BitPay currently), + users are sending a single payment to a single address in a + "transaction", i.e. an Output. Showing the user only that output makes + for a much better, easier to understand experience. +

+

+ E.g. Alice sends Bob $10. Her wallet creates a transaction using the + closest available output – in Alice's case, she's not used the wallet + much before, and just withdrew $1000 from an exchange. The wallet sends + $10 to Bob, and $990 back to an internal change address. In the + transaction history, Alice can see the $10 payment. Out of curiosity, + she clicks through to "view her transaction on the blockchain". When she + sees the "$1000 transaction" panic ensues. +

+

+ Outputs still need to be designed – we could consider a view that looks + almost like a transaction receipt. If any view in Insight is also for a + "non-technical" audience, it's this one. The most important element is + the `value` (in the user's displayAs currency). Other data we will want + to include: `timeNormalized`, `address` (or `lockingScript` if no + address format applies), "included in transaction", "mined in block", + "confirmations" and if it has already been spent, "used in input". + `lockingScript` should always be possible to see, even if the output has + a valid address (maybe in a detail view, or if the user clicks on the + address). +

- diff --git a/packages/insight/src/app/shared/transaction/transaction.component.html b/packages/insight/src/app/shared/transaction/transaction.component.html index 23308ddc752..7d96da1ba64 100644 --- a/packages/insight/src/app/shared/transaction/transaction.component.html +++ b/packages/insight/src/app/shared/transaction/transaction.component.html @@ -14,6 +14,7 @@ > Pending + diff --git a/packages/insight/src/app/shared/transaction/transaction.component.ts b/packages/insight/src/app/shared/transaction/transaction.component.ts index d084e0ef3d8..27449097709 100644 --- a/packages/insight/src/app/shared/transaction/transaction.component.ts +++ b/packages/insight/src/app/shared/transaction/transaction.component.ts @@ -17,6 +17,7 @@ export class TransactionComponent { transaction: TransactionJSON; pending = SpentHeightIndicators.pending; + conflicting = SpentHeightIndicators.conflicting; /** * The unit in which to display value – can be either a valid denomination for diff --git a/packages/insight/src/app/transaction/input/input.page.html b/packages/insight/src/app/transaction/input/input.page.html index f890f815275..6342cb119df 100644 --- a/packages/insight/src/app/transaction/input/input.page.html +++ b/packages/insight/src/app/transaction/input/input.page.html @@ -9,3 +9,5 @@
+ + diff --git a/packages/insight/src/app/transaction/input/input.page.ts b/packages/insight/src/app/transaction/input/input.page.ts index e9a37073f99..26407b753d4 100644 --- a/packages/insight/src/app/transaction/input/input.page.ts +++ b/packages/insight/src/app/transaction/input/input.page.ts @@ -1,53 +1,10 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { combineLatest, from, Subject } from 'rxjs'; -import { map, switchMap, take, tap } from 'rxjs/operators'; -import { ApiService } from '../../services/api/api.service'; -import { ConfigService } from '../../services/config/config.service'; -import { - Direction, - StreamingFindOptions, - TransactionJSON -} from '../../types/bitcore-node'; +import { Component } from '@angular/core'; @Component({ selector: 'app-input-page', templateUrl: 'input.page.html', styleUrls: ['input.page.scss'] }) -export class InputPage implements OnInit { - private _query$: Subject< - StreamingFindOptions & { - blockHeight?: number; - blockHash?: string; - } - > = new Subject(); - query$ = this._query$.asObservable(); - - hash = this.route.params - .pipe( - take(1), - map(param => param['hash']) - ) - .toPromise() as Promise; - - block$ = combineLatest(from(this.hash), this.config.currentChain$).pipe( - switchMap(([hash, chain]) => this.apiService.streamBlock(chain, hash)) - ); - - constructor( - public config: ConfigService, - private route: ActivatedRoute, - private apiService: ApiService - ) {} - ngOnInit() { - this.hash.then(hash => { - this._query$.next({ - blockHash: hash, - limit: 20, - direction: Direction.ascending, - paging: 'txid' - }); - }); - } +export class InputPage { + // TODO: see OutputPage } diff --git a/packages/insight/src/app/transaction/inputs/inputs.page.html b/packages/insight/src/app/transaction/inputs/inputs.page.html index 7c97c0e63a7..17f5ab6daa4 100644 --- a/packages/insight/src/app/transaction/inputs/inputs.page.html +++ b/packages/insight/src/app/transaction/inputs/inputs.page.html @@ -16,4 +16,5 @@ other details? This section should blend into the page, rather than being a clickable-card. +
TODO: inputs list
diff --git a/packages/insight/src/app/transaction/inputs/inputs.page.ts b/packages/insight/src/app/transaction/inputs/inputs.page.ts index 298bd1e2a9f..93ff47a1467 100644 --- a/packages/insight/src/app/transaction/inputs/inputs.page.ts +++ b/packages/insight/src/app/transaction/inputs/inputs.page.ts @@ -1,53 +1,10 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { combineLatest, from, Subject } from 'rxjs'; -import { map, switchMap, take, tap } from 'rxjs/operators'; -import { ApiService } from '../../services/api/api.service'; -import { ConfigService } from '../../services/config/config.service'; -import { - Direction, - StreamingFindOptions, - TransactionJSON -} from '../../types/bitcore-node'; +import { Component } from '@angular/core'; @Component({ selector: 'app-inputs-page', templateUrl: 'inputs.page.html', styleUrls: ['inputs.page.scss'] }) -export class InputsPage implements OnInit { - private _query$: Subject< - StreamingFindOptions & { - blockHeight?: number; - blockHash?: string; - } - > = new Subject(); - query$ = this._query$.asObservable(); - - hash = this.route.params - .pipe( - take(1), - map(param => param['hash']) - ) - .toPromise() as Promise; - - block$ = combineLatest(from(this.hash), this.config.currentChain$).pipe( - switchMap(([hash, chain]) => this.apiService.streamBlock(chain, hash)) - ); - - constructor( - public config: ConfigService, - private route: ActivatedRoute, - private apiService: ApiService - ) {} - ngOnInit() { - this.hash.then(hash => { - this._query$.next({ - blockHash: hash, - limit: 20, - direction: Direction.ascending, - paging: 'txid' - }); - }); - } +export class InputsPage { + // TODO: see OutputsPage } diff --git a/packages/insight/src/app/transaction/output/output.page.html b/packages/insight/src/app/transaction/output/output.page.html index 182558b51da..f1042ff91b7 100644 --- a/packages/insight/src/app/transaction/output/output.page.html +++ b/packages/insight/src/app/transaction/output/output.page.html @@ -8,4 +8,11 @@ - + + + diff --git a/packages/insight/src/app/transaction/output/output.page.ts b/packages/insight/src/app/transaction/output/output.page.ts index 83774150f05..206c024a4ee 100644 --- a/packages/insight/src/app/transaction/output/output.page.ts +++ b/packages/insight/src/app/transaction/output/output.page.ts @@ -1,14 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { combineLatest, from, Subject } from 'rxjs'; -import { map, switchMap, take, tap } from 'rxjs/operators'; +import { combineLatest, from, Observable, of } from 'rxjs'; +import { filter, map, switchMap } from 'rxjs/operators'; import { ApiService } from '../../services/api/api.service'; import { ConfigService } from '../../services/config/config.service'; -import { - Direction, - StreamingFindOptions, - TransactionJSON -} from '../../types/bitcore-node'; +import { CoinJSON } from '../../types/bitcore-node'; @Component({ selector: 'app-output-page', @@ -16,38 +12,29 @@ import { styleUrls: ['output.page.scss'] }) export class OutputPage implements OnInit { - private _query$: Subject< - StreamingFindOptions & { - blockHeight?: number; - blockHash?: string; - } - > = new Subject(); - query$ = this._query$.asObservable(); - - hash = this.route.params - .pipe( - take(1), - map(param => param['hash']) - ) - .toPromise() as Promise; - - block$ = combineLatest(from(this.hash), this.config.currentChain$).pipe( - switchMap(([hash, chain]) => this.apiService.streamBlock(chain, hash)) - ); + coin$: Observable; constructor( public config: ConfigService, private route: ActivatedRoute, private apiService: ApiService ) {} + ngOnInit() { - this.hash.then(hash => { - this._query$.next({ - blockHash: hash, - limit: 20, - direction: Direction.ascending, - paging: 'txid' - }); - }); + this.coin$ = combineLatest( + this.config.currentChain$, + this.route.paramMap.pipe( + switchMap(params => of([params.get('hash'), params.get('output')])), + filter<[string, string]>( + ([hash, output]) => hash !== null && output !== null + ) + ) + ).pipe( + switchMap(([chain, [hash, index]]) => + this.apiService + .streamTransactionCoins(chain, hash) + .pipe(map(listing => listing.outputs[index])) + ) + ); } } diff --git a/packages/insight/src/app/transaction/transaction.page.ts b/packages/insight/src/app/transaction/transaction.page.ts index 44dec49a672..5de79c177ef 100644 --- a/packages/insight/src/app/transaction/transaction.page.ts +++ b/packages/insight/src/app/transaction/transaction.page.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { combineLatest, Observable, of } from 'rxjs'; -import { filter, map, switchMap, tap } from 'rxjs/operators'; +import { filter, switchMap } from 'rxjs/operators'; import { ApiService } from '../services/api/api.service'; import { ConfigService } from '../services/config/config.service'; import { TransactionJSON } from '../types/bitcore-node'; diff --git a/packages/insight/src/environments/environment.ts b/packages/insight/src/environments/environment.ts index e8266e39512..49ade1def11 100644 --- a/packages/insight/src/environments/environment.ts +++ b/packages/insight/src/environments/environment.ts @@ -19,8 +19,7 @@ export const environment = { apiPrefix: '/api', production: false, debugRouting: false, - // pollingRateMilliseconds: 60 * 1000, - pollingRateMilliseconds: 5 * 1000, + pollingRateMilliseconds: 20 * 1000, ...{ loggingSettings: { ...environmentProd.loggingSettings,