WIP: Bisq DAO support. Transactions list and details.
This commit is contained in:
1 parent
94ccd98d0a
commit
d39b4a5c92
59 files changed
+926
-38
No files matched your search
@@ -43,3 +43,4 @@ testem.log
|
||||
Thumbs.db
|
||||
|
||||
cache.json
|
||||
blocks.json
|
||||
@@ -13,6 +13,7 @@
|
||||
"INITIAL_BLOCK_AMOUNT": 8,
|
||||
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
||||
"ELECTRS_API_URL": "https://www.blockstream.info/testnet/api",
|
||||
"BISQ_ENABLED": false,
|
||||
"SSL": false,
|
||||
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
||||
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import * as fs from 'fs';
|
||||
import { BisqBlocks, BisqBlock, BisqTransaction } from '../interfaces';
|
||||
|
||||
class Bisq {
|
||||
static FILE_NAME = './blocks.json';
|
||||
private latestBlockHeight = 0;
|
||||
private blocks: BisqBlock[] = [];
|
||||
private transactions: BisqTransaction[] = [];
|
||||
private transactionsIndex: { [txId: string]: BisqTransaction } = {};
|
||||
private blocksIndex: { [hash: string]: BisqBlock } = {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
startBisqService(): void {
|
||||
this.loadBisqDumpFile();
|
||||
}
|
||||
|
||||
async loadBisqDumpFile(): Promise<void> {
|
||||
await this.loadBisqBlocksDump();
|
||||
this.buildIndex();
|
||||
}
|
||||
|
||||
getTransaction(txId: string): BisqTransaction | undefined {
|
||||
return this.transactionsIndex[txId];
|
||||
}
|
||||
|
||||
getTransactions(start: number, length: number): [BisqTransaction[], number] {
|
||||
return [this.transactions.slice(start, length + start), this.transactions.length];
|
||||
}
|
||||
|
||||
getBlock(hash: string): BisqBlock | undefined {
|
||||
return this.blocksIndex[hash];
|
||||
}
|
||||
|
||||
private buildIndex() {
|
||||
this.blocks.forEach((block) => {
|
||||
this.blocksIndex[block.hash] = block;
|
||||
block.txs.forEach((tx) => {
|
||||
this.transactions.push(tx);
|
||||
this.transactionsIndex[tx.id] = tx;
|
||||
});
|
||||
});
|
||||
this.blocks.reverse();
|
||||
this.transactions.reverse();
|
||||
console.log('Bisq data index rebuilt');
|
||||
}
|
||||
|
||||
private async loadBisqBlocksDump() {
|
||||
const start = new Date().getTime();
|
||||
const cacheData = await this.loadData();
|
||||
if (cacheData) {
|
||||
console.log('Parsing Bisq data from dump file');
|
||||
const data: BisqBlocks = JSON.parse(cacheData);
|
||||
if (data.blocks) {
|
||||
this.blocks = data.blocks;
|
||||
this.latestBlockHeight = data.chainHeight;
|
||||
const end = new Date().getTime();
|
||||
const time = end - start;
|
||||
console.log('Loaded bisq dump in ' + time + ' ms');
|
||||
} else {
|
||||
throw new Error(`Bisq dump didn't contain any blocks`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private loadData(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(Bisq.FILE_NAME, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new Bisq();
|
||||
@@ -73,10 +73,10 @@ class Blocks {
|
||||
console.log(`${found} of ${txIds.length} found in mempool. ${notFound} not found.`);
|
||||
|
||||
block.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||
block.coinbaseTx = this.stripCoinbaseTransaction(transactions[0]);
|
||||
transactions.sort((a, b) => b.feePerVsize - a.feePerVsize);
|
||||
block.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.feePerVsize)) : 0;
|
||||
block.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions, 8, 1) : [0, 0];
|
||||
block.coinbaseTx = this.stripCoinbaseTransaction(transactions[0]);
|
||||
|
||||
this.blocks.push(block);
|
||||
if (this.blocks.length > config.KEEP_BLOCK_AMOUNT) {
|
||||
|
||||
@@ -35,6 +35,9 @@ class Mempool {
|
||||
|
||||
public setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
|
||||
this.mempoolCache = mempoolData;
|
||||
if (this.mempoolChangedCallback) {
|
||||
this.mempoolChangedCallback(this.mempoolCache, [], []);
|
||||
}
|
||||
}
|
||||
|
||||
public async updateMemPoolInfo() {
|
||||
|
||||
@@ -14,6 +14,7 @@ import diskCache from './api/disk-cache';
|
||||
import statistics from './api/statistics';
|
||||
import websocketHandler from './api/websocket-handler';
|
||||
import fiatConversion from './api/fiat-conversion';
|
||||
import bisq from './api/bisq';
|
||||
|
||||
class Server {
|
||||
wss: WebSocket.Server;
|
||||
@@ -50,6 +51,10 @@ class Server {
|
||||
fiatConversion.startService();
|
||||
diskCache.loadMempoolCache();
|
||||
|
||||
if (config.BISQ_ENABLED) {
|
||||
bisq.startBisqService();
|
||||
}
|
||||
|
||||
this.server.listen(config.HTTP_PORT, () => {
|
||||
console.log(`Server started on port ${config.HTTP_PORT}`);
|
||||
});
|
||||
@@ -84,6 +89,14 @@ class Server {
|
||||
.get(config.API_ENDPOINT + 'statistics/1y', routes.get1YStatistics.bind(routes))
|
||||
.get(config.API_ENDPOINT + 'backend-info', routes.getBackendInfo)
|
||||
;
|
||||
|
||||
if (config.BISQ_ENABLED) {
|
||||
this.app
|
||||
.get(config.API_ENDPOINT + 'bisq/tx/:txId', routes.getBisqTransaction)
|
||||
.get(config.API_ENDPOINT + 'bisq/block/:hash', routes.getBisqBlock)
|
||||
.get(config.API_ENDPOINT + 'bisq/txs/:index/:length', routes.getBisqTransactions)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -230,3 +230,77 @@ export interface VbytesPerSecond {
|
||||
unixTime: number;
|
||||
vSize: number;
|
||||
}
|
||||
|
||||
export interface BisqBlocks {
|
||||
chainHeight: number;
|
||||
blocks: BisqBlock[];
|
||||
}
|
||||
|
||||
export interface BisqBlock {
|
||||
height: number;
|
||||
time: number;
|
||||
hash: string;
|
||||
previousBlockHash: string;
|
||||
txs: BisqTransaction[];
|
||||
}
|
||||
|
||||
export interface BisqTransaction {
|
||||
txVersion: string;
|
||||
id: string;
|
||||
blockHeight: number;
|
||||
blockHash: string;
|
||||
time: number;
|
||||
inputs: BisqInput[];
|
||||
outputs: BisqOutput[];
|
||||
txType: string;
|
||||
txTypeDisplayString: string;
|
||||
burntFee: number;
|
||||
invalidatedBsq: number;
|
||||
unlockBlockHeight: number;
|
||||
}
|
||||
|
||||
interface BisqInput {
|
||||
spendingTxOutputIndex: number;
|
||||
spendingTxId: string;
|
||||
bsqAmount: number;
|
||||
isVerified: boolean;
|
||||
address: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface BisqOutput {
|
||||
txVersion: string;
|
||||
txId: string;
|
||||
index: number;
|
||||
bsqAmount: number;
|
||||
btcAmount: number;
|
||||
height: number;
|
||||
isVerified: boolean;
|
||||
burntFee: number;
|
||||
invalidatedBsq: number;
|
||||
address: string;
|
||||
scriptPubKey: BisqScriptPubKey;
|
||||
time: any;
|
||||
txType: string;
|
||||
txTypeDisplayString: string;
|
||||
txOutputType: string;
|
||||
txOutputTypeDisplayString: string;
|
||||
lockTime: number;
|
||||
isUnspent: boolean;
|
||||
spentInfo: SpentInfo;
|
||||
opReturn?: string;
|
||||
}
|
||||
|
||||
interface BisqScriptPubKey {
|
||||
addresses: string[];
|
||||
asm: string;
|
||||
hex: string;
|
||||
reqSigs: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface SpentInfo {
|
||||
height: number;
|
||||
inputIndex: number;
|
||||
txId: string;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import feeApi from './api/fee-api';
|
||||
import backendInfo from './api/backend-info';
|
||||
import mempoolBlocks from './api/mempool-blocks';
|
||||
import mempool from './api/mempool';
|
||||
import bisq from './api/bisq';
|
||||
|
||||
class Routes {
|
||||
private cache = {};
|
||||
@@ -85,6 +86,36 @@ class Routes {
|
||||
public getBackendInfo(req: Request, res: Response) {
|
||||
res.send(backendInfo.getBackendInfo());
|
||||
}
|
||||
|
||||
public getBisqTransaction(req: Request, res: Response) {
|
||||
const result = bisq.getTransaction(req.params.txId);
|
||||
if (result) {
|
||||
res.send(result);
|
||||
} else {
|
||||
res.status(404).send('Bisq transaction not found');
|
||||
}
|
||||
}
|
||||
|
||||
public getBisqTransactions(req: Request, res: Response) {
|
||||
const index = parseInt(req.params.index, 10) || 0;
|
||||
const length = parseInt(req.params.length, 10) > 100 ? 100 : parseInt(req.params.length, 10) || 25;
|
||||
const [transactions, count] = bisq.getTransactions(index, length);
|
||||
if (transactions) {
|
||||
res.header('X-Total-Count', count.toString());
|
||||
res.send(transactions);
|
||||
} else {
|
||||
res.status(404).send('Bisq transaction not found');
|
||||
}
|
||||
}
|
||||
|
||||
public getBisqBlock(req: Request, res: Response) {
|
||||
const result = bisq.getBlock(req['hash']);
|
||||
if (result) {
|
||||
res.send(result);
|
||||
} else {
|
||||
res.status(404).send('Bisq block not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new Routes();
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"TESTNET_ENABLED": false,
|
||||
"LIQUID_ENABLED": false,
|
||||
"BISQ_ENABLED": false,
|
||||
"ELCTRS_ITEMS_PER_PAGE": 25,
|
||||
"KEEP_BLOCKS_AMOUNT": 8
|
||||
}
|
||||
@@ -40,6 +40,10 @@
|
||||
"@angular/platform-browser": "~9.1.0",
|
||||
"@angular/platform-browser-dynamic": "~9.1.0",
|
||||
"@angular/router": "~9.1.0",
|
||||
"@fortawesome/angular-fontawesome": "^0.6.1",
|
||||
"@fortawesome/fontawesome-common-types": "^0.2.29",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^6.1.0",
|
||||
"@types/qrcode": "^1.3.4",
|
||||
"bootstrap": "4.5.0",
|
||||
|
||||
@@ -179,6 +179,11 @@ const routes: Routes = [
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'bisq',
|
||||
component: MasterPageComponent,
|
||||
loadChildren: () => import('./bisq/bisq.module').then(m => m.BisqModule)
|
||||
},
|
||||
{
|
||||
path: 'tv',
|
||||
component: TelevisionComponent,
|
||||
|
||||
@@ -37,13 +37,15 @@ export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 7
|
||||
interface Env {
|
||||
TESTNET_ENABLED: boolean;
|
||||
LIQUID_ENABLED: boolean;
|
||||
BISQ_ENABLED: boolean;
|
||||
ELCTRS_ITEMS_PER_PAGE: number;
|
||||
KEEP_BLOCKS_AMOUNT: number;
|
||||
};
|
||||
}
|
||||
|
||||
const defaultEnv: Env = {
|
||||
'TESTNET_ENABLED': false,
|
||||
'LIQUID_ENABLED': false,
|
||||
'BISQ_ENABLED': false,
|
||||
'ELCTRS_ITEMS_PER_PAGE': 25,
|
||||
'KEEP_BLOCKS_AMOUNT': 8
|
||||
};
|
||||
|
||||
@@ -11,15 +11,11 @@ import { AppComponent } from './components/app/app.component';
|
||||
|
||||
import { StartComponent } from './components/start/start.component';
|
||||
import { ElectrsApiService } from './services/electrs-api.service';
|
||||
import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe';
|
||||
import { VbytesPipe } from './pipes/bytes-pipe/vbytes.pipe';
|
||||
import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe';
|
||||
import { TransactionComponent } from './components/transaction/transaction.component';
|
||||
import { TransactionsListComponent } from './components/transactions-list/transactions-list.component';
|
||||
import { AmountComponent } from './components/amount/amount.component';
|
||||
import { StateService } from './services/state.service';
|
||||
import { BlockComponent } from './components/block/block.component';
|
||||
import { ShortenStringPipe } from './pipes/shorten-string-pipe/shorten-string.pipe';
|
||||
import { AddressComponent } from './components/address/address.component';
|
||||
import { SearchFormComponent } from './components/search-form/search-form.component';
|
||||
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
|
||||
@@ -27,7 +23,6 @@ import { WebsocketService } from './services/websocket.service';
|
||||
import { TimeSinceComponent } from './components/time-since/time-since.component';
|
||||
import { AddressLabelsComponent } from './components/address-labels/address-labels.component';
|
||||
import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.component';
|
||||
import { CeilPipe } from './pipes/math-ceil/math-ceil.pipe';
|
||||
import { QrcodeComponent } from './components/qrcode/qrcode.component';
|
||||
import { ClipboardComponent } from './components/clipboard/clipboard.component';
|
||||
import { MasterPageComponent } from './components/master-page/master-page.component';
|
||||
@@ -46,12 +41,12 @@ import { TimespanComponent } from './components/timespan/timespan.component';
|
||||
import { SeoService } from './services/seo.service';
|
||||
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
|
||||
import { AssetComponent } from './components/asset/asset.component';
|
||||
import { ScriptpubkeyTypePipe } from './pipes/scriptpubkey-type-pipe/scriptpubkey-type.pipe';
|
||||
import { AssetsComponent } from './assets/assets.component';
|
||||
import { RelativeUrlPipe } from './pipes/relative-url/relative-url.pipe';
|
||||
import { MinerComponent } from './pipes/miner/miner.component';
|
||||
import { Hex2asciiPipe } from './pipes/hex2ascii/hex2ascii.pipe';
|
||||
import { StatusViewComponent } from './components/status-view/status-view.component';
|
||||
import { MinerComponent } from './components/miner/miner.component';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { BisqTransfersComponent } from './components/bisq-transfers/bisq-transfers.component';
|
||||
import { BisqTransactionDetailsComponent } from './components/bisq-transaction-details/bisq-transaction-details.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -66,11 +61,6 @@ import { StatusViewComponent } from './components/status-view/status-view.compon
|
||||
TransactionComponent,
|
||||
BlockComponent,
|
||||
TransactionsListComponent,
|
||||
BytesPipe,
|
||||
VbytesPipe,
|
||||
WuBytesPipe,
|
||||
CeilPipe,
|
||||
ShortenStringPipe,
|
||||
AddressComponent,
|
||||
AmountComponent,
|
||||
SearchFormComponent,
|
||||
@@ -88,12 +78,11 @@ import { StatusViewComponent } from './components/status-view/status-view.compon
|
||||
FeeDistributionGraphComponent,
|
||||
MempoolGraphComponent,
|
||||
AssetComponent,
|
||||
ScriptpubkeyTypePipe,
|
||||
AssetsComponent,
|
||||
RelativeUrlPipe,
|
||||
MinerComponent,
|
||||
Hex2asciiPipe,
|
||||
StatusViewComponent,
|
||||
BisqTransfersComponent,
|
||||
BisqTransactionDetailsComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@@ -105,12 +94,12 @@ import { StatusViewComponent } from './components/status-view/status-view.compon
|
||||
NgbTooltipModule,
|
||||
NgbPaginationModule,
|
||||
InfiniteScrollModule,
|
||||
SharedModule,
|
||||
],
|
||||
providers: [
|
||||
ElectrsApiService,
|
||||
StateService,
|
||||
WebsocketService,
|
||||
VbytesPipe,
|
||||
AudioService,
|
||||
SeoService,
|
||||
],
|
||||
|
||||
@@ -67,7 +67,7 @@ export class AssetsComponent implements OnInit {
|
||||
});
|
||||
this.assets = this.assets.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
||||
this.assetsCache = this.assets;
|
||||
this.searchForm.controls['searchText'].enable();
|
||||
this.searchForm.get('searchText').enable();
|
||||
this.filteredAssets = this.assets.slice(0, this.itemsPerPage);
|
||||
this.isLoading = false;
|
||||
},
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<div class="container-xl">
|
||||
<h2 style="float: left;">Latest BSQ Transactions</h2>
|
||||
<br>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<table class="table table-borderless table-striped">
|
||||
<thead>
|
||||
<th>Transaction</th>
|
||||
<th>Type</th>
|
||||
<th>Total Sent (BSQ)</th>
|
||||
<th>Outputs</th>
|
||||
<th>Block Height</th>
|
||||
<th>Block Time</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let tx of transactions">
|
||||
<td><a [routerLink]="['/tx/' | relativeUrl, tx.id]" [state]="{ bsqTx: tx }">{{ tx.id | shortenString : 16 }}</a></td>
|
||||
<td><app-bisq-icon class="mr-1" [txType]="tx.txType"></app-bisq-icon> {{ tx.txTypeDisplayString }}</td>
|
||||
<td>{{ calculateTotalOutput(tx.outputs) / 100 | number: '1.2-2' }}</td>
|
||||
<td>{{ tx.outputs.length }}</td>
|
||||
<td><a [routerLink]="['/block/' | relativeUrl, tx.blockHash]">{{ tx.blockHeight }}</a></td>
|
||||
<td>{{ tx.time | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
<ngb-pagination [collectionSize]="totalCount" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="5" [boundaryLinks]="true"></ngb-pagination>
|
||||
|
||||
</div>
|
||||
Whitespace-only changes.
@@ -0,0 +1,50 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { BisqTransaction, BisqOutput } from '../../interfaces/bisq.interfaces';
|
||||
import { Subject } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bisq-transactions',
|
||||
templateUrl: './bisq-transactions.component.html',
|
||||
styleUrls: ['./bisq-transactions.component.scss']
|
||||
})
|
||||
export class BisqTransactionsComponent implements OnInit {
|
||||
transactions: BisqTransaction[];
|
||||
totalCount: number;
|
||||
page = 1;
|
||||
itemsPerPage: number;
|
||||
contentSpace = window.innerHeight - (200 + 200);
|
||||
fiveItemsPxSize = 250;
|
||||
|
||||
pageSubject$ = new Subject<number>();
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10);
|
||||
|
||||
this.pageSubject$
|
||||
.pipe(
|
||||
switchMap((page) => this.apiService.listBisqTransactions$((page - 1) * 10, this.itemsPerPage))
|
||||
)
|
||||
.subscribe((response) => {
|
||||
this.transactions = response.body;
|
||||
this.totalCount = parseInt(response.headers.get('x-total-count'), 10);
|
||||
}, (error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
this.pageSubject$.next(1);
|
||||
}
|
||||
|
||||
pageChange(page: number) {
|
||||
this.pageSubject$.next(page);
|
||||
}
|
||||
|
||||
calculateTotalOutput(outputs: BisqOutput[]): number {
|
||||
return outputs.reduce((acc: number, output: BisqOutput) => acc + output.bsqAmount, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { BisqRoutingModule } from './bisq.routing.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
|
||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
BisqTransactionsComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
BisqRoutingModule,
|
||||
SharedModule,
|
||||
NgbPaginationModule,
|
||||
],
|
||||
})
|
||||
export class BisqModule { }
|
||||
@@ -0,0 +1,58 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { StartComponent } from '../components/start/start.component';
|
||||
import { TransactionComponent } from '../components/transaction/transaction.component';
|
||||
import { BlockComponent } from '../components/block/block.component';
|
||||
import { MempoolBlockComponent } from '../components/mempool-block/mempool-block.component';
|
||||
import { AboutComponent } from '../components/about/about.component';
|
||||
import { AddressComponent } from '../components/address/address.component';
|
||||
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
|
||||
import { StatisticsComponent } from '../components/statistics/statistics.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: BisqTransactionsComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
component: TransactionComponent
|
||||
},
|
||||
{
|
||||
path: 'block/:id',
|
||||
component: BlockComponent
|
||||
},
|
||||
{
|
||||
path: 'mempool-block/:id',
|
||||
component: MempoolBlockComponent
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent,
|
||||
},
|
||||
{
|
||||
path: 'address/:id',
|
||||
children: [],
|
||||
component: AddressComponent
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
redirectTo: ''
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class BisqRoutingModule { }
|
||||
@@ -0,0 +1 @@
|
||||
<fa-icon [icon]="iconProp" [fixedWidth]="true" [ngStyle]="{ 'color': '#' + color }"></fa-icon>
|
||||
Whitespace-only changes.
@@ -0,0 +1,81 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit, Input } from '@angular/core';
|
||||
import { IconPrefix, IconName } from '@fortawesome/fontawesome-common-types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bisq-icon',
|
||||
templateUrl: './bisq-icon.component.html',
|
||||
styleUrls: ['./bisq-icon.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BisqIconComponent implements OnInit {
|
||||
@Input() txType: string;
|
||||
|
||||
iconProp: [IconPrefix, IconName] = ['fas', 'leaf'];
|
||||
color: string;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
switch (this.txType) {
|
||||
case 'UNVERIFIED':
|
||||
this.iconProp[1] = 'question';
|
||||
this.color = 'ffac00';
|
||||
break;
|
||||
case 'INVALID':
|
||||
this.iconProp[1] = 'exclamation-triangle';
|
||||
this.color = 'ff4500';
|
||||
break;
|
||||
case 'GENESIS':
|
||||
this.iconProp[1] = 'rocket';
|
||||
this.color = '25B135';
|
||||
break;
|
||||
case 'TRANSFER_BSQ':
|
||||
this.iconProp[1] = 'retweet';
|
||||
this.color = 'a3a3a3';
|
||||
break;
|
||||
case 'PAY_TRADE_FEE':
|
||||
this.iconProp[1] = 'leaf';
|
||||
this.color = '689f43';
|
||||
break;
|
||||
case 'PROPOSAL':
|
||||
this.iconProp[1] = 'file-alt';
|
||||
this.color = '6c8b3b';
|
||||
break;
|
||||
case 'COMPENSATION_REQUEST':
|
||||
this.iconProp[1] = 'money-bill';
|
||||
this.color = '689f43';
|
||||
break;
|
||||
case 'REIMBURSEMENT_REQUEST':
|
||||
this.iconProp[1] = 'money-bill';
|
||||
this.color = '04a908';
|
||||
break;
|
||||
case 'BLIND_VOTE':
|
||||
this.iconProp[1] = 'eye-slash';
|
||||
this.color = '07579a';
|
||||
break;
|
||||
case 'VOTE_REVEAL':
|
||||
this.iconProp[1] = 'eye';
|
||||
this.color = '4AC5FF';
|
||||
break;
|
||||
case 'LOCKUP':
|
||||
this.iconProp[1] = 'lock';
|
||||
this.color = '0056c4';
|
||||
break;
|
||||
case 'UNLOCK':
|
||||
this.iconProp[1] = 'lock-open';
|
||||
this.color = '1d965f';
|
||||
break;
|
||||
case 'ASSET_LISTING_FEE':
|
||||
this.iconProp[1] = 'file-alt';
|
||||
this.color = '6c8b3b';
|
||||
break;
|
||||
case 'PROOF_OF_BURN':
|
||||
this.iconProp[1] = 'file-alt';
|
||||
this.color = '6c8b3b';
|
||||
break;
|
||||
default:
|
||||
this.iconProp[1] = 'question';
|
||||
this.color = 'ffac00';
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<div class="box">
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Inputs</td>
|
||||
<td>{{ totalInput / 100 | number: '1.2-2' }} BSQ</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Outputs</td>
|
||||
<td>{{ totalOutput / 100 | number: '1.2-2' }} BSQ</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Burnt</td>
|
||||
<td>{{ tx.burntFee / 100 | number: '1.2-2' }} BSQ</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Issuance</td>
|
||||
<td>{{ totalIssued / 100 | number: '1.2-2' }} BSQ</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Type</td>
|
||||
<td><app-bisq-icon class="mr-1" [txType]="tx.txType"></app-bisq-icon> {{ tx.txTypeDisplayString }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{{ tx.txVersion }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Whitespace-only changes.
+26
@@ -0,0 +1,26 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core';
|
||||
import { BisqTransaction } from 'src/app/interfaces/bisq.interfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bisq-transaction-details',
|
||||
templateUrl: './bisq-transaction-details.component.html',
|
||||
styleUrls: ['./bisq-transaction-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BisqTransactionDetailsComponent implements OnChanges {
|
||||
@Input() tx: BisqTransaction;
|
||||
|
||||
totalInput: number;
|
||||
totalOutput: number;
|
||||
totalIssued: number;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnChanges() {
|
||||
this.totalInput = this.tx.inputs.filter((input) => input.isVerified).reduce((acc, input) => acc + input.bsqAmount, 0);
|
||||
this.totalOutput = this.tx.outputs.filter((output) => output.isVerified).reduce((acc, output) => acc + output.bsqAmount, 0);
|
||||
this.totalIssued = this.tx.outputs
|
||||
.filter((output) => output.isVerified && output.txOutputType === 'ISSUANCE_CANDIDATE_OUTPUT')
|
||||
.reduce((acc, output) => acc + output.bsqAmount, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<div class="header-bg box">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
|
||||
<tbody>
|
||||
<ng-template ngFor let-input [ngForOf]="tx.inputs" [ngForTrackBy]="trackByIndexFn">
|
||||
<tr *ngIf="input.isVerified">
|
||||
<td class="arrow-td">
|
||||
<ng-template [ngIf]="input.spendingTxId === null" [ngIfElse]="hasPreoutput">
|
||||
<i class="arrow grey"></i>
|
||||
</ng-template>
|
||||
<ng-template #hasPreoutput>
|
||||
<a [routerLink]="['/tx/' | relativeUrl, input.spendingTxId]">
|
||||
<i class="arrow red"></i>
|
||||
</a>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td>
|
||||
<a [routerLink]="['/address/' | relativeUrl, input.address]" title="{{ input.address }}">
|
||||
<span class="d-block d-lg-none">B{{ input.address | shortenString : 16 }}</span>
|
||||
<span class="d-none d-lg-block">B{{ input.address | shortenString : 35 }}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right nowrap">
|
||||
{{ input.bsqAmount / 100 | number: '1.2-2' }} BSQ
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="w-100 d-block d-md-none"></div>
|
||||
<div class="col mobile-bottomcol">
|
||||
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
|
||||
<tbody>
|
||||
<ng-template ngFor let-output [ngForOf]="tx.outputs" [ngForTrackBy]="trackByIndexFn">
|
||||
<tr *ngIf="output.isVerified && output.opReturn === undefined">
|
||||
<td>
|
||||
<a [routerLink]="['/address/' | relativeUrl, output.address]" title="{{ output.address }}">
|
||||
<span class="d-block d-lg-none">B{{ output.address | shortenString : 16 }}</span>
|
||||
<span class="d-none d-lg-block">B{{ output.address | shortenString : 35 }}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right nowrap">
|
||||
{{ output.bsqAmount / 100 | number: '1.2-2' }} BSQ
|
||||
</td>
|
||||
<td class="pl-1 arrow-td">
|
||||
<i *ngIf="!output.spentInfo; else spent" class="arrow green"></i>
|
||||
<ng-template #spent>
|
||||
<a [routerLink]="['/tx/' | relativeUrl, output.spentInfo.txId]"><i class="arrow red"></i></a>
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,84 @@
|
||||
.arrow-td {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
display: inline-block!important;
|
||||
position: relative;
|
||||
width: 14px;
|
||||
height: 22px;
|
||||
box-sizing: content-box
|
||||
}
|
||||
|
||||
.arrow:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: calc(-1*30px/3);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 6.66px solid transparent;
|
||||
border-bottom: 6.66px solid transparent
|
||||
}
|
||||
|
||||
.arrow:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: calc(30px/6);
|
||||
width: calc(30px/3);
|
||||
height: calc(20px/3);
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.arrow.green:before {
|
||||
border-left: 10px solid #28a745;
|
||||
}
|
||||
.arrow.green:after {
|
||||
background-color:#28a745;
|
||||
}
|
||||
|
||||
.arrow.red:before {
|
||||
border-left: 10px solid #dc3545;
|
||||
}
|
||||
.arrow.red:after {
|
||||
background-color:#dc3545;
|
||||
}
|
||||
|
||||
.arrow.grey:before {
|
||||
border-left: 10px solid #6c757d;
|
||||
}
|
||||
|
||||
.arrow.grey:after {
|
||||
background-color:#6c757d;
|
||||
}
|
||||
|
||||
.scriptmessage {
|
||||
max-width: 280px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.scriptmessage.longer {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.mobile-bottomcol {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.scriptmessage {
|
||||
max-width: 90px !important;
|
||||
}
|
||||
.scriptmessage.longer {
|
||||
max-width: 280px !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { BisqTransaction } from 'src/app/interfaces/bisq.interfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bisq-transfers',
|
||||
templateUrl: './bisq-transfers.component.html',
|
||||
styleUrls: ['./bisq-transfers.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class BisqTransfersComponent {
|
||||
@Input() tx: BisqTransaction;
|
||||
|
||||
constructor() { }
|
||||
|
||||
trackByIndexFn(index: number) {
|
||||
return index;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
|
||||
gradientColors = {
|
||||
'': ['#9339f4', '#105fb0'],
|
||||
bisq: ['#9339f4', '#105fb0'],
|
||||
liquid: ['#116761', '#183550'],
|
||||
testnet: ['#1d486f', '#183550'],
|
||||
};
|
||||
|
||||
@@ -17,6 +17,5 @@ export class BlockchainComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.stateService.blocks$.subscribe(() => this.isLoading = false);
|
||||
this.stateService.networkChanged$.subscribe(() => this.isLoading = true);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
||||
<a class="navbar-brand" routerLink="/" style="position: relative;">
|
||||
<a class="navbar-brand" [routerLink]="['/' | relativeUrl]" style="position: relative;">
|
||||
<img src="./resources/mempool-logo.png" height="35" width="140" class="logo" [ngStyle]="{'opacity': connectionState === 2 ? 1 : 0.5 }">
|
||||
<div class="badge badge-warning connection-badge" *ngIf="connectionState === 0">Offline</div>
|
||||
<div class="badge badge-warning connection-badge" style="left: 30px;" *ngIf="connectionState === 1">Reconnecting...</div>
|
||||
</a>
|
||||
|
||||
<div class="btn-group" style="margin-right: 16px;" *ngIf="env.TESTNET_ENABLED || env.LIQUID_ENABLED">
|
||||
<div class="btn-group" style="margin-right: 16px;" *ngIf="env.TESTNET_ENABLED || env.LIQUID_ENABLED || env.BISQ_ENABLED">
|
||||
<button type="button" (click)="networkDropdownHidden = !networkDropdownHidden" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu" [class.d-block]="!networkDropdownHidden">
|
||||
<a class="dropdown-item mainnet" [class.active]="network === ''" routerLink="/"><img src="./resources/bitcoin-logo.png" style="width: 35.5px;"> Mainnet</a>
|
||||
<a class="dropdown-item mainnet" routerLink="/"><img src="./resources/bitcoin-logo.png" style="width: 35.5px;"> Mainnet</a>
|
||||
<a *ngIf="env.LIQUID_ENABLED" class="dropdown-item liquid" [class.active]="network === 'liquid'" routerLink="/liquid"><img src="./resources/liquid-logo.png" style="width: 35.5px;"> Liquid</a>
|
||||
<a *ngIf="env.BISQ_ENABLED" class="dropdown-item mainnet" [class.active]="network === 'bisq'" routerLink="/bisq"><img src="./resources/bisq-logo.png" style="width: 35.5px;"> Bisq</a>
|
||||
<a *ngIf="env.TESTNET_ENABLED" class="dropdown-item testnet" [class.active]="network === 'testnet'" routerLink="/testnet"><img src="./resources/testnet-logo.png" style="width: 35.5px;"> Testnet</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,14 +37,6 @@ export class MasterPageComponent implements OnInit {
|
||||
this.stateService.networkChanged$
|
||||
.subscribe((network) => {
|
||||
this.network = network;
|
||||
|
||||
if (network === 'testnet') {
|
||||
this.tvViewRoute = '/testnet-tv';
|
||||
} else if (network === 'liquid') {
|
||||
this.tvViewRoute = '/liquid-tv';
|
||||
} else {
|
||||
this.tvViewRoute = '/tv';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
|
||||
import { formatDate } from '@angular/common';
|
||||
import { VbytesPipe } from 'src/app/pipes/bytes-pipe/vbytes.pipe';
|
||||
import { VbytesPipe } from 'src/app/shared/pipes/bytes-pipe/vbytes.pipe';
|
||||
import * as Chartist from 'chartist';
|
||||
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -148,6 +148,21 @@
|
||||
|
||||
<br>
|
||||
|
||||
<ng-template [ngIf]="bisqTx">
|
||||
<h2>BSQ Information</h2>
|
||||
|
||||
<app-bisq-transaction-details [tx]="bisqTx"></app-bisq-transaction-details>
|
||||
|
||||
<br>
|
||||
|
||||
<h2>BSQ transfers</h2>
|
||||
|
||||
<app-bisq-transfers [tx]="bisqTx"></app-bisq-transfers>
|
||||
|
||||
<br>
|
||||
|
||||
</ng-template>
|
||||
|
||||
<h2>Inputs & Outputs</h2>
|
||||
|
||||
<app-transactions-list [transactions]="[tx]" [transactionPage]="true"></app-transactions-list>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AudioService } from 'src/app/services/audio.service';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { calcSegwitFeeGains } from 'src/app/bitcoin.utils';
|
||||
import { BisqTransaction } from 'src/app/interfaces/bisq.interfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transaction',
|
||||
@@ -37,6 +38,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
isRbfTransaction: boolean;
|
||||
rbfTransaction: undefined | Transaction;
|
||||
bisqTx: BisqTransaction;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -90,6 +92,10 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
this.segwitGains = calcSegwitFeeGains(tx);
|
||||
this.isRbfTransaction = tx.vin.some((v) => v.sequence < 0xfffffffe);
|
||||
|
||||
if (this.network === 'bisq') {
|
||||
this.loadBisqTransaction();
|
||||
}
|
||||
|
||||
if (!tx.status.confirmed) {
|
||||
this.websocketService.startTrackTransaction(tx.txid);
|
||||
|
||||
@@ -133,6 +139,17 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
.subscribe((rbfTransaction) => this.rbfTransaction = rbfTransaction);
|
||||
}
|
||||
|
||||
loadBisqTransaction() {
|
||||
if (history.state.bsqTx) {
|
||||
this.bisqTx = history.state.bsqTx;
|
||||
} else {
|
||||
this.apiService.getBisqTransaction$(this.txId)
|
||||
.subscribe((tx) => {
|
||||
this.bisqTx = tx;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadElectrsTransactionError(error: any): Observable<any> {
|
||||
if (error.status === 404 && /^[a-fA-F0-9]{64}$/.test(this.txId)) {
|
||||
this.websocketService.startMultiTrackTransaction(this.txId);
|
||||
@@ -204,6 +221,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
this.waitingForTransaction = false;
|
||||
this.isLoadingTx = true;
|
||||
this.rbfTransaction = undefined;
|
||||
this.bisqTx = undefined;
|
||||
this.transactionTime = -1;
|
||||
document.body.scrollTo(0, 0);
|
||||
this.leaveTransaction();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="col">
|
||||
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
|
||||
<tbody>
|
||||
<tr *ngFor="let vin of getFilteredTxVin(tx)">
|
||||
<tr *ngFor="let vin of getFilteredTxVin(tx); trackBy: trackByIndexFn">
|
||||
<td class="arrow-td">
|
||||
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
|
||||
<i class="arrow grey"></i>
|
||||
@@ -73,7 +73,7 @@
|
||||
<div class="col mobile-bottomcol">
|
||||
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
|
||||
<tbody>
|
||||
<tr *ngFor="let vout of getFilteredTxVout(tx); let vindex = index;">
|
||||
<tr *ngFor="let vout of getFilteredTxVout(tx); let vindex = index; trackBy: trackByIndexFn">
|
||||
<td>
|
||||
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
||||
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
|
||||
|
||||
@@ -109,4 +109,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
getFilteredTxVout(tx: Transaction) {
|
||||
return tx.vout.slice(0, tx['@voutLength']);
|
||||
}
|
||||
|
||||
trackByIndexFn(index: number) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
export interface BisqBlocks {
|
||||
chainHeight: number;
|
||||
blocks: BisqBlock[];
|
||||
}
|
||||
|
||||
export interface BisqBlock {
|
||||
height: number;
|
||||
time: number;
|
||||
hash: string;
|
||||
previousBlockHash: string;
|
||||
txs: BisqTransaction[];
|
||||
}
|
||||
|
||||
export interface BisqTransaction {
|
||||
txVersion: string;
|
||||
id: string;
|
||||
blockHeight: number;
|
||||
blockHash: string;
|
||||
time: number;
|
||||
inputs: BisqInput[];
|
||||
outputs: BisqOutput[];
|
||||
txType: string;
|
||||
txTypeDisplayString: string;
|
||||
burntFee: number;
|
||||
invalidatedBsq: number;
|
||||
unlockBlockHeight: number;
|
||||
}
|
||||
|
||||
interface BisqInput {
|
||||
spendingTxOutputIndex: number;
|
||||
spendingTxId: string;
|
||||
bsqAmount: number;
|
||||
isVerified: boolean;
|
||||
address: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface BisqOutput {
|
||||
txVersion: string;
|
||||
txId: string;
|
||||
index: number;
|
||||
bsqAmount: number;
|
||||
btcAmount: number;
|
||||
height: number;
|
||||
isVerified: boolean;
|
||||
burntFee: number;
|
||||
invalidatedBsq: number;
|
||||
address: string;
|
||||
scriptPubKey: BisqScriptPubKey;
|
||||
spentInfo?: SpentInfo;
|
||||
time: any;
|
||||
txType: string;
|
||||
txTypeDisplayString: string;
|
||||
txOutputType: string;
|
||||
txOutputTypeDisplayString: string;
|
||||
lockTime: number;
|
||||
isUnspent: boolean;
|
||||
opReturn?: string;
|
||||
}
|
||||
|
||||
interface BisqScriptPubKey {
|
||||
addresses: string[];
|
||||
asm: string;
|
||||
hex: string;
|
||||
reqSigs: number;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface SpentInfo {
|
||||
height: number;
|
||||
inputIndex: number;
|
||||
txId: string;
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { Observable } from 'rxjs';
|
||||
import { StateService } from './state.service';
|
||||
import { BisqTransaction, BisqBlock } from '../interfaces/bisq.interfaces';
|
||||
|
||||
const API_BASE_URL = '/api{network}/v1';
|
||||
|
||||
@@ -18,6 +19,9 @@ export class ApiService {
|
||||
) {
|
||||
this.apiBaseUrl = API_BASE_URL.replace('{network}', '');
|
||||
this.stateService.networkChanged$.subscribe((network) => {
|
||||
if (network === 'bisq') {
|
||||
network = '';
|
||||
}
|
||||
this.apiBaseUrl = API_BASE_URL.replace('{network}', network ? '/' + network : '');
|
||||
});
|
||||
}
|
||||
@@ -57,4 +61,16 @@ export class ApiService {
|
||||
});
|
||||
return this.httpClient.get<number[]>(this.apiBaseUrl + '/transaction-times', { params });
|
||||
}
|
||||
|
||||
getBisqTransaction$(txId: string): Observable<BisqTransaction> {
|
||||
return this.httpClient.get<BisqTransaction>(this.apiBaseUrl + '/bisq/tx/' + txId);
|
||||
}
|
||||
|
||||
listBisqTransactions$(start: number, length: number): Observable<HttpResponse<BisqTransaction[]>> {
|
||||
return this.httpClient.get<BisqTransaction[]>(this.apiBaseUrl + `/bisq/txs/${start}/${length}`, { observe: 'response' });
|
||||
}
|
||||
|
||||
getBisqBlock$(hash: string): Observable<BisqBlock> {
|
||||
return this.httpClient.get<BisqBlock>(this.apiBaseUrl + '/bisq/block/' + hash);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ export class ElectrsApiService {
|
||||
) {
|
||||
this.apiBaseUrl = API_BASE_URL;
|
||||
this.stateService.networkChanged$.subscribe((network) => {
|
||||
if (network === 'bisq') {
|
||||
network = '';
|
||||
}
|
||||
this.apiBaseUrl = API_BASE_URL + '/' + network;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ export class StateService {
|
||||
this.networkChanged$.next('testnet');
|
||||
}
|
||||
return;
|
||||
case 'bisq':
|
||||
if (this.network !== 'bisq') {
|
||||
this.network = 'bisq';
|
||||
this.networkChanged$.next('bisq');
|
||||
}
|
||||
return;
|
||||
default:
|
||||
if (this.network !== '') {
|
||||
this.network = '';
|
||||
|
||||
@@ -29,11 +29,14 @@ export class WebsocketService {
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
) {
|
||||
this.network = this.stateService.network;
|
||||
this.network = this.stateService.network === 'bisq' ? '' : this.stateService.network;
|
||||
this.websocketSubject = webSocket<WebsocketResponse | any>(WEB_SOCKET_URL + '/' + this.network);
|
||||
this.startSubscription();
|
||||
|
||||
this.stateService.networkChanged$.subscribe((network) => {
|
||||
if (network === 'bisq') {
|
||||
network = '';
|
||||
}
|
||||
if (network === this.network) {
|
||||
return;
|
||||
}
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,60 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { VbytesPipe } from './pipes/bytes-pipe/vbytes.pipe';
|
||||
import { ShortenStringPipe } from './pipes/shorten-string-pipe/shorten-string.pipe';
|
||||
import { CeilPipe } from './pipes/math-ceil/math-ceil.pipe';
|
||||
import { Hex2asciiPipe } from './pipes/hex2ascii/hex2ascii.pipe';
|
||||
import { RelativeUrlPipe } from './pipes/relative-url/relative-url.pipe';
|
||||
import { ScriptpubkeyTypePipe } from './pipes/scriptpubkey-type-pipe/scriptpubkey-type.pipe';
|
||||
import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe';
|
||||
import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe';
|
||||
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
|
||||
import { BisqIconComponent } from '../components/bisq-icon/bisq-icon.component';
|
||||
import { faLeaf, faQuestion, faExclamationTriangle, faRocket, faRetweet, faFileAlt, faMoneyBill, faEye, faEyeSlash, faLock, faLockOpen } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ScriptpubkeyTypePipe,
|
||||
RelativeUrlPipe,
|
||||
Hex2asciiPipe,
|
||||
BytesPipe,
|
||||
VbytesPipe,
|
||||
WuBytesPipe,
|
||||
CeilPipe,
|
||||
ShortenStringPipe,
|
||||
BisqIconComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FontAwesomeModule,
|
||||
],
|
||||
providers: [
|
||||
VbytesPipe,
|
||||
],
|
||||
exports: [
|
||||
ScriptpubkeyTypePipe,
|
||||
RelativeUrlPipe,
|
||||
Hex2asciiPipe,
|
||||
BytesPipe,
|
||||
VbytesPipe,
|
||||
WuBytesPipe,
|
||||
CeilPipe,
|
||||
ShortenStringPipe,
|
||||
BisqIconComponent,
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
constructor(library: FaIconLibrary) {
|
||||
library.addIcons(faQuestion);
|
||||
library.addIcons(faExclamationTriangle);
|
||||
library.addIcons(faRocket);
|
||||
library.addIcons(faRetweet);
|
||||
library.addIcons(faLeaf);
|
||||
library.addIcons(faFileAlt);
|
||||
library.addIcons(faMoneyBill);
|
||||
library.addIcons(faEye);
|
||||
library.addIcons(faEyeSlash);
|
||||
library.addIcons(faLock);
|
||||
library.addIcons(faLockOpen);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
@@ -1189,6 +1189,30 @@
|
||||
lodash "^4.17.13"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@fortawesome/angular-fontawesome@^0.6.1":
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.6.1.tgz#1ebe5db16bfdd4be44bdde61f78c760eb4e219fa"
|
||||
integrity sha512-ARQjtRuT+ZskzJDJKPwuiGO3+7nS0iyNLU/uHVJHfG4LwGJxwVIGldwg1SU957sra0Z0OtWEajHMhiS4vB9LwQ==
|
||||
|
||||
"@fortawesome/fontawesome-common-types@^0.2.29":
|
||||
version "0.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.29.tgz#e1a456b643237462d390304cab6975ff3fd68397"
|
||||
integrity sha512-cY+QfDTbZ7XVxzx7jxbC98Oxr/zc7R2QpTLqTxqlfyXDrAJjzi/xUIqAUsygELB62JIrbsWxtSRhayKFkGI7MA==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@^1.2.28":
|
||||
version "1.2.29"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.29.tgz#34ef32824664534f9e4ef37982ebf286b899a189"
|
||||
integrity sha512-xmPmP2t8qrdo8RyKihTkGb09RnZoc+7HFBCnr0/6ZhStdGDSLeEd7ajV181+2W29NWIFfylO13rU+s3fpy3cnA==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.29"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^5.13.0":
|
||||
version "5.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.1.tgz#010a846b718a0f110b3cd137d072639b4e8bd41a"
|
||||
integrity sha512-LQH/0L1p4+rqtoSHa9qFYR84hpuRZKqaQ41cfBQx8b68p21zoWSekTAeA54I/2x9VlCHDLFlG74Nmdg4iTPQOg==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "^0.2.29"
|
||||
|
||||
"@istanbuljs/schema@^0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
|
||||
|
||||
Reference in new issue
Block a user