Merge branch 'master' into mononaut/fix-mobile-bottom-nav
This commit is contained in:
commit
2e7a701ca7
@ -18,6 +18,7 @@ import blocks from '../blocks';
|
|||||||
import bitcoinClient from './bitcoin-client';
|
import bitcoinClient from './bitcoin-client';
|
||||||
import difficultyAdjustment from '../difficulty-adjustment';
|
import difficultyAdjustment from '../difficulty-adjustment';
|
||||||
import transactionRepository from '../../repositories/TransactionRepository';
|
import transactionRepository from '../../repositories/TransactionRepository';
|
||||||
|
import rbfCache from '../rbf-cache';
|
||||||
|
|
||||||
class BitcoinRoutes {
|
class BitcoinRoutes {
|
||||||
public initRoutes(app: Application) {
|
public initRoutes(app: Application) {
|
||||||
@ -31,6 +32,8 @@ class BitcoinRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'backend-info', this.getBackendInfo)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'init-data', this.getInitData)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', this.validateAddress)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'validate-address/:address', this.validateAddress)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/replaces', this.getRbfHistory)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/cached', this.getCachedTx)
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', this.$postTransactionForm)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
|
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@ -589,6 +592,28 @@ class BitcoinRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getRbfHistory(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const result = rbfCache.getReplaces(req.params.txId);
|
||||||
|
res.json(result || []);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getCachedTx(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const result = rbfCache.getTx(req.params.txId);
|
||||||
|
if (result) {
|
||||||
|
res.json(result);
|
||||||
|
} else {
|
||||||
|
res.status(404).send('not found');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async getTransactionOutspends(req: Request, res: Response) {
|
private async getTransactionOutspends(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const result = await bitcoinApi.$getOutspends(req.params.txId);
|
const result = await bitcoinApi.$getOutspends(req.params.txId);
|
||||||
|
@ -35,24 +35,31 @@ export class Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getFeesInRange(transactions: TransactionExtended[], rangeLength: number) {
|
static getFeesInRange(transactions: TransactionExtended[], rangeLength: number) {
|
||||||
const arr = [transactions[transactions.length - 1].effectiveFeePerVsize];
|
const filtered: TransactionExtended[] = [];
|
||||||
|
let lastValidRate = Infinity;
|
||||||
|
// filter out anomalous fee rates to ensure monotonic range
|
||||||
|
for (const tx of transactions) {
|
||||||
|
if (tx.effectiveFeePerVsize <= lastValidRate) {
|
||||||
|
filtered.push(tx);
|
||||||
|
lastValidRate = tx.effectiveFeePerVsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const arr = [filtered[filtered.length - 1].effectiveFeePerVsize];
|
||||||
const chunk = 1 / (rangeLength - 1);
|
const chunk = 1 / (rangeLength - 1);
|
||||||
let itemsToAdd = rangeLength - 2;
|
let itemsToAdd = rangeLength - 2;
|
||||||
|
|
||||||
while (itemsToAdd > 0) {
|
while (itemsToAdd > 0) {
|
||||||
arr.push(transactions[Math.floor(transactions.length * chunk * itemsToAdd)].effectiveFeePerVsize);
|
arr.push(filtered[Math.floor(filtered.length * chunk * itemsToAdd)].effectiveFeePerVsize);
|
||||||
itemsToAdd--;
|
itemsToAdd--;
|
||||||
}
|
}
|
||||||
|
|
||||||
arr.push(transactions[0].effectiveFeePerVsize);
|
arr.push(filtered[0].effectiveFeePerVsize);
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended } {
|
static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended } {
|
||||||
const matches: { [txid: string]: TransactionExtended } = {};
|
const matches: { [txid: string]: TransactionExtended } = {};
|
||||||
deleted
|
deleted
|
||||||
// The replaced tx must have at least one input with nSequence < maxint-1 (That’s the opt-in)
|
|
||||||
.filter((tx) => tx.vin.some((vin) => vin.sequence < 0xfffffffe))
|
|
||||||
.forEach((deletedTx) => {
|
.forEach((deletedTx) => {
|
||||||
const foundMatches = added.find((addedTx) => {
|
const foundMatches = added.find((addedTx) => {
|
||||||
// The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
|
// The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
|
||||||
@ -61,7 +68,7 @@ export class Common {
|
|||||||
&& addedTx.feePerVsize > deletedTx.feePerVsize
|
&& addedTx.feePerVsize > deletedTx.feePerVsize
|
||||||
// Spends one or more of the same inputs
|
// Spends one or more of the same inputs
|
||||||
&& deletedTx.vin.some((deletedVin) =>
|
&& deletedTx.vin.some((deletedVin) =>
|
||||||
addedTx.vin.some((vin) => vin.txid === deletedVin.txid));
|
addedTx.vin.some((vin) => vin.txid === deletedVin.txid && vin.vout === deletedVin.vout));
|
||||||
});
|
});
|
||||||
if (foundMatches) {
|
if (foundMatches) {
|
||||||
matches[deletedTx.txid] = foundMatches;
|
matches[deletedTx.txid] = foundMatches;
|
||||||
|
@ -461,8 +461,8 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery(this.getCreateCompactTransactionsTableQuery(), await this.$checkIfTableExists('compact_transactions'));
|
await this.$executeQuery(this.getCreateCompactTransactionsTableQuery(), await this.$checkIfTableExists('compact_transactions'));
|
||||||
try {
|
try {
|
||||||
await this.$convertCompactCpfpTables();
|
await this.$convertCompactCpfpTables();
|
||||||
await this.$executeQuery('DROP TABLE IF EXISTS `cpfp_clusters`');
|
|
||||||
await this.$executeQuery('DROP TABLE IF EXISTS `transactions`');
|
await this.$executeQuery('DROP TABLE IF EXISTS `transactions`');
|
||||||
|
await this.$executeQuery('DROP TABLE IF EXISTS `cpfp_clusters`');
|
||||||
await this.updateToSchemaVersion(52);
|
await this.updateToSchemaVersion(52);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
logger.warn('' + (e instanceof Error ? e.message : e));
|
logger.warn('' + (e instanceof Error ? e.message : e));
|
||||||
|
@ -55,12 +55,13 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P
|
|||||||
clChannelsDict[clChannel.short_channel_id] = clChannel;
|
clChannelsDict[clChannel.short_channel_id] = clChannel;
|
||||||
clChannelsDictCount[clChannel.short_channel_id] = 1;
|
clChannelsDictCount[clChannel.short_channel_id] = 1;
|
||||||
} else {
|
} else {
|
||||||
consolidatedChannelList.push(
|
const fullChannel = await buildFullChannel(clChannel, clChannelsDict[clChannel.short_channel_id]);
|
||||||
await buildFullChannel(clChannel, clChannelsDict[clChannel.short_channel_id])
|
if (fullChannel !== null) {
|
||||||
);
|
consolidatedChannelList.push(fullChannel);
|
||||||
delete clChannelsDict[clChannel.short_channel_id];
|
delete clChannelsDict[clChannel.short_channel_id];
|
||||||
clChannelsDictCount[clChannel.short_channel_id]++;
|
clChannelsDictCount[clChannel.short_channel_id]++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||||
@ -74,7 +75,10 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P
|
|||||||
channelProcessed = 0;
|
channelProcessed = 0;
|
||||||
const keys = Object.keys(clChannelsDict);
|
const keys = Object.keys(clChannelsDict);
|
||||||
for (const short_channel_id of keys) {
|
for (const short_channel_id of keys) {
|
||||||
consolidatedChannelList.push(await buildIncompleteChannel(clChannelsDict[short_channel_id]));
|
const incompleteChannel = await buildIncompleteChannel(clChannelsDict[short_channel_id]);
|
||||||
|
if (incompleteChannel !== null) {
|
||||||
|
consolidatedChannelList.push(incompleteChannel);
|
||||||
|
}
|
||||||
|
|
||||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||||
@ -92,10 +96,13 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P
|
|||||||
* Convert two clightning "getchannels" entries into a full a lnd "describegraph.edges" format
|
* Convert two clightning "getchannels" entries into a full a lnd "describegraph.edges" format
|
||||||
* In this case, clightning knows the channel policy for both nodes
|
* In this case, clightning knows the channel policy for both nodes
|
||||||
*/
|
*/
|
||||||
async function buildFullChannel(clChannelA: any, clChannelB: any): Promise<ILightningApi.Channel> {
|
async function buildFullChannel(clChannelA: any, clChannelB: any): Promise<ILightningApi.Channel | null> {
|
||||||
const lastUpdate = Math.max(clChannelA.last_update ?? 0, clChannelB.last_update ?? 0);
|
const lastUpdate = Math.max(clChannelA.last_update ?? 0, clChannelB.last_update ?? 0);
|
||||||
|
|
||||||
const tx = await FundingTxFetcher.$fetchChannelOpenTx(clChannelA.short_channel_id);
|
const tx = await FundingTxFetcher.$fetchChannelOpenTx(clChannelA.short_channel_id);
|
||||||
|
if (!tx) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const parts = clChannelA.short_channel_id.split('x');
|
const parts = clChannelA.short_channel_id.split('x');
|
||||||
const outputIdx = parts[2];
|
const outputIdx = parts[2];
|
||||||
|
|
||||||
@ -115,8 +122,11 @@ async function buildFullChannel(clChannelA: any, clChannelB: any): Promise<ILigh
|
|||||||
* Convert one clightning "getchannels" entry into a full a lnd "describegraph.edges" format
|
* Convert one clightning "getchannels" entry into a full a lnd "describegraph.edges" format
|
||||||
* In this case, clightning knows the channel policy of only one node
|
* In this case, clightning knows the channel policy of only one node
|
||||||
*/
|
*/
|
||||||
async function buildIncompleteChannel(clChannel: any): Promise<ILightningApi.Channel> {
|
async function buildIncompleteChannel(clChannel: any): Promise<ILightningApi.Channel | null> {
|
||||||
const tx = await FundingTxFetcher.$fetchChannelOpenTx(clChannel.short_channel_id);
|
const tx = await FundingTxFetcher.$fetchChannelOpenTx(clChannel.short_channel_id);
|
||||||
|
if (!tx) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const parts = clChannel.short_channel_id.split('x');
|
const parts = clChannel.short_channel_id.split('x');
|
||||||
const outputIdx = parts[2];
|
const outputIdx = parts[2];
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ class Mempool {
|
|||||||
for (const rbfTransaction in rbfTransactions) {
|
for (const rbfTransaction in rbfTransactions) {
|
||||||
if (this.mempoolCache[rbfTransaction]) {
|
if (this.mempoolCache[rbfTransaction]) {
|
||||||
// Store replaced transactions
|
// Store replaced transactions
|
||||||
rbfCache.add(rbfTransaction, rbfTransactions[rbfTransaction].txid);
|
rbfCache.add(this.mempoolCache[rbfTransaction], rbfTransactions[rbfTransaction].txid);
|
||||||
// Erase the replaced transactions from the local mempool
|
// Erase the replaced transactions from the local mempool
|
||||||
delete this.mempoolCache[rbfTransaction];
|
delete this.mempoolCache[rbfTransaction];
|
||||||
}
|
}
|
||||||
@ -236,6 +236,7 @@ class Mempool {
|
|||||||
const lazyDeleteAt = this.mempoolCache[tx].deleteAfter;
|
const lazyDeleteAt = this.mempoolCache[tx].deleteAfter;
|
||||||
if (lazyDeleteAt && lazyDeleteAt < now) {
|
if (lazyDeleteAt && lazyDeleteAt < now) {
|
||||||
delete this.mempoolCache[tx];
|
delete this.mempoolCache[tx];
|
||||||
|
rbfCache.evict(tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,62 @@
|
|||||||
export interface CachedRbf {
|
import { TransactionExtended } from "../mempool.interfaces";
|
||||||
txid: string;
|
|
||||||
expires: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RbfCache {
|
class RbfCache {
|
||||||
private cache: { [txid: string]: CachedRbf; } = {};
|
private replacedBy: { [txid: string]: string; } = {};
|
||||||
|
private replaces: { [txid: string]: string[] } = {};
|
||||||
|
private txs: { [txid: string]: TransactionExtended } = {};
|
||||||
|
private expiring: { [txid: string]: Date } = {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
setInterval(this.cleanup.bind(this), 1000 * 60 * 60);
|
setInterval(this.cleanup.bind(this), 1000 * 60 * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(replacedTxId: string, newTxId: string): void {
|
public add(replacedTx: TransactionExtended, newTxId: string): void {
|
||||||
this.cache[replacedTxId] = {
|
this.replacedBy[replacedTx.txid] = newTxId;
|
||||||
expires: new Date(Date.now() + 1000 * 604800), // 1 week
|
this.txs[replacedTx.txid] = replacedTx;
|
||||||
txid: newTxId,
|
if (!this.replaces[newTxId]) {
|
||||||
};
|
this.replaces[newTxId] = [];
|
||||||
|
}
|
||||||
|
this.replaces[newTxId].push(replacedTx.txid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(txId: string): CachedRbf | undefined {
|
public getReplacedBy(txId: string): string | undefined {
|
||||||
return this.cache[txId];
|
return this.replacedBy[txId];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getReplaces(txId: string): string[] | undefined {
|
||||||
|
return this.replaces[txId];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTx(txId: string): TransactionExtended | undefined {
|
||||||
|
return this.txs[txId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// flag a transaction as removed from the mempool
|
||||||
|
public evict(txid): void {
|
||||||
|
this.expiring[txid] = new Date(Date.now() + 1000 * 86400); // 24 hours
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanup(): void {
|
private cleanup(): void {
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
for (const c in this.cache) {
|
for (const txid in this.expiring) {
|
||||||
if (this.cache[c].expires < currentDate) {
|
if (this.expiring[txid] < currentDate) {
|
||||||
delete this.cache[c];
|
delete this.expiring[txid];
|
||||||
|
this.remove(txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove a transaction & all previous versions from the cache
|
||||||
|
private remove(txid): void {
|
||||||
|
// don't remove a transaction while a newer version remains in the mempool
|
||||||
|
if (this.replaces[txid] && !this.replacedBy[txid]) {
|
||||||
|
const replaces = this.replaces[txid];
|
||||||
|
delete this.replaces[txid];
|
||||||
|
for (const tx of replaces) {
|
||||||
|
// recursively remove prior versions from the cache
|
||||||
|
delete this.replacedBy[tx];
|
||||||
|
delete this.txs[tx];
|
||||||
|
this.remove(tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,10 @@ class WebsocketHandler {
|
|||||||
client['track-tx'] = parsedMessage['track-tx'];
|
client['track-tx'] = parsedMessage['track-tx'];
|
||||||
// Client is telling the transaction wasn't found
|
// Client is telling the transaction wasn't found
|
||||||
if (parsedMessage['watch-mempool']) {
|
if (parsedMessage['watch-mempool']) {
|
||||||
const rbfCacheTx = rbfCache.get(client['track-tx']);
|
const rbfCacheTxid = rbfCache.getReplacedBy(client['track-tx']);
|
||||||
if (rbfCacheTx) {
|
if (rbfCacheTxid) {
|
||||||
response['txReplaced'] = {
|
response['txReplaced'] = {
|
||||||
txid: rbfCacheTx.txid,
|
txid: rbfCacheTxid,
|
||||||
};
|
};
|
||||||
client['track-tx'] = null;
|
client['track-tx'] = null;
|
||||||
} else {
|
} else {
|
||||||
@ -467,6 +467,7 @@ class WebsocketHandler {
|
|||||||
for (const txId of txIds) {
|
for (const txId of txIds) {
|
||||||
delete _memPool[txId];
|
delete _memPool[txId];
|
||||||
removed.push(txId);
|
removed.push(txId);
|
||||||
|
rbfCache.evict(txId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||||
|
@ -210,6 +210,9 @@ class NetworkSyncService {
|
|||||||
const channels = await channelsApi.$getChannelsWithoutCreatedDate();
|
const channels = await channelsApi.$getChannelsWithoutCreatedDate();
|
||||||
for (const channel of channels) {
|
for (const channel of channels) {
|
||||||
const transaction = await fundingTxFetcher.$fetchChannelOpenTx(channel.short_id);
|
const transaction = await fundingTxFetcher.$fetchChannelOpenTx(channel.short_id);
|
||||||
|
if (!transaction) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await DB.query(`
|
await DB.query(`
|
||||||
UPDATE channels SET created = FROM_UNIXTIME(?) WHERE channels.id = ?`,
|
UPDATE channels SET created = FROM_UNIXTIME(?) WHERE channels.id = ?`,
|
||||||
[transaction.timestamp, channel.id]
|
[transaction.timestamp, channel.id]
|
||||||
|
@ -71,7 +71,7 @@ class FundingTxFetcher {
|
|||||||
this.running = false;
|
this.running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $fetchChannelOpenTx(channelId: string): Promise<{timestamp: number, txid: string, value: number}> {
|
public async $fetchChannelOpenTx(channelId: string): Promise<{timestamp: number, txid: string, value: number} | null> {
|
||||||
channelId = Common.channelIntegerIdToShortId(channelId);
|
channelId = Common.channelIntegerIdToShortId(channelId);
|
||||||
|
|
||||||
if (this.fundingTxCache[channelId]) {
|
if (this.fundingTxCache[channelId]) {
|
||||||
@ -102,6 +102,11 @@ class FundingTxFetcher {
|
|||||||
const rawTx = await bitcoinClient.getRawTransaction(txid);
|
const rawTx = await bitcoinClient.getRawTransaction(txid);
|
||||||
const tx = await bitcoinClient.decodeRawTransaction(rawTx);
|
const tx = await bitcoinClient.decodeRawTransaction(rawTx);
|
||||||
|
|
||||||
|
if (!tx || !tx.vout || tx.vout.length < parseInt(outputIdx, 10) + 1 || tx.vout[outputIdx].value === undefined) {
|
||||||
|
logger.err(`Cannot find blockchain funding tx for channel id ${channelId}. Possible reasons are: bitcoin backend timeout or the channel shortId is not valid`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
this.fundingTxCache[channelId] = {
|
this.fundingTxCache[channelId] = {
|
||||||
timestamp: block.time,
|
timestamp: block.time,
|
||||||
txid: txid,
|
txid: txid,
|
||||||
|
3
contributors/piterden.txt
Normal file
3
contributors/piterden.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of December 17, 2022.
|
||||||
|
|
||||||
|
Signed: piterden
|
@ -17,7 +17,7 @@ _Note: address lookups require an Electrum Server and will not work with this co
|
|||||||
|
|
||||||
The default Docker configuration assumes you have the following configuration in your `bitcoin.conf` file:
|
The default Docker configuration assumes you have the following configuration in your `bitcoin.conf` file:
|
||||||
|
|
||||||
```
|
```ini
|
||||||
txindex=1
|
txindex=1
|
||||||
server=1
|
server=1
|
||||||
rpcuser=mempool
|
rpcuser=mempool
|
||||||
@ -26,7 +26,7 @@ rpcpassword=mempool
|
|||||||
|
|
||||||
If you want to use different credentials, specify them in the `docker-compose.yml` file:
|
If you want to use different credentials, specify them in the `docker-compose.yml` file:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
MEMPOOL_BACKEND: "none"
|
MEMPOOL_BACKEND: "none"
|
||||||
@ -54,7 +54,7 @@ First, configure `bitcoind` as specified above, and make sure your Electrum Serv
|
|||||||
|
|
||||||
Then, set the following variables in `docker-compose.yml` so Mempool can connect to your Electrum Server:
|
Then, set the following variables in `docker-compose.yml` so Mempool can connect to your Electrum Server:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
MEMPOOL_BACKEND: "electrum"
|
MEMPOOL_BACKEND: "electrum"
|
||||||
@ -85,7 +85,7 @@ Below we list all settings from `mempool-config.json` and the corresponding over
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
"NETWORK": "mainnet",
|
"NETWORK": "mainnet",
|
||||||
"BACKEND": "electrum",
|
"BACKEND": "electrum",
|
||||||
@ -116,7 +116,7 @@ Below we list all settings from `mempool-config.json` and the corresponding over
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
MEMPOOL_NETWORK: ""
|
MEMPOOL_NETWORK: ""
|
||||||
@ -153,8 +153,8 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
"PORT": 8332,
|
"PORT": 8332,
|
||||||
"USERNAME": "mempool",
|
"USERNAME": "mempool",
|
||||||
@ -163,7 +163,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
CORE_RPC_HOST: ""
|
CORE_RPC_HOST: ""
|
||||||
@ -176,7 +176,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"ELECTRUM": {
|
"ELECTRUM": {
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
"PORT": 50002,
|
"PORT": 50002,
|
||||||
@ -185,7 +185,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
ELECTRUM_HOST: ""
|
ELECTRUM_HOST: ""
|
||||||
@ -197,14 +197,14 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"REST_API_URL": "http://127.0.0.1:3000"
|
"REST_API_URL": "http://127.0.0.1:3000"
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
ESPLORA_REST_API_URL: ""
|
ESPLORA_REST_API_URL: ""
|
||||||
@ -214,7 +214,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"SECOND_CORE_RPC": {
|
"SECOND_CORE_RPC": {
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
"PORT": 8332,
|
"PORT": 8332,
|
||||||
@ -224,7 +224,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
SECOND_CORE_RPC_HOST: ""
|
SECOND_CORE_RPC_HOST: ""
|
||||||
@ -237,7 +237,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"DATABASE": {
|
"DATABASE": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
@ -249,7 +249,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
DATABASE_ENABLED: ""
|
DATABASE_ENABLED: ""
|
||||||
@ -264,7 +264,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"SYSLOG": {
|
"SYSLOG": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
@ -275,7 +275,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
SYSLOG_ENABLED: ""
|
SYSLOG_ENABLED: ""
|
||||||
@ -289,7 +289,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"STATISTICS": {
|
"STATISTICS": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"TX_PER_SECOND_SAMPLE_PERIOD": 150
|
"TX_PER_SECOND_SAMPLE_PERIOD": 150
|
||||||
@ -297,7 +297,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
STATISTICS_ENABLED: ""
|
STATISTICS_ENABLED: ""
|
||||||
@ -308,7 +308,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"BISQ": {
|
"BISQ": {
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
|
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
|
||||||
@ -316,7 +316,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
BISQ_ENABLED: ""
|
BISQ_ENABLED: ""
|
||||||
@ -327,7 +327,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"SOCKS5PROXY": {
|
"SOCKS5PROXY": {
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
@ -338,7 +338,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
SOCKS5PROXY_ENABLED: ""
|
SOCKS5PROXY_ENABLED: ""
|
||||||
@ -352,7 +352,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"PRICE_DATA_SERVER": {
|
"PRICE_DATA_SERVER": {
|
||||||
"TOR_URL": "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices",
|
"TOR_URL": "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices",
|
||||||
"CLEARNET_URL": "https://price.bisq.wiz.biz/getAllMarketPrices"
|
"CLEARNET_URL": "https://price.bisq.wiz.biz/getAllMarketPrices"
|
||||||
@ -360,7 +360,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
PRICE_DATA_SERVER_TOR_URL: ""
|
PRICE_DATA_SERVER_TOR_URL: ""
|
||||||
@ -371,7 +371,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"LIGHTNING": {
|
"LIGHTNING": {
|
||||||
"ENABLED": false
|
"ENABLED": false
|
||||||
"BACKEND": "lnd"
|
"BACKEND": "lnd"
|
||||||
@ -383,7 +383,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
LIGHTNING_ENABLED: false
|
LIGHTNING_ENABLED: false
|
||||||
@ -398,7 +398,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"LND": {
|
"LND": {
|
||||||
"TLS_CERT_PATH": ""
|
"TLS_CERT_PATH": ""
|
||||||
"MACAROON_PATH": ""
|
"MACAROON_PATH": ""
|
||||||
@ -407,7 +407,7 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
LND_TLS_CERT_PATH: ""
|
LND_TLS_CERT_PATH: ""
|
||||||
@ -419,14 +419,14 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
`mempool-config.json`:
|
`mempool-config.json`:
|
||||||
```
|
```json
|
||||||
"CLIGHTNING": {
|
"CLIGHTNING": {
|
||||||
"SOCKET": ""
|
"SOCKET": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Corresponding `docker-compose.yml` overrides:
|
Corresponding `docker-compose.yml` overrides:
|
||||||
```
|
```yaml
|
||||||
api:
|
api:
|
||||||
environment:
|
environment:
|
||||||
CLIGHTNING_SOCKET: ""
|
CLIGHTNING_SOCKET: ""
|
||||||
|
@ -138,6 +138,10 @@
|
|||||||
"translation": "src/locale/messages.hi.xlf",
|
"translation": "src/locale/messages.hi.xlf",
|
||||||
"baseHref": "/hi/"
|
"baseHref": "/hi/"
|
||||||
},
|
},
|
||||||
|
"ne": {
|
||||||
|
"translation": "src/locale/messages.ne.xlf",
|
||||||
|
"baseHref": "/ne/"
|
||||||
|
},
|
||||||
"lt": {
|
"lt": {
|
||||||
"translation": "src/locale/messages.lt.xlf",
|
"translation": "src/locale/messages.lt.xlf",
|
||||||
"baseHref": "/lt/"
|
"baseHref": "/lt/"
|
||||||
|
@ -116,6 +116,7 @@ export const languages: Language[] = [
|
|||||||
// { code: 'hr', name: 'Hrvatski' }, // Croatian
|
// { code: 'hr', name: 'Hrvatski' }, // Croatian
|
||||||
// { code: 'id', name: 'Bahasa Indonesia' },// Indonesian
|
// { code: 'id', name: 'Bahasa Indonesia' },// Indonesian
|
||||||
{ code: 'hi', name: 'हिन्दी' }, // Hindi
|
{ code: 'hi', name: 'हिन्दी' }, // Hindi
|
||||||
|
{ code: 'ne', name: 'नेपाली' }, // Nepalese
|
||||||
{ code: 'it', name: 'Italiano' }, // Italian
|
{ code: 'it', name: 'Italiano' }, // Italian
|
||||||
{ code: 'he', name: 'עברית' }, // Hebrew
|
{ code: 'he', name: 'עברית' }, // Hebrew
|
||||||
{ code: 'ka', name: 'ქართული' }, // Georgian
|
{ code: 'ka', name: 'ქართული' }, // Georgian
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<h1 i18n="shared.address">Address</h1>
|
<h1 i18n="shared.address">Address</h1>
|
||||||
<span class="address-link">
|
<span class="address-link">
|
||||||
<a [routerLink]="['/address/' | relativeUrl, addressString]">
|
<app-truncate [text]="addressString" [lastChars]="8" [link]="['/address/' | relativeUrl, addressString]">
|
||||||
<span class="d-inline d-lg-none">{{ addressString | shortenString : 24 }}</span>
|
|
||||||
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
|
||||||
</a>
|
|
||||||
<app-clipboard [text]="addressString"></app-clipboard>
|
<app-clipboard [text]="addressString"></app-clipboard>
|
||||||
|
</app-truncate>
|
||||||
</span>
|
</span>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
<h1 i18n="shared.transaction">Transaction</h1>
|
<h1 i18n="shared.transaction">Transaction</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="tx-link float-left">
|
<span class="tx-link">
|
||||||
<a [routerLink]="['/tx' | relativeUrl, bisqTx.id]">
|
<span class="txid">
|
||||||
<span class="d-inline d-lg-none">{{ bisqTx.id | shortenString : 24 }}</span>
|
<app-truncate [text]="bisqTx.id" [lastChars]="12" [link]="['/tx/' | relativeUrl, bisqTx.id]">
|
||||||
<span class="d-none d-lg-inline">{{ bisqTx.id }}</span>
|
|
||||||
</a>
|
|
||||||
<app-clipboard [text]="bisqTx.id"></app-clipboard>
|
<app-clipboard [text]="bisqTx.id"></app-clipboard>
|
||||||
|
</app-truncate>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="grow"></span>
|
<span class="grow"></span>
|
||||||
<div class="container-buttons">
|
<div class="container-buttons">
|
||||||
|
@ -6,17 +6,16 @@
|
|||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<div class="row d-flex justify-content-between">
|
<div class="row d-flex justify-content-between">
|
||||||
<div class="title-wrapper">
|
<div class="title-wrapper">
|
||||||
<h1 class="title truncated"><span class="first">{{addressString.slice(0,-4)}}</span><span class="last-four">{{addressString.slice(-4)}}</span></h1>
|
<h1 class="title"><app-truncate [text]="addressString"></app-truncate></h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngIf="addressInfo && addressInfo.unconfidential">
|
<tr *ngIf="addressInfo && addressInfo.unconfidential">
|
||||||
<td i18n="address.unconfidential">Unconfidential</td>
|
<td i18n="address.unconfidential">Unconfidential</td>
|
||||||
<td><a [routerLink]="['/address/' | relativeUrl, addressInfo.unconfidential]">
|
<td>
|
||||||
<span class="d-inline d-lg-none">{{ addressInfo.unconfidential | shortenString : 14 }}</span>
|
<app-truncate [text]="addressInfo.unconfidential" [lastChars]="7" [link]="['/address/' | relativeUrl, addressInfo.unconfidential]"></app-truncate>
|
||||||
<span class="d-none d-lg-inline">{{ addressInfo.unconfidential }}</span>
|
</td>
|
||||||
</a> <app-clipboard [text]="addressInfo.unconfidential"></app-clipboard></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<ng-template [ngIf]="!address.electrum">
|
<ng-template [ngIf]="!address.electrum">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -2,11 +2,9 @@
|
|||||||
<div class="title-address">
|
<div class="title-address">
|
||||||
<h1 i18n="shared.address">Address</h1>
|
<h1 i18n="shared.address">Address</h1>
|
||||||
<div class="tx-link">
|
<div class="tx-link">
|
||||||
<a [routerLink]="['/address/' | relativeUrl, addressString]" >
|
<app-truncate [text]="addressString" [lastChars]="8" [link]="['/address/' | relativeUrl, addressString]">
|
||||||
<span class="d-inline d-lg-none">{{ addressString | shortenString : 18 }}</span>
|
|
||||||
<span class="d-none d-lg-inline">{{ addressString }}</span>
|
|
||||||
</a>
|
|
||||||
<app-clipboard [text]="addressString"></app-clipboard>
|
<app-clipboard [text]="addressString"></app-clipboard>
|
||||||
|
</app-truncate>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -21,10 +19,11 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngIf="addressInfo && addressInfo.unconfidential">
|
<tr *ngIf="addressInfo && addressInfo.unconfidential">
|
||||||
<td i18n="address.unconfidential">Unconfidential</td>
|
<td i18n="address.unconfidential">Unconfidential</td>
|
||||||
<td><a [routerLink]="['/address/' | relativeUrl, addressInfo.unconfidential]">
|
<td>
|
||||||
<span class="d-inline d-lg-none">{{ addressInfo.unconfidential | shortenString : 14 }}</span>
|
<app-truncate [text]="addressInfo.unconfidential" [lastChars]="8" [link]="['/address/' | relativeUrl, addressInfo.unconfidential]">
|
||||||
<span class="d-none d-lg-inline">{{ addressInfo.unconfidential }}</span>
|
<app-clipboard [text]="addressInfo.unconfidential"></app-clipboard>
|
||||||
</a> <app-clipboard [text]="addressInfo.unconfidential"></app-clipboard></td>
|
</app-truncate>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<ng-template [ngIf]="!address.electrum">
|
<ng-template [ngIf]="!address.electrum">
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -2,11 +2,9 @@
|
|||||||
<div class="title-asset">
|
<div class="title-asset">
|
||||||
<h1 i18n="asset|Liquid Asset page title">Asset</h1>
|
<h1 i18n="asset|Liquid Asset page title">Asset</h1>
|
||||||
<div class="tx-link">
|
<div class="tx-link">
|
||||||
<a [routerLink]="['/assets/asset/' | relativeUrl, assetString]">
|
<app-truncate [text]="assetString" [lastChars]="8" [link]="['/assets/asset/' | relativeUrl, assetString]">
|
||||||
<span class="d-inline d-lg-none">{{ assetString | shortenString : 24 }}</span>
|
|
||||||
<span class="d-none d-lg-inline">{{ assetString }}</span>
|
|
||||||
</a>
|
|
||||||
<app-clipboard [text]="assetString"></app-clipboard>
|
<app-clipboard [text]="assetString"></app-clipboard>
|
||||||
|
</app-truncate>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -213,6 +213,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
legend: (data.series.length === 0) ? undefined : {
|
legend: (data.series.length === 0) ? undefined : {
|
||||||
|
padding: [10, 75],
|
||||||
data: data.legends,
|
data: data.legends,
|
||||||
selected: JSON.parse(this.storageService.getValue('fee_rates_legend')) ?? {
|
selected: JSON.parse(this.storageService.getValue('fee_rates_legend')) ?? {
|
||||||
'Min': true,
|
'Min': true,
|
||||||
|
@ -10,12 +10,13 @@ const defaultHoverColor = hexToColor('1bd8f4');
|
|||||||
|
|
||||||
const feeColors = mempoolFeeColors.map(hexToColor);
|
const feeColors = mempoolFeeColors.map(hexToColor);
|
||||||
const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
|
const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
|
||||||
|
const marginalFeeColors = feeColors.map((color) => darken(desaturate(color, 0.8), 1.1));
|
||||||
const auditColors = {
|
const auditColors = {
|
||||||
censored: hexToColor('f344df'),
|
censored: hexToColor('f344df'),
|
||||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||||
added: hexToColor('0099ff'),
|
added: hexToColor('0099ff'),
|
||||||
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
||||||
}
|
};
|
||||||
|
|
||||||
// convert from this class's update format to TxSprite's update format
|
// convert from this class's update format to TxSprite's update format
|
||||||
function toSpriteUpdate(params: ViewUpdateParams): SpriteUpdateParams {
|
function toSpriteUpdate(params: ViewUpdateParams): SpriteUpdateParams {
|
||||||
@ -161,13 +162,13 @@ export default class TxView implements TransactionStripped {
|
|||||||
case 'censored':
|
case 'censored':
|
||||||
return auditColors.censored;
|
return auditColors.censored;
|
||||||
case 'missing':
|
case 'missing':
|
||||||
return auditColors.missing;
|
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||||
case 'fresh':
|
case 'fresh':
|
||||||
return auditColors.missing;
|
return auditColors.missing;
|
||||||
case 'added':
|
case 'added':
|
||||||
return auditColors.added;
|
return auditColors.added;
|
||||||
case 'selected':
|
case 'selected':
|
||||||
return auditColors.selected;
|
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||||
case 'found':
|
case 'found':
|
||||||
if (this.context === 'projected') {
|
if (this.context === 'projected') {
|
||||||
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
||||||
|
@ -35,12 +35,12 @@
|
|||||||
<tr *ngIf="tx && tx.status && tx.status.length">
|
<tr *ngIf="tx && tx.status && tx.status.length">
|
||||||
<td class="td-width" i18n="transaction.audit-status">Audit status</td>
|
<td class="td-width" i18n="transaction.audit-status">Audit status</td>
|
||||||
<ng-container [ngSwitch]="tx?.status">
|
<ng-container [ngSwitch]="tx?.status">
|
||||||
<td *ngSwitchCase="'found'" i18n="transaction.audit.match">match</td>
|
<td *ngSwitchCase="'found'" i18n="transaction.audit.match">Match</td>
|
||||||
<td *ngSwitchCase="'censored'" i18n="transaction.audit.removed">removed</td>
|
<td *ngSwitchCase="'censored'" i18n="transaction.audit.removed">Removed</td>
|
||||||
<td *ngSwitchCase="'missing'" i18n="transaction.audit.marginal">marginal fee rate</td>
|
<td *ngSwitchCase="'missing'" i18n="transaction.audit.marginal">Marginal fee rate</td>
|
||||||
<td *ngSwitchCase="'fresh'" i18n="transaction.audit.recently-broadcast">recently broadcast</td>
|
<td *ngSwitchCase="'fresh'" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</td>
|
||||||
<td *ngSwitchCase="'added'" i18n="transaction.audit.added">added</td>
|
<td *ngSwitchCase="'added'" i18n="transaction.audit.added">Added</td>
|
||||||
<td *ngSwitchCase="'selected'" i18n="transaction.audit.marginal">marginal fee rate</td>
|
<td *ngSwitchCase="'selected'" i18n="transaction.audit.marginal">Marginal fee rate</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -4,8 +4,7 @@ import { StateService } from '../../services/state.service';
|
|||||||
import { specialBlocks } from '../../app.constants';
|
import { specialBlocks } from '../../app.constants';
|
||||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { config } from 'process';
|
import { CacheService } from '../../services/cache.service';
|
||||||
import { CacheService } from 'src/app/services/cache.service';
|
|
||||||
|
|
||||||
interface BlockchainBlock extends BlockExtended {
|
interface BlockchainBlock extends BlockExtended {
|
||||||
placeholder?: boolean;
|
placeholder?: boolean;
|
||||||
|
@ -64,26 +64,6 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
&.truncated {
|
|
||||||
text-overflow: unset;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: baseline;
|
|
||||||
|
|
||||||
.first {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
margin-right: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-four {
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .title-wrapper {
|
::ng-deep .title-wrapper {
|
||||||
|
@ -13,21 +13,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="mining.rewards-per-tx" i18n-ngbTooltip="mining.rewards-per-tx"
|
<h5 class="card-title" i18n="mining.fees-per-block" i18n-ngbTooltip="mining.fees-per-block"
|
||||||
ngbTooltip="Reward Per Tx" placement="bottom" #rewardspertx [disableTooltip]="!isEllipsisActive(rewardspertx)">Reward Per Tx</h5>
|
ngbTooltip="Avg Block Fees" placement="bottom" #rewardsperblock [disableTooltip]="!isEllipsisActive(rewardsperblock)">Avg Block Fees</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.rewards-per-tx-desc" ngbTooltip="Average miners' reward per transaction in the past 144 blocks" placement="bottom">
|
<div class="card-text" i18n-ngbTooltip="mining.fees-per-block-desc" ngbTooltip="Average fees per block in the past 144 blocks" placement="bottom">
|
||||||
<div class="fee-text">
|
<div class="fee-text">
|
||||||
{{ rewardStats.rewardPerTx | amountShortener: 2 }}
|
{{ (rewardStats.feePerBlock / 100000000) | amountShortener: 4 }}
|
||||||
<span i18n="shared.sat-vbyte|sat/vB">sats/tx</span>
|
<span i18n="shared.btc-block|BTC/block">BTC/block</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat">
|
||||||
<app-fiat [value]="rewardStats.rewardPerTx"></app-fiat>
|
<app-fiat [value]="rewardStats.feePerBlock"></app-fiat>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="mining.average-fee" i18n-ngbTooltip="mining.average-fee"
|
<h5 class="card-title" i18n="mining.average-fee" i18n-ngbTooltip="mining.average-fee"
|
||||||
ngbTooltip="Average Fee" placement="bottom" #averagefee [disableTooltip]="!isEllipsisActive(averagefee)">Average Fee</h5>
|
ngbTooltip="Avg Tx Fee" placement="bottom" #averagefee [disableTooltip]="!isEllipsisActive(averagefee)">Avg Tx Fee</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.average-fee" ngbTooltip="Fee paid on average for each transaction in the past 144 blocks" placement="bottom">
|
<div class="card-text" i18n-ngbTooltip="mining.average-fee" ngbTooltip="Fee paid on average for each transaction in the past 144 blocks" placement="bottom">
|
||||||
<div class="fee-text">{{ rewardStats.feePerTx | amountShortener: 2 }}
|
<div class="fee-text">{{ rewardStats.feePerTx | amountShortener: 2 }}
|
||||||
<span i18n="shared.sat-vbyte|sat/vB">sats/tx</span>
|
<span i18n="shared.sat-vbyte|sat/vB">sats/tx</span>
|
||||||
|
@ -42,8 +42,8 @@ export class RewardStatsComponent implements OnInit {
|
|||||||
map((stats) => {
|
map((stats) => {
|
||||||
return {
|
return {
|
||||||
totalReward: stats.totalReward,
|
totalReward: stats.totalReward,
|
||||||
rewardPerTx: stats.totalReward / stats.totalTx,
|
|
||||||
feePerTx: stats.totalFee / stats.totalTx,
|
feePerTx: stats.totalFee / stats.totalTx,
|
||||||
|
feePerBlock: stats.totalFee / 144,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
</app-preview-title>
|
</app-preview-title>
|
||||||
<div class="row d-flex justify-content-between full-width-row">
|
<div class="row d-flex justify-content-between full-width-row">
|
||||||
<div class="title-wrapper">
|
<div class="title-wrapper">
|
||||||
<h1 class="title truncated"><span class="first">{{txId.slice(0,-4)}}</span><span class="last-four">{{txId.slice(-4)}}</span></h1>
|
<h1 class="title truncated"><app-truncate [text]="txId"></app-truncate></h1>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="network !== 'liquid' && network !== 'liquidtestnet'" class="features">
|
<div *ngIf="network !== 'liquid' && network !== 'liquidtestnet'" class="features">
|
||||||
<app-tx-features [tx]="tx"></app-tx-features>
|
<app-tx-features [tx]="tx"></app-tx-features>
|
||||||
|
@ -3,21 +3,25 @@
|
|||||||
<div class="title-block">
|
<div class="title-block">
|
||||||
<div *ngIf="rbfTransaction" class="alert alert-mempool" role="alert">
|
<div *ngIf="rbfTransaction" class="alert alert-mempool" role="alert">
|
||||||
<span i18n="transaction.rbf.replacement|RBF replacement">This transaction has been replaced by:</span>
|
<span i18n="transaction.rbf.replacement|RBF replacement">This transaction has been replaced by:</span>
|
||||||
<a class="alert-link" [routerLink]="['/tx/' | relativeUrl, rbfTransaction.txid]">
|
<app-truncate [text]="rbfTransaction.txid" [lastChars]="12" [link]="['/tx/' | relativeUrl, rbfTransaction.txid]"></app-truncate>
|
||||||
<span class="d-inline d-lg-none">{{ rbfTransaction.txid | shortenString : 24 }}</span>
|
|
||||||
<span class="d-none d-lg-inline">{{ rbfTransaction.txid }}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="!rbfTransaction || rbfTransaction?.size">
|
<div *ngIf="rbfReplaces?.length" class="alert alert-mempool" role="alert">
|
||||||
|
<span i18n="transaction.rbf.replaced|RBF replaced">This transaction replaced:</span>
|
||||||
|
<div class="tx-list">
|
||||||
|
<app-truncate [text]="replaced" [lastChars]="12" *ngFor="let replaced of rbfReplaces" [link]="['/tx/' | relativeUrl, replaced]"></app-truncate>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!rbfTransaction || rbfTransaction?.size || tx">
|
||||||
<h1 i18n="shared.transaction">Transaction</h1>
|
<h1 i18n="shared.transaction">Transaction</h1>
|
||||||
|
|
||||||
<span class="tx-link float-left">
|
<span class="tx-link">
|
||||||
<a [routerLink]="['/tx/' | relativeUrl, txId]">
|
<span class="txid">
|
||||||
<span class="d-inline d-lg-none">{{ txId | shortenString : 24 }}</span>
|
<app-truncate [text]="txId" [lastChars]="12" [link]="['/tx/' | relativeUrl, txId]">
|
||||||
<span class="d-none d-lg-inline">{{ txId }}</span>
|
|
||||||
</a>
|
|
||||||
<app-clipboard [text]="txId"></app-clipboard>
|
<app-clipboard [text]="txId"></app-clipboard>
|
||||||
|
</app-truncate>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="container-buttons">
|
<div class="container-buttons">
|
||||||
@ -28,7 +32,10 @@
|
|||||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="tx && !tx?.status.confirmed">
|
<ng-template [ngIf]="tx && !tx?.status?.confirmed && replaced">
|
||||||
|
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Replaced</button>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template [ngIf]="tx && !tx?.status?.confirmed && !replaced">
|
||||||
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
@ -91,7 +98,7 @@
|
|||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-template [ngIf]="transactionTime !== 0">
|
<ng-template [ngIf]="transactionTime !== 0 && !replaced">
|
||||||
<tr *ngIf="transactionTime === -1; else firstSeenTmpl">
|
<tr *ngIf="transactionTime === -1; else firstSeenTmpl">
|
||||||
<td><span class="skeleton-loader"></span></td>
|
<td><span class="skeleton-loader"></span></td>
|
||||||
<td><span class="skeleton-loader"></span></td>
|
<td><span class="skeleton-loader"></span></td>
|
||||||
@ -103,7 +110,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<tr>
|
<tr *ngIf="!replaced">
|
||||||
<td class="td-width" i18n="transaction.eta|Transaction ETA">ETA</td>
|
<td class="td-width" i18n="transaction.eta|Transaction ETA">ETA</td>
|
||||||
<td>
|
<td>
|
||||||
<ng-template [ngIf]="txInBlockIndex === undefined" [ngIfElse]="estimationTmpl">
|
<ng-template [ngIf]="txInBlockIndex === undefined" [ngIfElse]="estimationTmpl">
|
||||||
@ -144,12 +151,12 @@
|
|||||||
<br>
|
<br>
|
||||||
|
|
||||||
<h2 class="text-left">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="xs"></fa-icon></h2>
|
<h2 class="text-left">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true" size="xs"></fa-icon></h2>
|
||||||
<div class="box">
|
<div class="box cpfp-details">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-fixed table-borderless table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n="transactions-list.vout.scriptpubkey-type">Type</th>
|
<th i18n="transactions-list.vout.scriptpubkey-type">Type</th>
|
||||||
<th i18n="dashboard.latest-transactions.txid">TXID</th>
|
<th class="txids" i18n="dashboard.latest-transactions.txid">TXID</th>
|
||||||
<th class="d-none d-lg-table-cell" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</th>
|
<th class="d-none d-lg-table-cell" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</th>
|
||||||
<th i18n="transaction.fee-rate|Transaction fee rate">Fee rate</th>
|
<th i18n="transaction.fee-rate|Transaction fee rate">Fee rate</th>
|
||||||
<th class="d-none d-lg-table-cell"></th>
|
<th class="d-none d-lg-table-cell"></th>
|
||||||
@ -159,10 +166,8 @@
|
|||||||
<ng-template [ngIf]="cpfpInfo?.descendants?.length">
|
<ng-template [ngIf]="cpfpInfo?.descendants?.length">
|
||||||
<tr *ngFor="let cpfpTx of cpfpInfo.descendants">
|
<tr *ngFor="let cpfpTx of cpfpInfo.descendants">
|
||||||
<td><span class="badge badge-primary" i18n="transaction.descendant|Descendant">Descendant</span></td>
|
<td><span class="badge badge-primary" i18n="transaction.descendant|Descendant">Descendant</span></td>
|
||||||
<td><a [routerLink]="['/tx' | relativeUrl, cpfpTx.txid]">
|
<td>
|
||||||
<span class="d-inline d-lg-none">{{ cpfpTx.txid | shortenString : 8 }}</span>
|
<app-truncate [text]="cpfpTx.txid" [link]="['/tx' | relativeUrl, cpfpTx.txid]"></app-truncate>
|
||||||
<span class="d-none d-lg-inline">{{ cpfpTx.txid }}</span>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-lg-table-cell" [innerHTML]="cpfpTx.weight / 4 | vbytes: 2"></td>
|
<td class="d-none d-lg-table-cell" [innerHTML]="cpfpTx.weight / 4 | vbytes: 2"></td>
|
||||||
<td>{{ cpfpTx.fee / (cpfpTx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
<td>{{ cpfpTx.fee / (cpfpTx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
||||||
@ -172,11 +177,8 @@
|
|||||||
<ng-template [ngIf]="cpfpInfo?.bestDescendant">
|
<ng-template [ngIf]="cpfpInfo?.bestDescendant">
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-success" i18n="transaction.descendant|Descendant">Descendant</span></td>
|
<td><span class="badge badge-success" i18n="transaction.descendant|Descendant">Descendant</span></td>
|
||||||
<td>
|
<td class="txids">
|
||||||
<a [routerLink]="['/tx' | relativeUrl, cpfpInfo.bestDescendant.txid]">
|
<app-truncate [text]="cpfpInfo.bestDescendant.txid" [link]="['/tx' | relativeUrl, cpfpInfo.bestDescendant.txid]"></app-truncate>
|
||||||
<span class="d-inline d-lg-none">{{ cpfpInfo.bestDescendant.txid | shortenString : 8 }}</span>
|
|
||||||
<span class="d-none d-lg-inline">{{ cpfpInfo.bestDescendant.txid }}</span>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-lg-table-cell" [innerHTML]="cpfpInfo.bestDescendant.weight / 4 | vbytes: 2"></td>
|
<td class="d-none d-lg-table-cell" [innerHTML]="cpfpInfo.bestDescendant.weight / 4 | vbytes: 2"></td>
|
||||||
<td>{{ cpfpInfo.bestDescendant.fee / (cpfpInfo.bestDescendant.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
<td>{{ cpfpInfo.bestDescendant.fee / (cpfpInfo.bestDescendant.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
||||||
@ -186,10 +188,8 @@
|
|||||||
<ng-template [ngIf]="cpfpInfo?.ancestors?.length">
|
<ng-template [ngIf]="cpfpInfo?.ancestors?.length">
|
||||||
<tr *ngFor="let cpfpTx of cpfpInfo.ancestors">
|
<tr *ngFor="let cpfpTx of cpfpInfo.ancestors">
|
||||||
<td><span class="badge badge-primary" i18n="transaction.ancestor|Transaction Ancestor">Ancestor</span></td>
|
<td><span class="badge badge-primary" i18n="transaction.ancestor|Transaction Ancestor">Ancestor</span></td>
|
||||||
<td><a [routerLink]="['/tx' | relativeUrl, cpfpTx.txid]">
|
<td class="txids">
|
||||||
<span class="d-inline d-lg-none">{{ cpfpTx.txid | shortenString : 8 }}</span>
|
<app-truncate [text]="cpfpTx.txid" [link]="['/tx' | relativeUrl, cpfpTx.txid]"></app-truncate>
|
||||||
<span class="d-none d-lg-inline">{{ cpfpTx.txid }}</span>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-lg-table-cell" [innerHTML]="cpfpTx.weight / 4 | vbytes: 2"></td>
|
<td class="d-none d-lg-table-cell" [innerHTML]="cpfpTx.weight / 4 | vbytes: 2"></td>
|
||||||
<td>{{ cpfpTx.fee / (cpfpTx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
<td>{{ cpfpTx.fee / (cpfpTx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
||||||
|
@ -19,22 +19,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tx-link {
|
.tx-link {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: baseline;
|
||||||
|
width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-right: 0px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
@media (min-width: 651px) {
|
@media (min-width: 651px) {
|
||||||
display: flex;
|
|
||||||
width: auto;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
|
margin-right: 1em;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@media (max-width: 650px) {
|
@media (max-width: 650px) {
|
||||||
|
width: 100%;
|
||||||
order: 3;
|
order: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.txid {
|
||||||
|
width: 200px;
|
||||||
|
min-width: 200px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.td-width {
|
.td-width {
|
||||||
@ -189,3 +199,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cpfp-details {
|
||||||
|
.txids {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-list {
|
||||||
|
.alert-link {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
@ -40,15 +40,21 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
transactionTime = -1;
|
transactionTime = -1;
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
fetchCpfpSubscription: Subscription;
|
fetchCpfpSubscription: Subscription;
|
||||||
|
fetchRbfSubscription: Subscription;
|
||||||
|
fetchCachedTxSubscription: Subscription;
|
||||||
txReplacedSubscription: Subscription;
|
txReplacedSubscription: Subscription;
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
queryParamsSubscription: Subscription;
|
queryParamsSubscription: Subscription;
|
||||||
urlFragmentSubscription: Subscription;
|
urlFragmentSubscription: Subscription;
|
||||||
fragmentParams: URLSearchParams;
|
fragmentParams: URLSearchParams;
|
||||||
rbfTransaction: undefined | Transaction;
|
rbfTransaction: undefined | Transaction;
|
||||||
|
replaced: boolean = false;
|
||||||
|
rbfReplaces: string[];
|
||||||
cpfpInfo: CpfpInfo | null;
|
cpfpInfo: CpfpInfo | null;
|
||||||
showCpfpDetails = false;
|
showCpfpDetails = false;
|
||||||
fetchCpfp$ = new Subject<string>();
|
fetchCpfp$ = new Subject<string>();
|
||||||
|
fetchRbfHistory$ = new Subject<string>();
|
||||||
|
fetchCachedTx$ = new Subject<string>();
|
||||||
now = new Date().getTime();
|
now = new Date().getTime();
|
||||||
timeAvg$: Observable<number>;
|
timeAvg$: Observable<number>;
|
||||||
liquidUnblinding = new LiquidUnblinding();
|
liquidUnblinding = new LiquidUnblinding();
|
||||||
@ -122,7 +128,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
delay(2000)
|
delay(2000)
|
||||||
)))
|
)),
|
||||||
|
catchError(() => {
|
||||||
|
return of(null);
|
||||||
|
})
|
||||||
|
)
|
||||||
),
|
),
|
||||||
catchError(() => {
|
catchError(() => {
|
||||||
return of(null);
|
return of(null);
|
||||||
@ -155,6 +165,49 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.cpfpInfo = cpfpInfo;
|
this.cpfpInfo = cpfpInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.fetchRbfSubscription = this.fetchRbfHistory$
|
||||||
|
.pipe(
|
||||||
|
switchMap((txId) =>
|
||||||
|
this.apiService
|
||||||
|
.getRbfHistory$(txId)
|
||||||
|
),
|
||||||
|
catchError(() => {
|
||||||
|
return of([]);
|
||||||
|
})
|
||||||
|
).subscribe((replaces) => {
|
||||||
|
this.rbfReplaces = replaces;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fetchCachedTxSubscription = this.fetchCachedTx$
|
||||||
|
.pipe(
|
||||||
|
switchMap((txId) =>
|
||||||
|
this.apiService
|
||||||
|
.getRbfCachedTx$(txId)
|
||||||
|
),
|
||||||
|
catchError(() => {
|
||||||
|
return of(null);
|
||||||
|
})
|
||||||
|
).subscribe((tx) => {
|
||||||
|
if (!tx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tx = tx;
|
||||||
|
if (tx.fee === undefined) {
|
||||||
|
this.tx.fee = 0;
|
||||||
|
}
|
||||||
|
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
|
||||||
|
this.isLoadingTx = false;
|
||||||
|
this.error = undefined;
|
||||||
|
this.waitingForTransaction = false;
|
||||||
|
this.graphExpanded = false;
|
||||||
|
this.setupGraph();
|
||||||
|
|
||||||
|
if (!this.tx?.status?.confirmed) {
|
||||||
|
this.fetchRbfHistory$.next(this.tx.txid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.subscription = this.route.paramMap
|
this.subscription = this.route.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
@ -268,6 +321,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.fetchCpfp$.next(this.tx.txid);
|
this.fetchCpfp$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
|
this.fetchRbfHistory$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
setTimeout(() => { this.applyFragment(); }, 0);
|
setTimeout(() => { this.applyFragment(); }, 0);
|
||||||
},
|
},
|
||||||
@ -299,6 +353,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.rbfTransaction = rbfTransaction;
|
this.rbfTransaction = rbfTransaction;
|
||||||
this.cacheService.setTxCache([this.rbfTransaction]);
|
this.cacheService.setTxCache([this.rbfTransaction]);
|
||||||
|
this.replaced = true;
|
||||||
|
if (rbfTransaction && !this.tx) {
|
||||||
|
this.fetchCachedTx$.next(this.txId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||||
@ -364,8 +422,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.waitingForTransaction = false;
|
this.waitingForTransaction = false;
|
||||||
this.isLoadingTx = true;
|
this.isLoadingTx = true;
|
||||||
this.rbfTransaction = undefined;
|
this.rbfTransaction = undefined;
|
||||||
|
this.replaced = false;
|
||||||
this.transactionTime = -1;
|
this.transactionTime = -1;
|
||||||
this.cpfpInfo = null;
|
this.cpfpInfo = null;
|
||||||
|
this.rbfReplaces = [];
|
||||||
this.showCpfpDetails = false;
|
this.showCpfpDetails = false;
|
||||||
document.body.scrollTo(0, 0);
|
document.body.scrollTo(0, 0);
|
||||||
this.leaveTransaction();
|
this.leaveTransaction();
|
||||||
@ -431,6 +491,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
this.fetchCpfpSubscription.unsubscribe();
|
this.fetchCpfpSubscription.unsubscribe();
|
||||||
|
this.fetchRbfSubscription.unsubscribe();
|
||||||
|
this.fetchCachedTxSubscription.unsubscribe();
|
||||||
this.txReplacedSubscription.unsubscribe();
|
this.txReplacedSubscription.unsubscribe();
|
||||||
this.blocksSubscription.unsubscribe();
|
this.blocksSubscription.unsubscribe();
|
||||||
this.queryParamsSubscription.unsubscribe();
|
this.queryParamsSubscription.unsubscribe();
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
<ng-container *ngFor="let tx of transactions; let i = index; trackBy: trackByFn">
|
<ng-container *ngFor="let tx of transactions; let i = index; trackBy: trackByFn">
|
||||||
<div *ngIf="!transactionPage" class="header-bg box tx-page-container">
|
<div *ngIf="!transactionPage" class="header-bg box tx-page-container">
|
||||||
<a class="float-left" [routerLink]="['/tx/' | relativeUrl, tx.txid]">
|
<a class="tx-link" [routerLink]="['/tx/' | relativeUrl, tx.txid]">
|
||||||
<span style="float: left;" class="d-block d-md-none">{{ tx.txid | shortenString : 16 }}</span>
|
<app-truncate [text]="tx.txid"></app-truncate>
|
||||||
<span style="float: left;" class="d-none d-md-block">{{ tx.txid }}</span>
|
|
||||||
</a>
|
</a>
|
||||||
<div class="float-right">
|
<div>
|
||||||
<ng-template [ngIf]="tx.status.confirmed">‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}</ng-template>
|
<ng-template [ngIf]="tx.status.confirmed">‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}</ng-template>
|
||||||
<ng-template [ngIf]="!tx.status.confirmed && tx.firstSeen">
|
<ng-template [ngIf]="!tx.status.confirmed && tx.firstSeen">
|
||||||
<i><app-time-since [time]="tx.firstSeen" [fastRender]="true"></app-time-since></i>
|
<i><app-time-since [time]="tx.firstSeen" [fastRender]="true"></app-time-since></i>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-bg box" infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()" [attr.data-cy]="'tx-' + i">
|
<div class="header-bg box" infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()" [attr.data-cy]="'tx-' + i">
|
||||||
@ -18,7 +16,7 @@
|
|||||||
<div *ngIf="errorUnblinded" class="error-unblinded">{{ errorUnblinded }}</div>
|
<div *ngIf="errorUnblinded" class="error-unblinded">{{ errorUnblinded }}</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<table class="table table-borderless smaller-text table-sm table-tx-vin">
|
<table class="table table-fixed table-borderless smaller-text table-sm table-tx-vin">
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-template ngFor let-vin let-vindex="index" [ngForOf]="tx.vin.slice(0, getVinLimit(tx))" [ngForTrackBy]="trackByIndexFn">
|
<ng-template ngFor let-vin let-vindex="index" [ngForOf]="tx.vin.slice(0, getVinLimit(tx))" [ngForTrackBy]="trackByIndexFn">
|
||||||
<tr [ngClass]="{
|
<tr [ngClass]="{
|
||||||
@ -49,7 +47,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="address-cell">
|
||||||
<div [ngSwitch]="true">
|
<div [ngSwitch]="true">
|
||||||
<ng-container *ngSwitchCase="vin.is_coinbase"><span i18n="transactions-list.coinbase">Coinbase</span><ng-template [ngIf]="network !== 'liquid' && network !== 'liquidtestnet'"> <span i18n="transactions-list.newly-generated-coins">(Newly Generated Coins)</span></ng-template><br /><a placement="bottom" [ngbTooltip]="vin.scriptsig | hex2ascii"><span class="badge badge-secondary scriptmessage longer">{{ vin.scriptsig | hex2ascii }}</span></a></ng-container>
|
<ng-container *ngSwitchCase="vin.is_coinbase"><span i18n="transactions-list.coinbase">Coinbase</span><ng-template [ngIf]="network !== 'liquid' && network !== 'liquidtestnet'"> <span i18n="transactions-list.newly-generated-coins">(Newly Generated Coins)</span></ng-template><br /><a placement="bottom" [ngbTooltip]="vin.scriptsig | hex2ascii"><span class="badge badge-secondary scriptmessage longer">{{ vin.scriptsig | hex2ascii }}</span></a></ng-container>
|
||||||
<ng-container *ngSwitchCase="vin.is_pegin">
|
<ng-container *ngSwitchCase="vin.is_pegin">
|
||||||
@ -66,12 +64,8 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #defaultAddress>
|
<ng-template #defaultAddress>
|
||||||
<a class="shortable-address" *ngIf="vin.prevout.scriptpubkey_address; else vinScriptPubkeyType" [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
|
<a class="address" *ngIf="vin.prevout.scriptpubkey_address; else vinScriptPubkeyType" [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
|
||||||
<span class="d-block d-lg-none">{{ vin.prevout.scriptpubkey_address | shortenString : 16 }}</span>
|
<app-truncate [text]="vin.prevout.scriptpubkey_address" [lastChars]="8"></app-truncate>
|
||||||
<span class="d-none d-lg-inline-flex justify-content-start">
|
|
||||||
<span class="addr-left flex-grow-1" [style]="vin.prevout.scriptpubkey_address.length > 40 ? 'max-width: 235px' : ''">{{ vin.prevout.scriptpubkey_address }}</span>
|
|
||||||
<span *ngIf="vin.prevout.scriptpubkey_address.length > 40" class="addr-right">{{ vin.prevout.scriptpubkey_address | capAddress: 40: 10 }}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
<ng-template #vinScriptPubkeyType>
|
<ng-template #vinScriptPubkeyType>
|
||||||
{{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
|
{{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
|
||||||
@ -100,7 +94,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="(showDetails$ | async) === true">
|
<tr *ngIf="(showDetails$ | async) === true">
|
||||||
<td colspan="3" class="details-container" >
|
<td colspan="3" class="details-container" >
|
||||||
<table class="table table-striped table-borderless details-table mb-3">
|
<table class="table table-striped table-fixed table-borderless details-table mb-3">
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-template [ngIf]="vin.scriptsig">
|
<ng-template [ngIf]="vin.scriptsig">
|
||||||
<tr>
|
<tr>
|
||||||
@ -112,9 +106,23 @@
|
|||||||
<td style="text-align: left;">{{ vin.scriptsig }}</td>
|
<td style="text-align: left;">{{ vin.scriptsig }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<tr *ngIf="vin.witness">
|
<tr *ngIf="vin.witness" class="vin-witness">
|
||||||
<td i18n="transactions-list.witness">Witness</td>
|
<td i18n="transactions-list.witness">Witness</td>
|
||||||
<td style="text-align: left;">{{ vin.witness.join(' ') }}</td>
|
<td style="text-align: left;">
|
||||||
|
<ng-container *ngFor="let witness of vin.witness; index as i">
|
||||||
|
<input type="checkbox" [id]="'tx' + vindex + 'witness' + i" style="display: none;">
|
||||||
|
<p class="witness-item" [class.accordioned]="witness.length > 1000">
|
||||||
|
{{ witness }}
|
||||||
|
</p>
|
||||||
|
<div class="witness-toggle" *ngIf="witness.length > 1000">
|
||||||
|
<span class="ellipsis">...</span>
|
||||||
|
<label [for]="'tx' + vindex + 'witness' + i" class="btn btn-sm btn-primary mt-2">
|
||||||
|
<span class="show-all" i18n="show-all">Show all</span>
|
||||||
|
<span class="show-less" i18n="show-less">Show less</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="vin.inner_redeemscript_asm">
|
<tr *ngIf="vin.inner_redeemscript_asm">
|
||||||
<td i18n="transactions-list.p2sh-redeem-script">P2SH redeem script</td>
|
<td i18n="transactions-list.p2sh-redeem-script">P2SH redeem script</td>
|
||||||
@ -153,7 +161,7 @@
|
|||||||
<ng-template #showMoreInputsLabel>
|
<ng-template #showMoreInputsLabel>
|
||||||
<span i18n="show-more">Show more</span>
|
<span i18n="show-more">Show more</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
({{ tx.vin.length - getVinLimit(tx) }} <span i18n="inputs-remaining">remaining</span>)
|
(<ng-container *ngTemplateOutlet="xRemaining; context: {$implicit: tx.vin.length - getVinLimit(tx)}"></ng-container>)
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -162,20 +170,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-100 d-block d-md-none"></div>
|
<div class="w-100 d-block d-md-none"></div>
|
||||||
<div class="col mobile-bottomcol">
|
<div class="col mobile-bottomcol">
|
||||||
<table class="table table-borderless smaller-text table-sm table-tx-vout">
|
<table class="table table-fixed table-borderless smaller-text table-sm table-tx-vout">
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx.vout.slice(0, getVoutLimit(tx))" [ngForTrackBy]="trackByIndexFn">
|
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx.vout.slice(0, getVoutLimit(tx))" [ngForTrackBy]="trackByIndexFn">
|
||||||
<tr [ngClass]="{
|
<tr [ngClass]="{
|
||||||
'assetBox': assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex,
|
'assetBox': assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex,
|
||||||
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
|
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
|
||||||
}">
|
}">
|
||||||
<td>
|
<td class="address-cell">
|
||||||
<a class="shortable-address" *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
<a class="address" *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>
|
<app-truncate [text]="vout.scriptpubkey_address" [lastChars]="8"></app-truncate>
|
||||||
<span class="d-none d-lg-inline-flex justify-content-start">
|
|
||||||
<span class="addr-left flex-grow-1" [style]="vout.scriptpubkey_address.length > 40 ? 'max-width: 235px' : ''">{{ vout.scriptpubkey_address }}</span>
|
|
||||||
<span *ngIf="vout.scriptpubkey_address.length > 40" class="addr-right">{{ vout.scriptpubkey_address | capAddress: 40: 10 }}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
<div>
|
<div>
|
||||||
<app-address-labels [vout]="vout" [channel]="tx._channels && tx._channels.outputs[vindex] ? tx._channels.outputs[vindex] : null"></app-address-labels>
|
<app-address-labels [vout]="vout" [channel]="tx._channels && tx._channels.outputs[vindex] ? tx._channels.outputs[vindex] : null"></app-address-labels>
|
||||||
@ -185,13 +189,11 @@
|
|||||||
<ng-container i18n="transactions-list.peg-out-to">Peg-out to <ng-container *ngTemplateOutlet="pegOutLink"></ng-container></ng-container>
|
<ng-container i18n="transactions-list.peg-out-to">Peg-out to <ng-container *ngTemplateOutlet="pegOutLink"></ng-container></ng-container>
|
||||||
<ng-template #pegOutLink>
|
<ng-template #pegOutLink>
|
||||||
<a *ngIf="stateService.env.BASE_MODULE === 'liquid'; else localPegoutLink" [attr.href]="'https://mempool.space/address/' + vout.pegout.scriptpubkey_address" title="{{ vout.pegout.scriptpubkey_address }}">
|
<a *ngIf="stateService.env.BASE_MODULE === 'liquid'; else localPegoutLink" [attr.href]="'https://mempool.space/address/' + vout.pegout.scriptpubkey_address" title="{{ vout.pegout.scriptpubkey_address }}">
|
||||||
<span class="d-block d-lg-none">{{ vout.pegout.scriptpubkey_address | shortenString : 16 }}</span>
|
<app-truncate [text]="vout.pegout.scriptpubkey_address"></app-truncate>
|
||||||
<span class="d-none d-lg-block">{{ vout.pegout.scriptpubkey_address | shortenString : 35 }}</span>
|
|
||||||
</a>
|
</a>
|
||||||
<ng-template #localPegoutLink>
|
<ng-template #localPegoutLink>
|
||||||
<a [routerLink]="['/address/', vout.pegout.scriptpubkey_address]" title="{{ vout.pegout.scriptpubkey_address }}">
|
<a [routerLink]="['/address/', vout.pegout.scriptpubkey_address]" title="{{ vout.pegout.scriptpubkey_address }}">
|
||||||
<span class="d-block d-lg-none">{{ vout.pegout.scriptpubkey_address | shortenString : 16 }}</span>
|
<app-truncate [text]="vout.pegout.scriptpubkey_address"></app-truncate>
|
||||||
<span class="d-none d-lg-block">{{ vout.pegout.scriptpubkey_address | shortenString : 35 }}</span>
|
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -270,7 +272,7 @@
|
|||||||
<ng-template #showMoreOutputsLabel>
|
<ng-template #showMoreOutputsLabel>
|
||||||
<span i18n="show-more">Show more</span>
|
<span i18n="show-more">Show more</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
({{ tx.vout.length - getVoutLimit(tx) }} <span i18n="outputs-remaining">remaining</span>)
|
(<ng-container *ngTemplateOutlet="xRemaining; context: {$implicit: tx.vout.length - getVoutLimit(tx)}"></ng-container>)
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -324,3 +326,5 @@
|
|||||||
<br />
|
<br />
|
||||||
<a [routerLink]="['/assets/asset/' | relativeUrl, item.asset]">{{ item.asset | shortenString : 13 }}</a>
|
<a [routerLink]="['/assets/asset/' | relativeUrl, item.asset]">{{ item.asset | shortenString : 13 }}</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #xRemaining let-x i18n="x-remaining">{{ x }} remaining</ng-template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
.arrow-td {
|
.arrow-td {
|
||||||
width: 20px;
|
width: 30px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
@ -45,6 +45,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.amount {
|
||||||
|
width: 32.5%;
|
||||||
|
}
|
||||||
|
|
||||||
.extra-info {
|
.extra-info {
|
||||||
display: none;
|
display: none;
|
||||||
@media (min-width: 576px) {
|
@media (min-width: 576px) {
|
||||||
@ -81,6 +85,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tx-page-container {
|
.tx-page-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
white-space: nowrap;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@ -97,9 +105,7 @@
|
|||||||
&:first-child {
|
&:first-child {
|
||||||
color: #ffffff66;
|
color: #ffffff66;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
@media (min-width: 476px) {
|
width: 150px;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
@ -130,14 +136,7 @@ h2 {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addr-left {
|
.address {
|
||||||
font-family: monospace;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
margin-right: -7px
|
|
||||||
}
|
|
||||||
|
|
||||||
.addr-right {
|
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,3 +145,50 @@ h2 {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tx-link {
|
||||||
|
width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-inline-end: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vin-witness {
|
||||||
|
.witness-item.accordioned {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .witness-item.accordioned {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.witness-toggle {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
.show-all {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.show-less {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ellipsis {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked ~ .witness-toggle {
|
||||||
|
.show-all {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.show-less {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.ellipsis {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,8 +31,7 @@
|
|||||||
<p *ngIf="!isConnector">Peg Out</p>
|
<p *ngIf="!isConnector">Peg Out</p>
|
||||||
<p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
|
<p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
|
||||||
<p class="address">
|
<p class="address">
|
||||||
<span class="first">{{ line.pegout.slice(0, -4) }}</span>
|
<app-truncate [text]="line.pegout"></app-truncate>
|
||||||
<span class="last-four">{{ line.pegout.slice(-4) }}</span>
|
|
||||||
</p>
|
</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -49,8 +48,7 @@
|
|||||||
<ng-container *ngIf="isConnector && line.txid">
|
<ng-container *ngIf="isConnector && line.txid">
|
||||||
<p>
|
<p>
|
||||||
<span i18n="transaction">Transaction</span>
|
<span i18n="transaction">Transaction</span>
|
||||||
<span class="first">{{ line.txid.slice(0, 8) }}</span>...
|
<app-truncate [text]="line.txid"></app-truncate>
|
||||||
<span class="last-four">{{ line.txid.slice(-4) }}</span>
|
|
||||||
</p>
|
</p>
|
||||||
<ng-container [ngSwitch]="line.type">
|
<ng-container [ngSwitch]="line.type">
|
||||||
<p *ngSwitchCase="'input'"><span i18n="transaction.output">Output</span> #{{ line.vout + 1 }}</p>
|
<p *ngSwitchCase="'input'"><span i18n="transaction.output">Output</span> #{{ line.vout + 1 }}</p>
|
||||||
@ -60,8 +58,7 @@
|
|||||||
<p *ngIf="line.value == null && line.confidential" i18n="shared.confidential">Confidential</p>
|
<p *ngIf="line.value == null && line.confidential" i18n="shared.confidential">Confidential</p>
|
||||||
<p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
|
<p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p>
|
||||||
<p *ngIf="line.type !== 'fee' && line.address" class="address">
|
<p *ngIf="line.type !== 'fee' && line.address" class="address">
|
||||||
<span class="first">{{ line.address.slice(0, -4) }}</span>
|
<app-truncate [text]="line.address"></app-truncate>
|
||||||
<span class="last-four">{{ line.address.slice(-4) }}</span>
|
|
||||||
</p>
|
</p>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,22 +17,5 @@
|
|||||||
.address {
|
.address {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
.first {
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
margin-right: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-four {
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -127,7 +127,11 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let transaction of transactions$ | async; let i = index;">
|
<tr *ngFor="let transaction of transactions$ | async; let i = index;">
|
||||||
<td class="table-cell-txid"><a [routerLink]="['/tx' | relativeUrl, transaction.txid]">{{ transaction.txid | shortenString : 10 }}</a></td>
|
<td class="table-cell-txid">
|
||||||
|
<a [routerLink]="['/tx' | relativeUrl, transaction.txid]">
|
||||||
|
<app-truncate [text]="transaction.txid" [lastChars]="5"></app-truncate>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
|
<td class="table-cell-satoshis"><app-amount *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'; else liquidAmount" [satoshis]="transaction.value" digitsInfo="1.2-4" [noFiat]="true"></app-amount><ng-template #liquidAmount i18n="shared.confidential">Confidential</ng-template></td>
|
||||||
<td class="table-cell-fiat" *ngIf="(network$ | async) === ''" ><app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat></td>
|
<td class="table-cell-fiat" *ngIf="(network$ | async) === ''" ><app-fiat [value]="transaction.value" digitsInfo="1.0-0"></app-fiat></td>
|
||||||
<td class="table-cell-fees">{{ transaction.fee / transaction.vsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
<td class="table-cell-fees">{{ transaction.fee / transaction.vsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></td>
|
||||||
|
@ -12,7 +12,8 @@ if (browserWindowEnv.BASE_MODULE && (browserWindowEnv.BASE_MODULE === 'bisq' ||
|
|||||||
routes = [
|
routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'api/rest'
|
redirectTo: 'api/rest',
|
||||||
|
pathMatch: 'full'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'api/:type',
|
path: 'api/:type',
|
||||||
@ -20,11 +21,13 @@ if (browserWindowEnv.BASE_MODULE && (browserWindowEnv.BASE_MODULE === 'bisq' ||
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'api',
|
path: 'api',
|
||||||
redirectTo: 'api/rest'
|
redirectTo: 'api/rest',
|
||||||
|
pathMatch: 'full'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
redirectTo: 'api/rest'
|
redirectTo: 'api/rest',
|
||||||
|
pathMatch: 'full'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<div class="mb-2 box-top">
|
<div class="mb-2 box-top">
|
||||||
<div class="box-left text-truncate">
|
<div class="box-left text-truncate">
|
||||||
<h3 class="mb-0 text-truncate">{{ channel.alias || '?' }}</h3>
|
<h3 class="mb-0 text-truncate">{{ channel.alias || '?' }}</h3>
|
||||||
<a [routerLink]="['/lightning/node' | relativeUrl, channel.public_key]" >
|
<app-truncate [text]="channel.public_key" [lastChars]="6" [link]="['/lightning/node' | relativeUrl, channel.public_key]">
|
||||||
{{ channel.public_key | shortenString : 12 }}
|
|
||||||
</a>
|
|
||||||
<app-clipboard [text]="channel.public_key"></app-clipboard>
|
<app-clipboard [text]="channel.public_key"></app-clipboard>
|
||||||
|
</app-truncate>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-right">
|
<div class="box-right">
|
||||||
<div class="second-line"><ng-container *ngTemplateOutlet="xChannels; context: {$implicit: channel.channels }"></ng-container></div>
|
<div class="second-line"><ng-container *ngTemplateOutlet="xChannels; context: {$implicit: channel.channels }"></ng-container></div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 767.98px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10,16 +10,13 @@
|
|||||||
.tx-link {
|
.tx-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@media (min-width: 650px) {
|
@media (min-width: 768px) {
|
||||||
|
top: 1px;
|
||||||
|
position: relative;
|
||||||
align-self: end;
|
align-self: end;
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: -3px;
|
|
||||||
}
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
top: 1px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
order: 2;
|
order: 2;
|
||||||
|
@ -49,10 +49,9 @@
|
|||||||
<td class="alias text-left">
|
<td class="alias text-left">
|
||||||
<div>{{ node.alias || '?' }}</div>
|
<div>{{ node.alias || '?' }}</div>
|
||||||
<div class="second-line">
|
<div class="second-line">
|
||||||
<a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">
|
<app-truncate [text]="node.public_key" [maxWidth]="200" [lastChars]="6" [link]="['/lightning/node' | relativeUrl, node.public_key]">
|
||||||
<span>{{ node.public_key | shortenString : publicKeySize }}</span>
|
|
||||||
</a>
|
|
||||||
<app-clipboard [text]="node.public_key" size="small"></app-clipboard>
|
<app-clipboard [text]="node.public_key" size="small"></app-clipboard>
|
||||||
|
</app-truncate>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="alias text-left d-none d-md-table-cell">
|
<td class="alias text-left d-none d-md-table-cell">
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
|
|
||||||
<!-- Top nodes per capacity -->
|
<!-- Top nodes per capacity -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card" style="height: 409px">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="['/lightning/nodes/rankings/liquidity' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/lightning/nodes/rankings/liquidity' | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="lightning.liquidity-ranking">Liquidity Ranking</h5>
|
<h5 class="card-title d-inline" i18n="lightning.liquidity-ranking">Liquidity Ranking</h5>
|
||||||
@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
<!-- Top nodes per channels -->
|
<!-- Top nodes per channels -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card" style="height: 409px">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="['/lightning/nodes/rankings/connectivity' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/lightning/nodes/rankings/connectivity' | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="lightning.connectivity-ranking">Connectivity Ranking</h5>
|
<h5 class="card-title d-inline" i18n="lightning.connectivity-ranking">Connectivity Ranking</h5>
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
<div class="title-container mb-2" *ngIf="!error">
|
<div class="title-container mb-2" *ngIf="!error">
|
||||||
<h1 class="mb-0 text-truncate">{{ node.alias }}</h1>
|
<h1 class="mb-0 text-truncate">{{ node.alias }}</h1>
|
||||||
<span class="tx-link">
|
<span class="tx-link">
|
||||||
<a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">
|
<span class="node-id">
|
||||||
<span class="d-inline d-lg-none">{{ node.public_key | shortenString : 24 }}</span>
|
<app-truncate [text]="node.public_key" [lastChars]="8" [link]="['/lightning/node' | relativeUrl, node.public_key]">
|
||||||
<span class="d-none d-lg-inline">{{ node.public_key }}</span>
|
|
||||||
</a>
|
|
||||||
<app-clipboard [text]="node.public_key"></app-clipboard>
|
<app-clipboard [text]="node.public_key"></app-clipboard>
|
||||||
|
</app-truncate>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -215,7 +215,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="hasDetails" class="text-right mt-3">
|
<div *ngIf="hasDetails" class="text-right mt-3">
|
||||||
<button type="button" class="btn btn-outline-info btn-sm btn-details" (click)="toggleShowDetails()" i18n="node.details|Node Details">Details</button>
|
<button type="button" class="btn btn-outline-info btn-sm btn-details" (click)="toggleShowDetails()" i18n="transaction.details|Transaction Details">Details</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!error">
|
<div *ngIf="!error">
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-id {
|
||||||
|
width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table table-borderless text-center m-auto" style="max-width: 900px">
|
<table class="table table-borderless table-fixed text-center m-auto" style="max-width: 900px">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-left rank" i18n="mining.rank">Rank</th>
|
<th class="text-left rank" i18n="mining.rank">Rank</th>
|
||||||
|
@ -42,14 +42,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rank {
|
.rank {
|
||||||
width: 20%;
|
width: 8%;
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
width: 20%;
|
width: 36%;
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
@ -59,21 +59,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.share {
|
.share {
|
||||||
width: 20%;
|
width: 15%;
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nodes {
|
.nodes {
|
||||||
width: 20%;
|
width: 15%;
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
width: 10%;
|
width: 10%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.capacity {
|
.capacity {
|
||||||
width: 20%;
|
width: 26%;
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
width: 10%;
|
width: 10%;
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
@ -91,3 +91,8 @@ a:hover .link {
|
|||||||
.flag {
|
.flag {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-truncate .link {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
<app-toggle [textLeft]="'Sort by nodes'" [textRight]="'capacity'" [checked]="true" (toggleStatusChanged)="onGroupToggleStatusChanged($event)"></app-toggle>
|
<app-toggle [textLeft]="'Sort by nodes'" [textRight]="'capacity'" [checked]="true" (toggleStatusChanged)="onGroupToggleStatusChanged($event)"></app-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table table-borderless text-center m-auto" style="max-width: 900px" *ngIf="!widget">
|
<table class="table table-borderless table-fixed text-center m-auto" style="max-width: 900px" *ngIf="!widget">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="rank text-left pl-0" i18n="mining.rank">Rank</th>
|
<th class="rank text-left pl-0" i18n="mining.rank">Rank</th>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div [class]="widget ? 'widget' : 'full'">
|
<div [class]="widget ? 'widget' : 'full'">
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless table-fixed">
|
||||||
<thead>
|
<thead>
|
||||||
<th class="rank"></th>
|
<th class="rank"></th>
|
||||||
<th class="alias text-left" i18n="nodes.alias">Alias</th>
|
<th class="alias text-left" i18n="nodes.alias">Alias</th>
|
||||||
@ -29,10 +29,10 @@
|
|||||||
{{ node.channels | number }}
|
{{ node.channels | number }}
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="timestamp-first text-left">
|
<td *ngIf="!widget" class="timestamp-first text-left">
|
||||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.firstSeen"></app-timestamp>
|
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.firstSeen" [hideTimeSince]="true"></app-timestamp>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="timestamp-update text-left">
|
<td *ngIf="!widget" class="timestamp-update text-left">
|
||||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt" [hideTimeSince]="true"></app-timestamp>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.container-xl {
|
.container-xl {
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
padding-bottom: 100px;
|
padding-bottom: 100px;
|
||||||
@media (min-width: 767.98px) {
|
@media (min-width: 960px) {
|
||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
}
|
}
|
||||||
@ -15,40 +15,44 @@
|
|||||||
width: 5%;
|
width: 5%;
|
||||||
}
|
}
|
||||||
.widget .rank {
|
.widget .rank {
|
||||||
@media (min-width: 767.98px) {
|
@media (min-width: 960px) {
|
||||||
width: 13%;
|
width: 13%;
|
||||||
}
|
}
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .alias {
|
.full .alias {
|
||||||
width: 10%;
|
width: 20%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
max-width: 175px;
|
width: 40%;
|
||||||
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.widget .alias {
|
.widget .alias {
|
||||||
width: 55%;
|
width: 60%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
max-width: 175px;
|
max-width: 175px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .capacity {
|
.full .capacity {
|
||||||
width: 10%;
|
width: 10%;
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.widget .capacity {
|
.widget .capacity {
|
||||||
width: 32%;
|
width: 32%;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
@ -57,28 +61,31 @@
|
|||||||
.full .channels {
|
.full .channels {
|
||||||
width: 15%;
|
width: 15%;
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .timestamp-first {
|
.full .timestamp-first {
|
||||||
width: 15%;
|
width: 10%;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .timestamp-update {
|
.full .timestamp-update {
|
||||||
width: 15%;
|
width: 10%;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .location {
|
.full .location {
|
||||||
width: 10%;
|
width: 15%;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,10 +29,10 @@
|
|||||||
<app-amount [satoshis]="node.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
<app-amount [satoshis]="node.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="timestamp-first text-left">
|
<td *ngIf="!widget" class="timestamp-first text-left">
|
||||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.firstSeen"></app-timestamp>
|
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.firstSeen" [hideTimeSince]="true"></app-timestamp>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="timestamp-update text-left">
|
<td *ngIf="!widget" class="timestamp-update text-left">
|
||||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt" [hideTimeSince]="true"></app-timestamp>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.container-xl {
|
.container-xl {
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
padding-bottom: 100px;
|
padding-bottom: 100px;
|
||||||
@media (min-width: 767.98px) {
|
@media (min-width: 960px) {
|
||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
}
|
}
|
||||||
@ -15,70 +15,77 @@
|
|||||||
width: 5%;
|
width: 5%;
|
||||||
}
|
}
|
||||||
.widget .rank {
|
.widget .rank {
|
||||||
@media (min-width: 767.98px) {
|
@media (min-width: 960px) {
|
||||||
width: 13%;
|
width: 13%;
|
||||||
}
|
}
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .alias {
|
.full .alias {
|
||||||
width: 10%;
|
width: 20%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
max-width: 175px;
|
width: 40%;
|
||||||
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.widget .alias {
|
.widget .alias {
|
||||||
width: 55%;
|
width: 60%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
max-width: 175px;
|
max-width: 175px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .channels {
|
.full .capacity {
|
||||||
width: 10%;
|
width: 10%;
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.widget .channels {
|
.widget .capacity {
|
||||||
width: 32%;
|
width: 32%;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .capacity {
|
.full .channels {
|
||||||
width: 15%;
|
width: 15%;
|
||||||
padding-right: 50px;
|
padding-right: 50px;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .timestamp-first {
|
.full .timestamp-first {
|
||||||
width: 15%;
|
width: 10%;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .timestamp-update {
|
.full .timestamp-update {
|
||||||
width: 15%;
|
width: 10%;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.full .location {
|
.full .location {
|
||||||
width: 10%;
|
width: 15%;
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 960px) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITrans
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||||
import { Outspend } from '../interfaces/electrs.interface';
|
import { Outspend, Transaction } from '../interfaces/electrs.interface';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -119,6 +119,14 @@ export class ApiService {
|
|||||||
return this.httpClient.get<AddressInformation>(this.apiBaseUrl + this.apiBasePath + '/api/v1/validate-address/' + address);
|
return this.httpClient.get<AddressInformation>(this.apiBaseUrl + this.apiBasePath + '/api/v1/validate-address/' + address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRbfHistory$(txid: string): Observable<string[]> {
|
||||||
|
return this.httpClient.get<string[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/replaces');
|
||||||
|
}
|
||||||
|
|
||||||
|
getRbfCachedTx$(txid: string): Observable<Transaction> {
|
||||||
|
return this.httpClient.get<Transaction>(this.apiBaseUrl + this.apiBasePath + '/api/v1/tx/' + txid + '/cached');
|
||||||
|
}
|
||||||
|
|
||||||
listLiquidPegsMonth$(): Observable<LiquidPegs[]> {
|
listLiquidPegsMonth$(): Observable<LiquidPegs[]> {
|
||||||
return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
|
return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<span *ngIf="seconds === undefined">-</span>
|
<span *ngIf="seconds === undefined">-</span>
|
||||||
<span *ngIf="seconds !== undefined">
|
<span *ngIf="seconds !== undefined">
|
||||||
‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }}
|
‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }}
|
||||||
<div class="lg-inline">
|
<div class="lg-inline" *ngIf="!hideTimeSince">
|
||||||
<i class="symbol">(<app-time-since [time]="seconds" [fastRender]="true"></app-time-since>)</i>
|
<i class="symbol">(<app-time-since [time]="seconds" [fastRender]="true"></app-time-since>)</i>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
@ -10,6 +10,7 @@ export class TimestampComponent implements OnChanges {
|
|||||||
@Input() unixTime: number;
|
@Input() unixTime: number;
|
||||||
@Input() dateString: string;
|
@Input() dateString: string;
|
||||||
@Input() customFormat: string;
|
@Input() customFormat: string;
|
||||||
|
@Input() hideTimeSince: boolean = false;
|
||||||
|
|
||||||
seconds: number | undefined = undefined;
|
seconds: number | undefined = undefined;
|
||||||
|
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
<span class="truncate" [style.max-width]="maxWidth ? maxWidth + 'px' : null">
|
||||||
|
<ng-container *ngIf="link">
|
||||||
|
<a [routerLink]="link" class="truncate-link">
|
||||||
|
<ng-container *ngIf="rtl; then rtlTruncated; else ltrTruncated;"></ng-container>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!link">
|
||||||
|
<ng-container *ngIf="rtl; then rtlTruncated; else ltrTruncated;"></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ng-template #ltrTruncated>
|
||||||
|
<span class="first">{{text.slice(0,-lastChars)}}</span><span class="last-four">{{text.slice(-lastChars)}}</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #rtlTruncated>
|
||||||
|
<span class="first">{{text.slice(lastChars)}}</span><span class="last-four">{{text.slice(0,lastChars)}}</span>
|
||||||
|
</ng-template>
|
@ -0,0 +1,26 @@
|
|||||||
|
.truncate {
|
||||||
|
text-overflow: unset;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
.truncate-link {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-shrink: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.first {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-four {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-truncate',
|
||||||
|
templateUrl: './truncate.component.html',
|
||||||
|
styleUrls: ['./truncate.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class TruncateComponent {
|
||||||
|
@Input() text: string;
|
||||||
|
@Input() link: any = null;
|
||||||
|
@Input() lastChars: number = 4;
|
||||||
|
@Input() maxWidth: number = null;
|
||||||
|
rtl: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
|
) {
|
||||||
|
if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) {
|
||||||
|
this.rtl = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,6 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class Decimal2HexPipe implements PipeTransform {
|
export class Decimal2HexPipe implements PipeTransform {
|
||||||
transform(decimal: number): string {
|
transform(decimal: number): string {
|
||||||
return `0x` + decimal.toString(16);
|
return `0x` + ( decimal.toString(16) ).padStart(8, '0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,7 @@ import { IndexingProgressComponent } from '../components/indexing-progress/index
|
|||||||
import { SvgImagesComponent } from '../components/svg-images/svg-images.component';
|
import { SvgImagesComponent } from '../components/svg-images/svg-images.component';
|
||||||
import { ChangeComponent } from '../components/change/change.component';
|
import { ChangeComponent } from '../components/change/change.component';
|
||||||
import { SatsComponent } from './components/sats/sats.component';
|
import { SatsComponent } from './components/sats/sats.component';
|
||||||
|
import { TruncateComponent } from './components/truncate/truncate.component';
|
||||||
import { SearchResultsComponent } from '../components/search-form/search-results/search-results.component';
|
import { SearchResultsComponent } from '../components/search-form/search-results/search-results.component';
|
||||||
import { TimestampComponent } from './components/timestamp/timestamp.component';
|
import { TimestampComponent } from './components/timestamp/timestamp.component';
|
||||||
import { ToggleComponent } from './components/toggle/toggle.component';
|
import { ToggleComponent } from './components/toggle/toggle.component';
|
||||||
@ -152,6 +153,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
|
|||||||
SvgImagesComponent,
|
SvgImagesComponent,
|
||||||
ChangeComponent,
|
ChangeComponent,
|
||||||
SatsComponent,
|
SatsComponent,
|
||||||
|
TruncateComponent,
|
||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
TimestampComponent,
|
TimestampComponent,
|
||||||
ToggleComponent,
|
ToggleComponent,
|
||||||
@ -252,6 +254,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
|
|||||||
SvgImagesComponent,
|
SvgImagesComponent,
|
||||||
ChangeComponent,
|
ChangeComponent,
|
||||||
SatsComponent,
|
SatsComponent,
|
||||||
|
TruncateComponent,
|
||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
TimestampComponent,
|
TimestampComponent,
|
||||||
ToggleComponent,
|
ToggleComponent,
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6425
frontend/src/locale/messages.da.xlf
Normal file
6425
frontend/src/locale/messages.da.xlf
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6744
frontend/src/locale/messages.ne.xlf
Normal file
6744
frontend/src/locale/messages.ne.xlf
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -106,6 +106,7 @@ http {
|
|||||||
~*^vi vi;
|
~*^vi vi;
|
||||||
~*^zh zh;
|
~*^zh zh;
|
||||||
~*^hi hi;
|
~*^hi hi;
|
||||||
|
~*^ne ne;
|
||||||
~*^lt lt;
|
~*^lt lt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +143,7 @@ http {
|
|||||||
~*^vi vi;
|
~*^vi vi;
|
||||||
~*^zh zh;
|
~*^zh zh;
|
||||||
~*^hi hi;
|
~*^hi hi;
|
||||||
|
~*^ne ne;
|
||||||
~*^lt lt;
|
~*^lt lt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user