Liquid peg outs address indexing: support for duplicates and minor fixes
This commit is contained in:
		
							parent
							
								
									1e15428a63
								
							
						
					
					
						commit
						163c0b6d78
					
				@ -154,7 +154,9 @@ class ElementsParser {
 | 
			
		||||
 | 
			
		||||
        // First, get the current UTXOs that need to be scanned in the block
 | 
			
		||||
        const utxos = await this.$getFederationUtxosToScan(auditProgress.lastBlockAudit);
 | 
			
		||||
        // logger.debug(`Found ${utxos.length} Federation UTXOs to scan in Bitcoin block height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip}`);
 | 
			
		||||
 | 
			
		||||
        // Get the peg-out addresses that need to be scanned
 | 
			
		||||
        const redeemAddresses = await this.$getRedeemAddressesToScan();
 | 
			
		||||
 | 
			
		||||
        // The fast way: check if these UTXOs are still unspent as of the current block with gettxout
 | 
			
		||||
        let spentAsTip: any[];
 | 
			
		||||
@ -163,41 +165,33 @@ class ElementsParser {
 | 
			
		||||
          const utxosToParse = await this.$getFederationUtxosToParse(utxos);
 | 
			
		||||
          spentAsTip = utxosToParse.spentAsTip;
 | 
			
		||||
          unspentAsTip = utxosToParse.unspentAsTip;
 | 
			
		||||
          // logger.debug(`${unspentAsTip.length} / ${utxos.length} Federation UTXOs are unspent as of tip`);
 | 
			
		||||
        } else { // If the audit status is too far in the past, it is useless to look for still unspent txos since they will all be spent as of the tip
 | 
			
		||||
          logger.debug(`Found ${utxos.length} Federation UTXOs and ${redeemAddresses.length} Peg-Out Addresses to scan in Bitcoin block height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip}`);
 | 
			
		||||
          logger.debug(`${unspentAsTip.length} / ${utxos.length} Federation UTXOs are unspent as of tip`);
 | 
			
		||||
        } else { // If the audit status is too far in the past, it is useless and wasteful to look for still unspent txos since they will all be spent as of the tip
 | 
			
		||||
          spentAsTip = utxos;
 | 
			
		||||
          unspentAsTip = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get the peg-out addresses that need to be scanned
 | 
			
		||||
        const redeemAddresses = await this.$getRedeemAddressesToScan();
 | 
			
		||||
        // if (redeemAddresses.length > 0) logger.debug(`Found ${redeemAddresses.length} peg-out addresses to scan`);
 | 
			
		||||
 | 
			
		||||
        // Logging during initial indexing
 | 
			
		||||
        if (auditProgress.confirmedTip - auditProgress.lastBlockAudit > 150) {
 | 
			
		||||
          // Logging
 | 
			
		||||
          const elapsedSeconds = (Date.now() / 1000) - timer;
 | 
			
		||||
          if (elapsedSeconds > 5) {
 | 
			
		||||
            const runningFor = (Date.now() / 1000) - startedAt;
 | 
			
		||||
            const blockPerSeconds = indexedThisRun / elapsedSeconds;
 | 
			
		||||
            indexingSpeeds.push(blockPerSeconds);
 | 
			
		||||
            if (indexingSpeeds.length > 100) indexingSpeeds.shift(); // Keep the length of the up to 100 last indexing speeds
 | 
			
		||||
            const eta = (auditProgress.confirmedTip - auditProgress.lastBlockAudit) / (indexingSpeeds.reduce((a, b) => a + b, 0) / indexingSpeeds.length);
 | 
			
		||||
            logger.debug(`Scanning ${utxos.length} Federation UTXOs and ${redeemAddresses.length} Peg-Out Addresses at height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip} | ~${blockPerSeconds.toFixed(2)} blocks/sec | elapsed: ${(runningFor / 60).toFixed(2)} minutes | ETA: ${(eta / 60).toFixed(2)} minutes`);
 | 
			
		||||
            const meanIndexingSpeed = indexingSpeeds.reduce((a, b) => a + b, 0) / indexingSpeeds.length;
 | 
			
		||||
            const eta = (auditProgress.confirmedTip - auditProgress.lastBlockAudit) / meanIndexingSpeed;
 | 
			
		||||
            logger.debug(`Scanning ${utxos.length} Federation UTXOs and ${redeemAddresses.length} Peg-Out Addresses at Bitcoin block height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip} | ~${meanIndexingSpeed.toFixed(2)} blocks/sec | elapsed: ${(runningFor / 60).toFixed(0)} minutes | ETA: ${(eta / 60).toFixed(0)} minutes`);
 | 
			
		||||
            timer = Date.now() / 1000;
 | 
			
		||||
            indexedThisRun = 0;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The slow way: parse the block to look for the spending tx
 | 
			
		||||
        // logger.debug(`${spentAsTip.length} / ${utxos.length} Federation UTXOs are spent as of tip`);
 | 
			
		||||
 | 
			
		||||
        const blockHash: IBitcoinApi.ChainTips = await bitcoinSecondClient.getBlockHash(auditProgress.lastBlockAudit);
 | 
			
		||||
        const block: IBitcoinApi.Block = await bitcoinSecondClient.getBlock(blockHash, 2);
 | 
			
		||||
        const nbUtxos = spentAsTip.length;
 | 
			
		||||
        await DB.query('START TRANSACTION;');
 | 
			
		||||
        await this.$parseBitcoinBlock(block, spentAsTip, unspentAsTip, auditProgress.confirmedTip, redeemAddresses);
 | 
			
		||||
        await DB.query(`COMMIT;`);
 | 
			
		||||
        // logger.debug(`Watched for spending of ${nbUtxos} Federation UTXOs in block ${auditProgress.lastBlockAudit} / ${auditProgress.confirmedTip}`);
 | 
			
		||||
 | 
			
		||||
        // Finally, update the lastblockupdate of the remaining UTXOs and save to the database
 | 
			
		||||
        const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`)
 | 
			
		||||
@ -236,14 +230,15 @@ class ElementsParser {
 | 
			
		||||
    return {spentAsTip, unspentAsTip};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $parseBitcoinBlock(block: IBitcoinApi.Block, spentAsTip: any[], unspentAsTip: any[], confirmedTip: number, redeemAddresses: string[] = []) {
 | 
			
		||||
    let mightRedeemInThisBlock = false; // If a Federation UTXO is spent in this block, we might find a peg-out address in the outputs...
 | 
			
		||||
  protected async $parseBitcoinBlock(block: IBitcoinApi.Block, spentAsTip: any[], unspentAsTip: any[], confirmedTip: number, redeemAddressesData: any[] = []) {
 | 
			
		||||
    const redeemAddresses: string[] = redeemAddressesData.map(redeemAddress => redeemAddress.bitcoinaddress);
 | 
			
		||||
    for (const tx of block.tx) {
 | 
			
		||||
      let mightRedeemInThisTx = false; // If a Federation UTXO is spent in this block, we might find a peg-out address in the outputs...
 | 
			
		||||
      // Check if the Federation UTXOs that was spent as of tip are spent in this block
 | 
			
		||||
      for (const input of tx.vin) {
 | 
			
		||||
        const txo = spentAsTip.find(txo => txo.txid === input.txid && txo.txindex === input.vout);
 | 
			
		||||
        if (txo) {
 | 
			
		||||
          mightRedeemInThisBlock = true;
 | 
			
		||||
          mightRedeemInThisTx = true;
 | 
			
		||||
          await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, lasttimeupdate = ? WHERE txid = ? AND txindex = ?`, [block.height, block.time, txo.txid, txo.txindex]);
 | 
			
		||||
          // Remove the TXO from the utxo array
 | 
			
		||||
          spentAsTip.splice(spentAsTip.indexOf(txo), 1);
 | 
			
		||||
@ -269,16 +264,31 @@ class ElementsParser {
 | 
			
		||||
            logger.debug(`Added new Federation UTXO ${tx.txid}:${output.n} (${output.value * 100000000} sats), change address: ${output.scriptPubKey.address}`);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (mightRedeemInThisBlock && output.scriptPubKey.address && redeemAddresses.includes(output.scriptPubKey.address)) {
 | 
			
		||||
          const query_add_redeem = `UPDATE elements_pegs SET bitcointxid = ?, bitcoinindex = ? WHERE bitcoinaddress = ?`;
 | 
			
		||||
          const params_add_redeem: (string | number)[] = [tx.txid, output.n, output.scriptPubKey.address];
 | 
			
		||||
          await DB.query(query_add_redeem, params_add_redeem);
 | 
			
		||||
          redeemAddresses.splice(redeemAddresses.indexOf(output.scriptPubKey.address), 1);
 | 
			
		||||
          logger.debug(`Added redeem txid ${tx.txid}:${output.n} to peg-out address ${output.scriptPubKey.address}`);
 | 
			
		||||
        if (mightRedeemInThisTx && output.scriptPubKey.address && redeemAddresses.includes(output.scriptPubKey.address)) {
 | 
			
		||||
          // Find the number of times output.scriptPubKey.address appears in redeemAddresses. There can be address reuse for peg-outs...
 | 
			
		||||
          const matchingAddress: any[] = redeemAddressesData.filter(redeemAddress => redeemAddress.bitcoinaddress === output.scriptPubKey.address && -redeemAddress.amount === Math.round(output.value * 100000000));
 | 
			
		||||
          if (matchingAddress.length > 0) {
 | 
			
		||||
            if (matchingAddress.length > 1) {
 | 
			
		||||
              // If there are more than one peg out address with the same amount, we can't know which one redeemed the UTXO: we take the oldest one
 | 
			
		||||
              matchingAddress.sort((a, b) => a.datetime - b.datetime);
 | 
			
		||||
              logger.debug(`Found redeem txid ${tx.txid}:${output.n} to peg-out address ${matchingAddress[0].bitcoinaddress}, amount ${matchingAddress[0].amount}, datetime ${matchingAddress[0].datetime}`);
 | 
			
		||||
            } else {
 | 
			
		||||
              logger.debug(`Found redeem txid ${tx.txid}:${output.n} to peg-out address ${matchingAddress[0].bitcoinaddress}, amount ${matchingAddress[0].amount}`);
 | 
			
		||||
            }
 | 
			
		||||
            const query_add_redeem = `UPDATE elements_pegs SET bitcointxid = ?, bitcoinindex = ? WHERE bitcoinaddress = ? AND amount = ? AND datetime = ?`;
 | 
			
		||||
            const params_add_redeem: (string | number)[] = [tx.txid, output.n, matchingAddress[0].bitcoinaddress, matchingAddress[0].amount, matchingAddress[0].datetime];
 | 
			
		||||
            await DB.query(query_add_redeem, params_add_redeem);
 | 
			
		||||
            const index = redeemAddressesData.indexOf(matchingAddress[0]);
 | 
			
		||||
            redeemAddressesData.splice(index, 1);
 | 
			
		||||
            redeemAddresses.splice(index, 1);
 | 
			
		||||
          } else { // The output amount does not match the peg-out amount... log it
 | 
			
		||||
            logger.debug(`Found redeem txid ${tx.txid}:${output.n} to peg-out address ${output.scriptPubKey.address} but output amount ${Math.round(output.value * 100000000)} does not match the peg-out amount!`);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    for (const utxo of spentAsTip) {
 | 
			
		||||
      await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [block.height, utxo.txid, utxo.txindex]);    
 | 
			
		||||
    }
 | 
			
		||||
@ -318,10 +328,10 @@ class ElementsParser {
 | 
			
		||||
    return rows[0]['number'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $getRedeemAddressesToScan(): Promise<string[]> {
 | 
			
		||||
    const query = `SELECT bitcoinaddress FROM elements_pegs where amount < 0 AND bitcoinaddress != '' AND bitcointxid = '';`;
 | 
			
		||||
  protected async $getRedeemAddressesToScan(): Promise<any[]> {
 | 
			
		||||
    const query = `SELECT datetime, amount, bitcoinaddress FROM elements_pegs where amount < 0 AND bitcoinaddress != '' AND bitcointxid = '';`;
 | 
			
		||||
    const [rows]: any[] = await DB.query(query);
 | 
			
		||||
    return rows.map((row: any) => row.bitcoinaddress);
 | 
			
		||||
    return rows;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ///////////// DATA QUERY //////////////
 | 
			
		||||
@ -423,9 +433,9 @@ class ElementsParser {
 | 
			
		||||
    return rows[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get the 300 most recent pegouts from the federation
 | 
			
		||||
  // Get recent pegouts from the federation (3 months old)
 | 
			
		||||
  public async $getRecentPegouts(): Promise<any> {
 | 
			
		||||
    const query = `SELECT txid, txindex, amount, bitcoinaddress, bitcointxid, bitcoinindex, datetime AS blocktime FROM elements_pegs WHERE amount < 0 ORDER BY blocktime DESC LIMIT 300;`;
 | 
			
		||||
    const query = `SELECT txid, txindex, amount, bitcoinaddress, bitcointxid, bitcoinindex, datetime AS blocktime FROM elements_pegs WHERE amount < 0 AND datetime > UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -90, CURRENT_TIMESTAMP())) ORDER BY blocktime;`;
 | 
			
		||||
    const [rows] = await DB.query(query);
 | 
			
		||||
    return rows;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
<div [ngClass]="{'widget': widget, 'address-container': !widget}">
 | 
			
		||||
  <div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
 | 
			
		||||
<div [ngClass]="{'widget': widget}">
 | 
			
		||||
 | 
			
		||||
  <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,3 @@
 | 
			
		||||
.address-container {
 | 
			
		||||
  @media (min-width: 1100px) {
 | 
			
		||||
    margin-left: 80px;
 | 
			
		||||
    margin-right: 80px;  
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spinner-border {
 | 
			
		||||
  height: 25px;
 | 
			
		||||
  width: 25px;
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
    <div class="fee-estimation-container">
 | 
			
		||||
      <div class="item">
 | 
			
		||||
        <a class="title-link" [routerLink]="['/audit/wallet/addresses' | relativeUrl]">
 | 
			
		||||
          <h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
 | 
			
		||||
          <h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
 | 
			
		||||
        </a>
 | 
			
		||||
        <div class="card-text">
 | 
			
		||||
          <div class="fee-text">{{ federationAddresses.length }} <span i18n="shared.addresses">addresses</span></div>
 | 
			
		||||
@ -19,7 +19,7 @@
 | 
			
		||||
  <div class="fee-estimation-container loading-container">
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <a class="title-link" [routerLink]="['/audit/wallet/addresses' | relativeUrl]">
 | 
			
		||||
        <h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
 | 
			
		||||
        <h5 class="card-title"><ng-container i18n="liquid.federation-wallet">Liquid Federation Wallet</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
 | 
			
		||||
      </a>
 | 
			
		||||
      <div class="card-text">
 | 
			
		||||
        <div class="skeleton-loader"></div>
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,5 @@
 | 
			
		||||
<div [ngClass]="{'widget': widget}">
 | 
			
		||||
 | 
			
		||||
  <div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
 | 
			
		||||
 | 
			
		||||
  <div class="clearfix"></div>
 | 
			
		||||
  
 | 
			
		||||
  <div style="min-height: 295px">
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,6 @@
 | 
			
		||||
      <h1 i18n="liquid.recent-pegs">Recent Peg-In / Out's</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
 | 
			
		||||
 | 
			
		||||
    <div class="clearfix"></div>
 | 
			
		||||
    
 | 
			
		||||
    <div style="min-height: 295px">
 | 
			
		||||
@ -14,7 +12,8 @@
 | 
			
		||||
        <thead style="vertical-align: middle;">
 | 
			
		||||
          <th class="transaction text-left" [ngClass]="{'widget': widget}" i18n="shared.transaction">Transaction</th>
 | 
			
		||||
          <th class="amount text-right" [ngClass]="{'widget': widget}" i18n="shared.amount">Amount</th>
 | 
			
		||||
          <th class="output text-left" *ngIf="!widget" i18n="liquid.bitcoin-funding-redeem">BTC Funding / Redeem</th>
 | 
			
		||||
          <th class="output text-left" *ngIf="!widget" i18n="liquid.fund-redemption-tx">Fund / Redemption Tx</th>
 | 
			
		||||
          <th class="address text-left" *ngIf="!widget" i18n="liquid.bitcoin-address">BTC Address</th>
 | 
			
		||||
          <th class="timestamp text-right" i18n="shared.date" [ngClass]="{'widget': widget}">Date</th>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody *ngIf="recentPegs$ | async as pegs; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
 | 
			
		||||
@ -23,17 +22,17 @@
 | 
			
		||||
              <td class="transaction text-left widget">
 | 
			
		||||
                <ng-container *ngIf="peg.amount > 0">
 | 
			
		||||
                  <a [routerLink]="['/tx' | relativeUrl, peg.txid]" [fragment]="'vin=' + peg.txindex">
 | 
			
		||||
                    <app-truncate [text]="peg.txid" [lastChars]="6"></app-truncate>
 | 
			
		||||
                    <app-truncate [text]="peg.txid"></app-truncate>
 | 
			
		||||
                  </a>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
                <ng-container *ngIf="peg.amount < 0">
 | 
			
		||||
                  <a [routerLink]="['/tx' | relativeUrl, peg.txid]" [fragment]="'vout=' + peg.txindex">
 | 
			
		||||
                    <app-truncate [text]="peg.txid" [lastChars]="6"></app-truncate>
 | 
			
		||||
                    <app-truncate [text]="peg.txid"></app-truncate>
 | 
			
		||||
                  </a>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="amount text-right" [ngClass]="{'credit': peg.amount > 0, 'debit': peg.amount < 0}">
 | 
			
		||||
                {{ peg.amount > 0 ? '+' : '-' }}<app-amount [satoshis]="peg.amount" [noFiat]="true" [forceBtc]="true"></app-amount>
 | 
			
		||||
              <td class="amount text-right widget" [ngClass]="{'credit': peg.amount > 0, 'debit': peg.amount < 0}">
 | 
			
		||||
                <app-amount [satoshis]="peg.amount" [noFiat]="true" [forceBtc]="true" [addPlus]="true"></app-amount>
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="timestamp text-right widget">
 | 
			
		||||
                <app-time kind="since" [time]="peg.blocktime"></app-time>
 | 
			
		||||
@ -55,7 +54,7 @@
 | 
			
		||||
                </ng-container>
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="amount text-right" [ngClass]="{'credit': peg.amount > 0, 'debit': peg.amount < 0}">
 | 
			
		||||
                {{ peg.amount > 0 ? '+' : '-' }}<app-amount [satoshis]="peg.amount" [noFiat]="true" [forceBtc]="true"></app-amount>
 | 
			
		||||
                <app-amount [satoshis]="peg.amount" [noFiat]="true" [forceBtc]="true" [addPlus]="true"></app-amount>
 | 
			
		||||
              </td>
 | 
			
		||||
              <td class="output text-left">
 | 
			
		||||
                <ng-container *ngIf="peg.bitcointxid; else redeemInProgress">
 | 
			
		||||
@ -65,7 +64,7 @@
 | 
			
		||||
                </ng-container>
 | 
			
		||||
                <ng-template #redeemInProgress>
 | 
			
		||||
                  <ng-container *ngIf="peg.bitcoinaddress; else noRedeem">
 | 
			
		||||
                    <span class="text-muted" i18n="liquid.redemption-in-progress">BTC Redemption in progress...</span>
 | 
			
		||||
                    <i><span class="text-muted" i18n="liquid.redemption-in-progress">Peg out in progress...</span></i>
 | 
			
		||||
                  </ng-container>
 | 
			
		||||
                </ng-template>
 | 
			
		||||
              </td>
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ tr, td, th {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  max-width: 160px;
 | 
			
		||||
  max-width: 120px;
 | 
			
		||||
}
 | 
			
		||||
.transaction.widget {
 | 
			
		||||
  width: 40%;
 | 
			
		||||
@ -62,7 +62,7 @@ tr, td, th {
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  max-width: 160px;
 | 
			
		||||
  @media (max-width: 840px) {
 | 
			
		||||
  @media (max-width: 825px) {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -72,7 +72,7 @@ tr, td, th {
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  max-width: 160px;
 | 
			
		||||
  @media (max-width: 527px) {
 | 
			
		||||
  @media (max-width: 840px) {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -14,8 +14,8 @@ import { WebsocketService } from '../../../services/websocket.service';
 | 
			
		||||
})
 | 
			
		||||
export class RecentPegsListComponent implements OnInit {
 | 
			
		||||
  @Input() widget: boolean = false;
 | 
			
		||||
  @Input() recentPegIns$: Observable<RecentPeg[]>;
 | 
			
		||||
  @Input() recentPegOuts$: Observable<RecentPeg[]>;
 | 
			
		||||
  @Input() recentPegIns$: Observable<RecentPeg[]> = of([]);
 | 
			
		||||
  @Input() recentPegOuts$: Observable<RecentPeg[]> = of([]);
 | 
			
		||||
 | 
			
		||||
  env: Env;
 | 
			
		||||
  isLoading = true;
 | 
			
		||||
@ -133,6 +133,7 @@ export class RecentPegsListComponent implements OnInit {
 | 
			
		||||
          return b.blocktime - a.blocktime;
 | 
			
		||||
        });
 | 
			
		||||
      }),
 | 
			
		||||
      filter(recentPegs => recentPegs.length > 0),
 | 
			
		||||
      tap(_ => this.isLoading = false),
 | 
			
		||||
      share()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<div class="fee-estimation-container">
 | 
			
		||||
  <div class="item">
 | 
			
		||||
    <a class="title-link" [routerLink]="['/audit/pegs' | relativeUrl]">
 | 
			
		||||
      <h5 class="card-title"><ng-container i18n="liquid.recent-pegs">Recent Peg-In / Out's</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
 | 
			
		||||
      <h5 class="card-title"><ng-container i18n="liquid.recent-pegs">Recent Peg-In / Out's</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
 | 
			
		||||
    </a>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@
 | 
			
		||||
        <div class="card-text">
 | 
			
		||||
          <div class="fee-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></div>
 | 
			
		||||
          <span class="fiat">
 | 
			
		||||
            <span>As of block <a [routerLink]="['/block', currentPeg.hash]" target="_blank">{{ currentPeg.lastBlockUpdate }}</a></span>
 | 
			
		||||
            <span>As of block <a [routerLink]="['/block', currentPeg.hash]">{{ currentPeg.lastBlockUpdate }}</a></span>
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@ -276,7 +276,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="item">
 | 
			
		||||
      <a class="title-link" [routerLink]="['/audit' | relativeUrl]">
 | 
			
		||||
        <h5 class="card-title"><ng-container i18n="dashboard.btc-reserves">BTC Reserves</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
 | 
			
		||||
        <h5 class="card-title"><ng-container i18n="dashboard.btc-reserves">BTC Reserves</ng-container> <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="font-size: 13px; color: #4a68b9"></fa-icon></h5>
 | 
			
		||||
      </a>
 | 
			
		||||
      <ng-container *ngIf="(currentReserves$ | async) as currentReserves; else loadingTransactions">
 | 
			
		||||
        <p i18n-ngbTooltip="liquid.last-bitcoin-audit-block" [ngbTooltip]="'BTC reserves last updated at Bitcoin block ' + (currentReserves.lastBlockUpdate)" placement="top" class="card-text">{{ +(currentReserves.amount) / 100000000 | number: '1.2-2' }} <span class="bitcoin-color">BTC</span></p>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user