WIP: Bisq DAO support. Transactions list and details.
This commit is contained in:
parent
2ebdb27dcb
commit
c21dad88bf
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@ -43,3 +43,4 @@ testem.log
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
cache.json
|
cache.json
|
||||||
|
blocks.json
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"INITIAL_BLOCK_AMOUNT": 8,
|
"INITIAL_BLOCK_AMOUNT": 8,
|
||||||
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
||||||
"ELECTRS_API_URL": "https://www.blockstream.info/testnet/api",
|
"ELECTRS_API_URL": "https://www.blockstream.info/testnet/api",
|
||||||
|
"BISQ_ENABLED": false,
|
||||||
"SSL": false,
|
"SSL": false,
|
||||||
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
||||||
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
|
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
|
||||||
|
78
backend/src/api/bisq.ts
Normal file
78
backend/src/api/bisq.ts
Normal file
@ -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.`);
|
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.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);
|
transactions.sort((a, b) => b.feePerVsize - a.feePerVsize);
|
||||||
block.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.feePerVsize)) : 0;
|
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.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions, 8, 1) : [0, 0];
|
||||||
block.coinbaseTx = this.stripCoinbaseTransaction(transactions[0]);
|
|
||||||
|
|
||||||
this.blocks.push(block);
|
this.blocks.push(block);
|
||||||
if (this.blocks.length > config.KEEP_BLOCK_AMOUNT) {
|
if (this.blocks.length > config.KEEP_BLOCK_AMOUNT) {
|
||||||
|
@ -35,6 +35,9 @@ class Mempool {
|
|||||||
|
|
||||||
public setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
|
public setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
|
||||||
this.mempoolCache = mempoolData;
|
this.mempoolCache = mempoolData;
|
||||||
|
if (this.mempoolChangedCallback) {
|
||||||
|
this.mempoolChangedCallback(this.mempoolCache, [], []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateMemPoolInfo() {
|
public async updateMemPoolInfo() {
|
||||||
|
@ -14,6 +14,7 @@ import diskCache from './api/disk-cache';
|
|||||||
import statistics from './api/statistics';
|
import statistics from './api/statistics';
|
||||||
import websocketHandler from './api/websocket-handler';
|
import websocketHandler from './api/websocket-handler';
|
||||||
import fiatConversion from './api/fiat-conversion';
|
import fiatConversion from './api/fiat-conversion';
|
||||||
|
import bisq from './api/bisq';
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
wss: WebSocket.Server;
|
wss: WebSocket.Server;
|
||||||
@ -50,6 +51,10 @@ class Server {
|
|||||||
fiatConversion.startService();
|
fiatConversion.startService();
|
||||||
diskCache.loadMempoolCache();
|
diskCache.loadMempoolCache();
|
||||||
|
|
||||||
|
if (config.BISQ_ENABLED) {
|
||||||
|
bisq.startBisqService();
|
||||||
|
}
|
||||||
|
|
||||||
this.server.listen(config.HTTP_PORT, () => {
|
this.server.listen(config.HTTP_PORT, () => {
|
||||||
console.log(`Server started on port ${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 + 'statistics/1y', routes.get1YStatistics.bind(routes))
|
||||||
.get(config.API_ENDPOINT + 'backend-info', routes.getBackendInfo)
|
.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;
|
unixTime: number;
|
||||||
vSize: 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 backendInfo from './api/backend-info';
|
||||||
import mempoolBlocks from './api/mempool-blocks';
|
import mempoolBlocks from './api/mempool-blocks';
|
||||||
import mempool from './api/mempool';
|
import mempool from './api/mempool';
|
||||||
|
import bisq from './api/bisq';
|
||||||
|
|
||||||
class Routes {
|
class Routes {
|
||||||
private cache = {};
|
private cache = {};
|
||||||
@ -85,6 +86,36 @@ class Routes {
|
|||||||
public getBackendInfo(req: Request, res: Response) {
|
public getBackendInfo(req: Request, res: Response) {
|
||||||
res.send(backendInfo.getBackendInfo());
|
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();
|
export default new Routes();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"TESTNET_ENABLED": false,
|
"TESTNET_ENABLED": false,
|
||||||
"LIQUID_ENABLED": false,
|
"LIQUID_ENABLED": false,
|
||||||
|
"BISQ_ENABLED": false,
|
||||||
"ELCTRS_ITEMS_PER_PAGE": 25,
|
"ELCTRS_ITEMS_PER_PAGE": 25,
|
||||||
"KEEP_BLOCKS_AMOUNT": 8
|
"KEEP_BLOCKS_AMOUNT": 8
|
||||||
}
|
}
|
@ -40,6 +40,10 @@
|
|||||||
"@angular/platform-browser": "~9.1.0",
|
"@angular/platform-browser": "~9.1.0",
|
||||||
"@angular/platform-browser-dynamic": "~9.1.0",
|
"@angular/platform-browser-dynamic": "~9.1.0",
|
||||||
"@angular/router": "~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",
|
"@ng-bootstrap/ng-bootstrap": "^6.1.0",
|
||||||
"@types/qrcode": "^1.3.4",
|
"@types/qrcode": "^1.3.4",
|
||||||
"bootstrap": "4.5.0",
|
"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',
|
path: 'tv',
|
||||||
component: TelevisionComponent,
|
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 {
|
interface Env {
|
||||||
TESTNET_ENABLED: boolean;
|
TESTNET_ENABLED: boolean;
|
||||||
LIQUID_ENABLED: boolean;
|
LIQUID_ENABLED: boolean;
|
||||||
|
BISQ_ENABLED: boolean;
|
||||||
ELCTRS_ITEMS_PER_PAGE: number;
|
ELCTRS_ITEMS_PER_PAGE: number;
|
||||||
KEEP_BLOCKS_AMOUNT: number;
|
KEEP_BLOCKS_AMOUNT: number;
|
||||||
};
|
}
|
||||||
|
|
||||||
const defaultEnv: Env = {
|
const defaultEnv: Env = {
|
||||||
'TESTNET_ENABLED': false,
|
'TESTNET_ENABLED': false,
|
||||||
'LIQUID_ENABLED': false,
|
'LIQUID_ENABLED': false,
|
||||||
|
'BISQ_ENABLED': false,
|
||||||
'ELCTRS_ITEMS_PER_PAGE': 25,
|
'ELCTRS_ITEMS_PER_PAGE': 25,
|
||||||
'KEEP_BLOCKS_AMOUNT': 8
|
'KEEP_BLOCKS_AMOUNT': 8
|
||||||
};
|
};
|
||||||
|
@ -11,15 +11,11 @@ import { AppComponent } from './components/app/app.component';
|
|||||||
|
|
||||||
import { StartComponent } from './components/start/start.component';
|
import { StartComponent } from './components/start/start.component';
|
||||||
import { ElectrsApiService } from './services/electrs-api.service';
|
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 { TransactionComponent } from './components/transaction/transaction.component';
|
||||||
import { TransactionsListComponent } from './components/transactions-list/transactions-list.component';
|
import { TransactionsListComponent } from './components/transactions-list/transactions-list.component';
|
||||||
import { AmountComponent } from './components/amount/amount.component';
|
import { AmountComponent } from './components/amount/amount.component';
|
||||||
import { StateService } from './services/state.service';
|
import { StateService } from './services/state.service';
|
||||||
import { BlockComponent } from './components/block/block.component';
|
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 { AddressComponent } from './components/address/address.component';
|
||||||
import { SearchFormComponent } from './components/search-form/search-form.component';
|
import { SearchFormComponent } from './components/search-form/search-form.component';
|
||||||
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.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 { TimeSinceComponent } from './components/time-since/time-since.component';
|
||||||
import { AddressLabelsComponent } from './components/address-labels/address-labels.component';
|
import { AddressLabelsComponent } from './components/address-labels/address-labels.component';
|
||||||
import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.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 { QrcodeComponent } from './components/qrcode/qrcode.component';
|
||||||
import { ClipboardComponent } from './components/clipboard/clipboard.component';
|
import { ClipboardComponent } from './components/clipboard/clipboard.component';
|
||||||
import { MasterPageComponent } from './components/master-page/master-page.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 { SeoService } from './services/seo.service';
|
||||||
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
|
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
|
||||||
import { AssetComponent } from './components/asset/asset.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 { 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 { 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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -66,11 +61,6 @@ import { StatusViewComponent } from './components/status-view/status-view.compon
|
|||||||
TransactionComponent,
|
TransactionComponent,
|
||||||
BlockComponent,
|
BlockComponent,
|
||||||
TransactionsListComponent,
|
TransactionsListComponent,
|
||||||
BytesPipe,
|
|
||||||
VbytesPipe,
|
|
||||||
WuBytesPipe,
|
|
||||||
CeilPipe,
|
|
||||||
ShortenStringPipe,
|
|
||||||
AddressComponent,
|
AddressComponent,
|
||||||
AmountComponent,
|
AmountComponent,
|
||||||
SearchFormComponent,
|
SearchFormComponent,
|
||||||
@ -88,12 +78,11 @@ import { StatusViewComponent } from './components/status-view/status-view.compon
|
|||||||
FeeDistributionGraphComponent,
|
FeeDistributionGraphComponent,
|
||||||
MempoolGraphComponent,
|
MempoolGraphComponent,
|
||||||
AssetComponent,
|
AssetComponent,
|
||||||
ScriptpubkeyTypePipe,
|
|
||||||
AssetsComponent,
|
AssetsComponent,
|
||||||
RelativeUrlPipe,
|
|
||||||
MinerComponent,
|
MinerComponent,
|
||||||
Hex2asciiPipe,
|
|
||||||
StatusViewComponent,
|
StatusViewComponent,
|
||||||
|
BisqTransfersComponent,
|
||||||
|
BisqTransactionDetailsComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -105,12 +94,12 @@ import { StatusViewComponent } from './components/status-view/status-view.compon
|
|||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
NgbPaginationModule,
|
NgbPaginationModule,
|
||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
|
SharedModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ElectrsApiService,
|
ElectrsApiService,
|
||||||
StateService,
|
StateService,
|
||||||
WebsocketService,
|
WebsocketService,
|
||||||
VbytesPipe,
|
|
||||||
AudioService,
|
AudioService,
|
||||||
SeoService,
|
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.assets = this.assets.sort((a: any, b: any) => a.name.localeCompare(b.name));
|
||||||
this.assetsCache = this.assets;
|
this.assetsCache = this.assets;
|
||||||
this.searchForm.controls['searchText'].enable();
|
this.searchForm.get('searchText').enable();
|
||||||
this.filteredAssets = this.assets.slice(0, this.itemsPerPage);
|
this.filteredAssets = this.assets.slice(0, this.itemsPerPage);
|
||||||
this.isLoading = false;
|
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>
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
19
frontend/src/app/bisq/bisq.module.ts
Normal file
19
frontend/src/app/bisq/bisq.module.ts
Normal file
@ -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 { }
|
58
frontend/src/app/bisq/bisq.routing.module.ts
Normal file
58
frontend/src/app/bisq/bisq.routing.module.ts
Normal file
@ -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>
|
81
frontend/src/app/components/bisq-icon/bisq-icon.component.ts
Normal file
81
frontend/src/app/components/bisq-icon/bisq-icon.component.ts
Normal file
@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
@ -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 = {
|
gradientColors = {
|
||||||
'': ['#9339f4', '#105fb0'],
|
'': ['#9339f4', '#105fb0'],
|
||||||
|
bisq: ['#9339f4', '#105fb0'],
|
||||||
liquid: ['#116761', '#183550'],
|
liquid: ['#116761', '#183550'],
|
||||||
testnet: ['#1d486f', '#183550'],
|
testnet: ['#1d486f', '#183550'],
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,5 @@ export class BlockchainComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.stateService.blocks$.subscribe(() => this.isLoading = false);
|
this.stateService.blocks$.subscribe(() => this.isLoading = false);
|
||||||
this.stateService.networkChanged$.subscribe(() => this.isLoading = true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
<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 }">
|
<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" *ngIf="connectionState === 0">Offline</div>
|
||||||
<div class="badge badge-warning connection-badge" style="left: 30px;" *ngIf="connectionState === 1">Reconnecting...</div>
|
<div class="badge badge-warning connection-badge" style="left: 30px;" *ngIf="connectionState === 1">Reconnecting...</div>
|
||||||
</a>
|
</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">
|
<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>
|
<span class="sr-only">Toggle Dropdown</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu" [class.d-block]="!networkDropdownHidden">
|
<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.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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,14 +37,6 @@ export class MasterPageComponent implements OnInit {
|
|||||||
this.stateService.networkChanged$
|
this.stateService.networkChanged$
|
||||||
.subscribe((network) => {
|
.subscribe((network) => {
|
||||||
this.network = 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 { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core';
|
||||||
import { formatDate } from '@angular/common';
|
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 * as Chartist from 'chartist';
|
||||||
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
|
import { OptimizedMempoolStats } from 'src/app/interfaces/node-api.interface';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
import { StateService } from 'src/app/services/state.service';
|
||||||
|
@ -148,6 +148,21 @@
|
|||||||
|
|
||||||
<br>
|
<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>
|
<h2>Inputs & Outputs</h2>
|
||||||
|
|
||||||
<app-transactions-list [transactions]="[tx]" [transactionPage]="true"></app-transactions-list>
|
<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 { ApiService } from 'src/app/services/api.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { calcSegwitFeeGains } from 'src/app/bitcoin.utils';
|
import { calcSegwitFeeGains } from 'src/app/bitcoin.utils';
|
||||||
|
import { BisqTransaction } from 'src/app/interfaces/bisq.interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transaction',
|
selector: 'app-transaction',
|
||||||
@ -37,6 +38,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
isRbfTransaction: boolean;
|
isRbfTransaction: boolean;
|
||||||
rbfTransaction: undefined | Transaction;
|
rbfTransaction: undefined | Transaction;
|
||||||
|
bisqTx: BisqTransaction;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -90,6 +92,10 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
this.segwitGains = calcSegwitFeeGains(tx);
|
this.segwitGains = calcSegwitFeeGains(tx);
|
||||||
this.isRbfTransaction = tx.vin.some((v) => v.sequence < 0xfffffffe);
|
this.isRbfTransaction = tx.vin.some((v) => v.sequence < 0xfffffffe);
|
||||||
|
|
||||||
|
if (this.network === 'bisq') {
|
||||||
|
this.loadBisqTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
if (!tx.status.confirmed) {
|
if (!tx.status.confirmed) {
|
||||||
this.websocketService.startTrackTransaction(tx.txid);
|
this.websocketService.startTrackTransaction(tx.txid);
|
||||||
|
|
||||||
@ -133,6 +139,17 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
.subscribe((rbfTransaction) => this.rbfTransaction = rbfTransaction);
|
.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> {
|
handleLoadElectrsTransactionError(error: any): Observable<any> {
|
||||||
if (error.status === 404 && /^[a-fA-F0-9]{64}$/.test(this.txId)) {
|
if (error.status === 404 && /^[a-fA-F0-9]{64}$/.test(this.txId)) {
|
||||||
this.websocketService.startMultiTrackTransaction(this.txId);
|
this.websocketService.startMultiTrackTransaction(this.txId);
|
||||||
@ -204,6 +221,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
this.waitingForTransaction = false;
|
this.waitingForTransaction = false;
|
||||||
this.isLoadingTx = true;
|
this.isLoadingTx = true;
|
||||||
this.rbfTransaction = undefined;
|
this.rbfTransaction = undefined;
|
||||||
|
this.bisqTx = undefined;
|
||||||
this.transactionTime = -1;
|
this.transactionTime = -1;
|
||||||
document.body.scrollTo(0, 0);
|
document.body.scrollTo(0, 0);
|
||||||
this.leaveTransaction();
|
this.leaveTransaction();
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
|
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let vin of getFilteredTxVin(tx)">
|
<tr *ngFor="let vin of getFilteredTxVin(tx); trackBy: trackByIndexFn">
|
||||||
<td class="arrow-td">
|
<td class="arrow-td">
|
||||||
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
|
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
|
||||||
<i class="arrow grey"></i>
|
<i class="arrow grey"></i>
|
||||||
@ -73,7 +73,7 @@
|
|||||||
<div class="col mobile-bottomcol">
|
<div class="col mobile-bottomcol">
|
||||||
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
|
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let vout of getFilteredTxVout(tx); let vindex = index;">
|
<tr *ngFor="let vout of getFilteredTxVout(tx); let vindex = index; trackBy: trackByIndexFn">
|
||||||
<td>
|
<td>
|
||||||
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
<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>
|
<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) {
|
getFilteredTxVout(tx: Transaction) {
|
||||||
return tx.vout.slice(0, tx['@voutLength']);
|
return tx.vout.slice(0, tx['@voutLength']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackByIndexFn(index: number) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
74
frontend/src/app/interfaces/bisq.interfaces.ts
Normal file
74
frontend/src/app/interfaces/bisq.interfaces.ts
Normal file
@ -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 { 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 { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
|
import { BisqTransaction, BisqBlock } from '../interfaces/bisq.interfaces';
|
||||||
|
|
||||||
const API_BASE_URL = '/api{network}/v1';
|
const API_BASE_URL = '/api{network}/v1';
|
||||||
|
|
||||||
@ -18,6 +19,9 @@ export class ApiService {
|
|||||||
) {
|
) {
|
||||||
this.apiBaseUrl = API_BASE_URL.replace('{network}', '');
|
this.apiBaseUrl = API_BASE_URL.replace('{network}', '');
|
||||||
this.stateService.networkChanged$.subscribe((network) => {
|
this.stateService.networkChanged$.subscribe((network) => {
|
||||||
|
if (network === 'bisq') {
|
||||||
|
network = '';
|
||||||
|
}
|
||||||
this.apiBaseUrl = API_BASE_URL.replace('{network}', network ? '/' + 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 });
|
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.apiBaseUrl = API_BASE_URL;
|
||||||
this.stateService.networkChanged$.subscribe((network) => {
|
this.stateService.networkChanged$.subscribe((network) => {
|
||||||
|
if (network === 'bisq') {
|
||||||
|
network = '';
|
||||||
|
}
|
||||||
this.apiBaseUrl = API_BASE_URL + '/' + network;
|
this.apiBaseUrl = API_BASE_URL + '/' + network;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,12 @@ export class StateService {
|
|||||||
this.networkChanged$.next('testnet');
|
this.networkChanged$.next('testnet');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
case 'bisq':
|
||||||
|
if (this.network !== 'bisq') {
|
||||||
|
this.network = 'bisq';
|
||||||
|
this.networkChanged$.next('bisq');
|
||||||
|
}
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
if (this.network !== '') {
|
if (this.network !== '') {
|
||||||
this.network = '';
|
this.network = '';
|
||||||
|
@ -29,11 +29,14 @@ export class WebsocketService {
|
|||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
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.websocketSubject = webSocket<WebsocketResponse | any>(WEB_SOCKET_URL + '/' + this.network);
|
||||||
this.startSubscription();
|
this.startSubscription();
|
||||||
|
|
||||||
this.stateService.networkChanged$.subscribe((network) => {
|
this.stateService.networkChanged$.subscribe((network) => {
|
||||||
|
if (network === 'bisq') {
|
||||||
|
network = '';
|
||||||
|
}
|
||||||
if (network === this.network) {
|
if (network === this.network) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
60
frontend/src/app/shared/shared.module.ts
Normal file
60
frontend/src/app/shared/shared.module.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
BIN
frontend/src/resources/bisq-logo.png
Normal file
BIN
frontend/src/resources/bisq-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
@ -1189,6 +1189,30 @@
|
|||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
to-fast-properties "^2.0.0"
|
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":
|
"@istanbuljs/schema@^0.1.2":
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
|
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user