Compare commits
49 Commits
dependabot
...
knorrium/t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad6b146aa0 | ||
|
|
98e9d1a6c3 | ||
|
|
c4577b8c09 | ||
|
|
95c4da51ed | ||
|
|
2e336d7ad1 | ||
|
|
903ff1ea66 | ||
|
|
f02d8e0626 | ||
|
|
ea04ea0048 | ||
|
|
d91c6bceed | ||
|
|
aa5355e93d | ||
|
|
9672928da9 | ||
|
|
d189e70817 | ||
|
|
bb91f9142e | ||
|
|
66f90cb0bd | ||
|
|
f1572f0038 | ||
|
|
c3963d6a0d | ||
|
|
1dd86df3e0 | ||
|
|
c8d443bea7 | ||
|
|
575fc737ca | ||
|
|
ebd4408b8d | ||
|
|
d6d8c85419 | ||
|
|
fbb409e17b | ||
|
|
b6d03953b9 | ||
|
|
d45104f7c9 | ||
|
|
d175c34e5b | ||
|
|
2bf2440e3a | ||
|
|
124c0acbe1 | ||
|
|
69c5a2fb5a | ||
|
|
c4f08e0d41 | ||
|
|
87ee14f189 | ||
|
|
122b4b05c4 | ||
|
|
09f7dddf14 | ||
|
|
f7ad45939c | ||
|
|
7b6246a035 | ||
|
|
a8d2138404 | ||
|
|
0b608c96dd | ||
|
|
a0402b92f9 | ||
|
|
14e05b43c7 | ||
|
|
fc8f8abc7e | ||
|
|
a1f1b09c55 | ||
|
|
ad2d7af084 | ||
|
|
9d3044efae | ||
|
|
bac21afa54 | ||
|
|
f98bb675e7 | ||
|
|
3e057f2db1 | ||
|
|
4fbdf92f0c | ||
|
|
563def45d8 | ||
|
|
a9a1ff68ab | ||
|
|
517e82ec8b |
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"NETWORKS": {
|
||||
"TESTNET": {
|
||||
"ENABLED": true,
|
||||
"WEIGHT": "__NETWORKS_TESTNET_WEIGHT__"
|
||||
},
|
||||
"MAINNET": {
|
||||
"ENABLED": true,
|
||||
"WEIGHT": "__NETWORKS_MAINNET_WEIGHT__"
|
||||
}
|
||||
},
|
||||
"MEMPOOL": {
|
||||
"ENABLED": true,
|
||||
"OFFICIAL": false,
|
||||
|
||||
@@ -157,6 +157,17 @@ describe('Mempool Backend Config', () => {
|
||||
PAID: false,
|
||||
API_KEY: '',
|
||||
});
|
||||
|
||||
expect(config.NETWORKS).toStrictEqual({
|
||||
MAINNET: {
|
||||
ENABLED: true,
|
||||
WEIGHT: 'foo'
|
||||
},
|
||||
TESTNET: {
|
||||
ENABLED: false,
|
||||
WEIGHT: 'bar'
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -190,6 +201,8 @@ describe('Mempool Backend Config', () => {
|
||||
expect(config.MEMPOOL_SERVICES).toStrictEqual(fixture.MEMPOOL_SERVICES);
|
||||
|
||||
expect(config.REDIS).toStrictEqual(fixture.REDIS);
|
||||
|
||||
expect(config.NETWORKS).toStrictEqual(fixture.NETWORKS);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -197,6 +210,22 @@ describe('Mempool Backend Config', () => {
|
||||
jest.isolateModules(() => {
|
||||
const startSh = fs.readFileSync(`${__dirname}/../../../docker/backend/start.sh`, 'utf-8');
|
||||
const fixture = JSON.parse(fs.readFileSync(`${__dirname}/../__fixtures__/mempool-config.template.json`, 'utf8'));
|
||||
|
||||
function generateKeys(obj, prefix = '') {
|
||||
let keys: string[] = [];
|
||||
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
const newPrefix = prefix ? `${prefix}_${key}` : key;
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
keys = keys.concat(generateKeys(obj[key], newPrefix));
|
||||
} else {
|
||||
keys.push(`${newPrefix}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
function parseJson(jsonObj, root?) {
|
||||
for (const [key, value] of Object.entries(jsonObj)) {
|
||||
@@ -208,6 +237,31 @@ describe('Mempool Backend Config', () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (root === 'NETWORKS') {
|
||||
const keys = generateKeys(value);
|
||||
keys.forEach(item => {
|
||||
const replaceStr = `__${root}_${key}_${item}__`;
|
||||
const envVarStr = `${root}_${key}_${item}`;
|
||||
|
||||
let defaultEntry = replaceStr + '=' + '\\${' + envVarStr + ':=(.*)' + '}';
|
||||
if (process.env.CI) {
|
||||
console.log(`looking for ${defaultEntry} in the start.sh script`);
|
||||
}
|
||||
|
||||
const re = new RegExp(defaultEntry);
|
||||
expect(startSh).toMatch(re);
|
||||
|
||||
const sedStr = 'sed -i "s!' + replaceStr + '!${' + replaceStr + '}!g" mempool-config.json';
|
||||
if (process.env.CI) {
|
||||
console.log(`looking for ${sedStr} in the start.sh script`);
|
||||
}
|
||||
expect(startSh).toContain(sedStr);
|
||||
|
||||
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (root) {
|
||||
//The flattened string, i.e, __MEMPOOL_ENABLED__
|
||||
const replaceStr = `${root ? '__' + root + '_' : '__'}${key}__`;
|
||||
@@ -264,6 +318,8 @@ describe('Mempool Backend Config', () => {
|
||||
const replaceStr = `${root ? '__' + root + '_' : '__'}${key}__`;
|
||||
expect(dockerJson).toContain(`"${key}": ${replaceStr}`);
|
||||
break;
|
||||
} else if (typeof value === 'object') {
|
||||
break;
|
||||
} else {
|
||||
//Check for top level config keys
|
||||
expect(dockerJson).toContain(`"${key}"`);
|
||||
|
||||
@@ -14,6 +14,7 @@ class AccelerationRoutes {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history', this.$getAcceleratorAccelerationsHistory.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/history/aggregated', this.$getAcceleratorAccelerationsHistoryAggregated.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/accelerations/stats', this.$getAcceleratorAccelerationsStats.bind(this))
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'services/accelerator/estimate', this.$getAcceleratorEstimate.bind(this))
|
||||
;
|
||||
}
|
||||
|
||||
@@ -64,6 +65,20 @@ class AccelerationRoutes {
|
||||
res.status(500).end();
|
||||
}
|
||||
}
|
||||
|
||||
private async $getAcceleratorEstimate(req: Request, res: Response): Promise<void> {
|
||||
const url = `${config.MEMPOOL_SERVICES.API}/${req.originalUrl.replace('/api/v1/services/', '')}`;
|
||||
try {
|
||||
const response = await axios.post(url, req.body, { responseType: 'stream', timeout: 10000 });
|
||||
for (const key in response.headers) {
|
||||
res.setHeader(key, response.headers[key]);
|
||||
}
|
||||
response.data.pipe(res);
|
||||
} catch (e) {
|
||||
logger.err(`Unable to get acceleration estimate from ${url} in $getAcceleratorEstimate(), ${e}`, this.tag);
|
||||
res.status(500).end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new AccelerationRoutes();
|
||||
@@ -165,13 +165,21 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
const mp = mempool.getMempool();
|
||||
for (const tx in mp) {
|
||||
for (const vout of mp[tx].vout) {
|
||||
if (vout.scriptpubkey_address.indexOf(prefix) === 0) {
|
||||
if (vout.scriptpubkey_address?.indexOf(prefix) === 0) {
|
||||
found[vout.scriptpubkey_address] = '';
|
||||
if (Object.keys(found).length >= 10) {
|
||||
return Object.keys(found);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const vin of mp[tx].vin) {
|
||||
if (vin.prevout?.scriptpubkey_address?.indexOf(prefix) === 0) {
|
||||
found[vin.prevout?.scriptpubkey_address] = '';
|
||||
if (Object.keys(found).length >= 10) {
|
||||
return Object.keys(found);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(found);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export namespace IEsploraApi {
|
||||
scriptpubkey: string;
|
||||
scriptpubkey_asm: string;
|
||||
scriptpubkey_type: string;
|
||||
scriptpubkey_address: string;
|
||||
scriptpubkey_address?: string;
|
||||
value: number;
|
||||
// Elements
|
||||
valuecommitment?: number;
|
||||
|
||||
@@ -724,7 +724,7 @@ class Blocks {
|
||||
}
|
||||
|
||||
const coinbaseTx = await bitcoinApi.$getCoinbaseTx(hash);
|
||||
const addresses = new Set<string>(coinbaseTx.vout.map(v => v.scriptpubkey_address).filter(a => a));
|
||||
const addresses = new Set<string>(coinbaseTx.vout.map(v => v.scriptpubkey_address).filter(a => a) as string[]);
|
||||
await blocksRepository.$saveCoinbaseAddresses(hash, [...addresses]);
|
||||
|
||||
// Logging
|
||||
|
||||
@@ -160,6 +160,16 @@ interface IConfig {
|
||||
PAID: boolean;
|
||||
API_KEY: string;
|
||||
},
|
||||
NETWORKS: {
|
||||
MAINNET: {
|
||||
ENABLED: boolean;
|
||||
WEIGHT: 'foo';
|
||||
},
|
||||
TESTNET: {
|
||||
ENABLED: boolean;
|
||||
WEIGHT: 'bar'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const defaults: IConfig = {
|
||||
@@ -320,6 +330,16 @@ const defaults: IConfig = {
|
||||
'PAID': false,
|
||||
'API_KEY': '',
|
||||
},
|
||||
'NETWORKS': {
|
||||
'MAINNET': {
|
||||
'ENABLED': true,
|
||||
'WEIGHT': 'foo',
|
||||
},
|
||||
'TESTNET': {
|
||||
'ENABLED': false,
|
||||
'WEIGHT': 'bar'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Config implements IConfig {
|
||||
@@ -341,6 +361,7 @@ class Config implements IConfig {
|
||||
MEMPOOL_SERVICES: IConfig['MEMPOOL_SERVICES'];
|
||||
REDIS: IConfig['REDIS'];
|
||||
FIAT_PRICE: IConfig['FIAT_PRICE'];
|
||||
NETWORKS: IConfig['NETWORKS'];
|
||||
|
||||
constructor() {
|
||||
const configs = this.merge(configFromFile, defaults);
|
||||
@@ -362,6 +383,7 @@ class Config implements IConfig {
|
||||
this.MEMPOOL_SERVICES = configs.MEMPOOL_SERVICES;
|
||||
this.REDIS = configs.REDIS;
|
||||
this.FIAT_PRICE = configs.FIAT_PRICE;
|
||||
this.NETWORKS = configs.NETWORKS;
|
||||
}
|
||||
|
||||
merge = (...objects: object[]): IConfig => {
|
||||
|
||||
@@ -155,5 +155,15 @@
|
||||
"ENABLED": __FIAT_PRICE_ENABLED__,
|
||||
"PAID": __FIAT_PRICE_PAID__,
|
||||
"API_KEY": "__FIAT_PRICE_API_KEY__"
|
||||
},
|
||||
"NETWORKS": {
|
||||
"MAINNET": {
|
||||
"ENABLED": __NETWORKS_MAINNET_ENABLED__,
|
||||
"WEIGHT": "__NETWORKS_MAINNET_WEIGHT__,"
|
||||
},
|
||||
"TESTNET": {
|
||||
"ENABLED": __NETWORKS_TESTNET_ENABLED__,
|
||||
"WEIGHT": "__NETWORKS_TESTNET_WEIGHT__"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,12 @@ __FIAT_PRICE_ENABLED__=${FIAT_PRICE_ENABLED:=true}
|
||||
__FIAT_PRICE_PAID__=${FIAT_PRICE_PAID:=false}
|
||||
__FIAT_PRICE_API_KEY__=${FIAT_PRICE_API_KEY:=""}
|
||||
|
||||
# NETWORKS
|
||||
__NETWORKS_TESTNET_ENABLED__=${NETWORKS_TESTNET_ENABLED:=true}
|
||||
__NETWORKS_TESTNET_WEIGHT__=${NETWORKS_TESTNET_WEIGHT:="0.1"}
|
||||
__NETWORKS_MAINNET_ENABLED__=${NETWORKS_MAINNET_ENABLED:=true}
|
||||
__NETWORKS_MAINNET_WEIGHT__=${NETWORKS_MAINNET_WEIGHT:="0.2"}
|
||||
|
||||
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
||||
|
||||
sed -i "s!__MEMPOOL_NETWORK__!${__MEMPOOL_NETWORK__}!g" mempool-config.json
|
||||
@@ -306,4 +312,10 @@ sed -i "s!__FIAT_PRICE_ENABLED__!${__FIAT_PRICE_ENABLED__}!g" mempool-config.jso
|
||||
sed -i "s!__FIAT_PRICE_PAID__!${__FIAT_PRICE_PAID__}!g" mempool-config.json
|
||||
sed -i "s!__FIAT_PRICE_API_KEY__!${__FIAT_PRICE_API_KEY__}!g" mempool-config.json
|
||||
|
||||
# NETWORKS
|
||||
sed -i "s!__NETWORKS_TESTNET_ENABLED__!${__NETWORKS_TESTNET_ENABLED__}!g" mempool-config.json
|
||||
sed -i "s!__NETWORKS_TESTNET_WEIGHT__!${__NETWORKS_TESTNET_WEIGHT__}!g" mempool-config.json
|
||||
sed -i "s!__NETWORKS_MAINNET_ENABLED__!${__NETWORKS_MAINNET_ENABLED__}!g" mempool-config.json
|
||||
sed -i "s!__NETWORKS_MAINNET_WEIGHT__!${__NETWORKS_MAINNET_WEIGHT__}!g" mempool-config.json
|
||||
|
||||
node /backend/package/index.js
|
||||
|
||||
@@ -18,6 +18,7 @@ fi
|
||||
|
||||
__MAINNET_ENABLED__=${MAINNET_ENABLED:=true}
|
||||
__TESTNET_ENABLED__=${TESTNET_ENABLED:=false}
|
||||
__TESTNET4_ENABLED__=${TESTNET_ENABLED:=false}
|
||||
__SIGNET_ENABLED__=${SIGNET_ENABLED:=false}
|
||||
__LIQUID_ENABLED__=${LIQUID_ENABLED:=false}
|
||||
__LIQUID_TESTNET_ENABLED__=${LIQUID_TESTNET_ENABLED:=false}
|
||||
@@ -46,6 +47,7 @@ __ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false}
|
||||
# Export as environment variables to be used by envsubst
|
||||
export __MAINNET_ENABLED__
|
||||
export __TESTNET_ENABLED__
|
||||
export __TESTNET4_ENABLED__
|
||||
export __SIGNET_ENABLED__
|
||||
export __LIQUID_ENABLED__
|
||||
export __LIQUID_TESTNET_ENABLED__
|
||||
|
||||
@@ -53,13 +53,26 @@
|
||||
<span>Spiral</span>
|
||||
</a>
|
||||
<a href="https://foundrydigital.com/" target="_blank" title="Foundry">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-10 -10 100 100" class="image">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-186.000000, -2316.000000)">
|
||||
<g transform="translate(186.000000, 2316.000000)">
|
||||
<rect id="" fill="#023D32" x="-10" y="-10" width="100" height="100" rx="8"></rect>
|
||||
<path d="M61.6666667,9.16666667 L61.6666667,17.0041667 L46.2625,17.0041667 C46.2625,17.0041667 44.1666667,16.6666667 44.1666667,18.3333333 L44.1666667,25.8025 L61.6666667,25.8025 L61.6666667,34.7391667 L44.1666667,34.7391667 L44.1666667,70.5575 L31.7825,70.5575 L31.7825,35 L19.1666667,35 L19.1666667,25.595 L31.6666667,25.595 L31.6666667,17.5 C31.6666667,17.5 32.5,9.16666667 40.4166667,9.16666667 L61.6666667,9.16666667 Z" id="Fill-1" fill="#86E2A0"></path>
|
||||
</g>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="b" data-name="Layer 2" style="zoom: 1;" width="32" height="76" viewBox="0 0 32 76">
|
||||
<defs>
|
||||
<style>
|
||||
.d {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.e {
|
||||
fill: #ff8200;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="c" data-name="b">
|
||||
<circle class="e" cx="24" cy="32" r="8" />
|
||||
<circle class="e" cx="24" cy="56" r="8" />
|
||||
<circle class="e" cx="8" cy="68" r="8" />
|
||||
<g>
|
||||
<circle class="d" cx="24" cy="8" r="8" />
|
||||
<circle class="d" cx="8" cy="20" r="8" />
|
||||
<circle class="d" cx="8" cy="44" r="8" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -259,22 +272,10 @@
|
||||
<img class="image" src="/resources/profile/bisq_network.png" />
|
||||
<span>Bisq</span>
|
||||
</a>
|
||||
<a href="https://github.com/BlueWallet/BlueWallet" target="_blank" title="BlueWallet">
|
||||
<img class="image" src="/resources/profile/bluewallet.png" />
|
||||
<span>BlueWallet</span>
|
||||
</a>
|
||||
<a href="https://github.com/muun/apollo" target="_blank" title="Muun Wallet">
|
||||
<img class="image" src="/resources/profile/muun.png" />
|
||||
<span>Muun</span>
|
||||
</a>
|
||||
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
|
||||
<img class="image" src="/resources/profile/electrum.png" />
|
||||
<span>Electrum</span>
|
||||
</a>
|
||||
<a href="https://github.com/cryptoadvance/specter-desktop" target="_blank" title="Specter Wallet">
|
||||
<img class="image" src="/resources/profile/specter.png" />
|
||||
<span>Specter</span>
|
||||
</a>
|
||||
<a href="https://github.com/sparrowwallet/sparrow" target="_blank" title="Sparrow Wallet">
|
||||
<img class="image" src="/resources/profile/sparrow.png" />
|
||||
<span>Sparrow</span>
|
||||
@@ -283,21 +284,37 @@
|
||||
<img class="image not-rounded" src="/resources/profile/phoenix.svg" />
|
||||
<span>Phoenix</span>
|
||||
</a>
|
||||
<a href="https://github.com/lnbits/lnbits-legend" target="_blank" title="LNbits">
|
||||
<img class="image" src="/resources/profile/lnbits.svg" />
|
||||
<span>LNBits</span>
|
||||
<a href="http://github.com/COLDCARD" target="_blank" title="COLDCARD">
|
||||
<img class="image coldcard" src="/resources/profile/coldcard.png" />
|
||||
<span>COLDCARD</span>
|
||||
</a>
|
||||
<a href="https://github.com/layer2tech/mercury-wallet" target="_blank" title="Mercury Wallet">
|
||||
<img class="image" src="/resources/profile/mercury.svg" />
|
||||
<span>Mercury</span>
|
||||
<a href="https://github.com/ZeusLN/zeus" target="_blank" title="ZEUS">
|
||||
<img class="image" src="/resources/profile/zeus.png" />
|
||||
<span>ZEUS</span>
|
||||
</a>
|
||||
<a href="https://github.com/MutinyWallet" target="_blank" title="Mutiny">
|
||||
<img class="image not-rounded" src="/resources/profile/mutiny.svg" />
|
||||
<span>Mutiny</span>
|
||||
</a>
|
||||
<a href="https://github.com/hsjoberg/blixt-wallet" target="_blank" title="Blixt Wallet">
|
||||
<img class="image" src="/resources/profile/blixt.png" />
|
||||
<span>Blixt</span>
|
||||
</a>
|
||||
<a href="https://github.com/ZeusLN/zeus" target="_blank" title="ZEUS">
|
||||
<img class="image" src="/resources/profile/zeus.png" />
|
||||
<span>ZEUS</span>
|
||||
<a href="https://github.com/nunchuk-io" target="_blank" title="Nunchuck">
|
||||
<img class="image" src="/resources/profile/nunchuk.svg" />
|
||||
<span>Nunchuk</span>
|
||||
</a>
|
||||
<a href="https://github.com/BlueWallet/BlueWallet" target="_blank" title="BlueWallet">
|
||||
<img class="image" src="/resources/profile/bluewallet.png" />
|
||||
<span>BlueWallet</span>
|
||||
</a>
|
||||
<a href="https://github.com/BoltzExchange" target="_blank" title="Boltz">
|
||||
<img class="image" src="/resources/profile/boltz.svg" />
|
||||
<span>Boltz</span>
|
||||
</a>
|
||||
<a href="https://github.com/lnbits/lnbits-legend" target="_blank" title="LNbits">
|
||||
<img class="image" src="/resources/profile/lnbits.svg" />
|
||||
<span>LNBits</span>
|
||||
</a>
|
||||
<a href="https://github.com/vulpemventures/marina" target="_blank" title="Marina Wallet">
|
||||
<img class="image" src="/resources/profile/marina.svg" />
|
||||
@@ -307,13 +324,9 @@
|
||||
<img class="image" src="/resources/profile/schildbach.svg" />
|
||||
<span>Schildbach</span>
|
||||
</a>
|
||||
<a href="https://github.com/nunchuk-io" target="_blank" title="Nunchuck">
|
||||
<img class="image" src="/resources/profile/nunchuk.svg" />
|
||||
<span>Nunchuk</span>
|
||||
</a>
|
||||
<a href="https://github.com/bitcoin-s/bitcoin-s" target="_blank" title="bitcoin-s">
|
||||
<img class="image" src="/resources/profile/bitcoin-s.svg" />
|
||||
<span>bitcoin-s</span>
|
||||
<a href="https://github.com/cryptoadvance/specter-desktop" target="_blank" title="Specter Wallet">
|
||||
<img class="image" src="/resources/profile/specter.png" />
|
||||
<span>Specter</span>
|
||||
</a>
|
||||
<a href="https://github.com/EdgeApp" target="_blank" title="Edge">
|
||||
<img class="image not-rounded" src="/resources/profile/edge.svg" />
|
||||
@@ -323,13 +336,13 @@
|
||||
<img class="image" src="/resources/profile/galoy.svg" />
|
||||
<span>Galoy</span>
|
||||
</a>
|
||||
<a href="https://github.com/BoltzExchange" target="_blank" title="Boltz">
|
||||
<img class="image" src="/resources/profile/boltz.svg" />
|
||||
<span>Boltz</span>
|
||||
<a href="https://github.com/muun/apollo" target="_blank" title="Muun Wallet">
|
||||
<img class="image" src="/resources/profile/muun.png" />
|
||||
<span>Muun</span>
|
||||
</a>
|
||||
<a href="https://github.com/MutinyWallet" target="_blank" title="Mutiny">
|
||||
<img class="image not-rounded" src="/resources/profile/mutiny.svg" />
|
||||
<span>Mutiny</span>
|
||||
<a href="https://github.com/bitcoin-s/bitcoin-s" target="_blank" title="bitcoin-s">
|
||||
<img class="image" src="/resources/profile/bitcoin-s.svg" />
|
||||
<span>bitcoin-s</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -156,6 +156,12 @@
|
||||
}
|
||||
img, svg {
|
||||
margin: 40px 29px 10px;
|
||||
&.image.coldcard {
|
||||
border-radius: 0;
|
||||
width: auto;
|
||||
max-height: 50px;
|
||||
margin: 40px 29px 14px 29px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<input type="radio" class="form-check-input" id="accelerate" name="accelerate" (change)="selectedOptionChanged($event)">
|
||||
<label class="form-check-label d-flex flex-column" for="accelerate">
|
||||
<span class="font-weight-bold">Accelerate</span>
|
||||
<span style="color: rgb(186, 186, 186); font-size: 14px;">Confirmation expected within ~30 minutes<br>
|
||||
<span style="color: rgb(186, 186, 186); font-size: 14px;" *ngIf="(etaInfo$ | async) as etaInfo">Confirmation expected <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time><br>
|
||||
@if (!calculating) {
|
||||
<app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small> <span class="symbol" i18n="shared.sats">sats</span></span>)
|
||||
} @else {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges } from '@angular/core';
|
||||
import { Subscription, tap, of, catchError } from 'rxjs';
|
||||
import { Subscription, tap, of, catchError, Observable } from 'rxjs';
|
||||
import { ServicesApiServices } from '../../services/services-api.service';
|
||||
import { nextRoundNumber } from '../../shared/common.utils';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { AudioService } from '../../services/audio.service';
|
||||
import { AccelerationEstimate } from '../accelerate-preview/accelerate-preview.component';
|
||||
import { EtaService } from '../../services/eta.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-accelerate-checkout',
|
||||
@@ -24,8 +26,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
square: { appId: string, locationId: string};
|
||||
accelerationUUID: string;
|
||||
estimateSubscription: Subscription;
|
||||
estimate: AccelerationEstimate;
|
||||
maxBidBoost: number; // sats
|
||||
cost: number; // sats
|
||||
etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>;
|
||||
|
||||
// square
|
||||
loadingCashapp = false;
|
||||
@@ -39,6 +43,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private servicesApiService: ServicesApiServices,
|
||||
private stateService: StateService,
|
||||
private etaService: EtaService,
|
||||
private audioService: AudioService,
|
||||
private cd: ChangeDetectorRef
|
||||
) {
|
||||
@@ -59,7 +64,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
locationId: ids.squareLocationId
|
||||
};
|
||||
if (this.step === 'cta') {
|
||||
this.estimate();
|
||||
this.fetchEstimate();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -99,7 +104,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Accelerator
|
||||
*/
|
||||
estimate() {
|
||||
fetchEstimate() {
|
||||
if (this.estimateSubscription) {
|
||||
this.estimateSubscription.unsubscribe();
|
||||
}
|
||||
@@ -110,16 +115,17 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
if (response.status === 204) {
|
||||
this.error = `cannot_accelerate_tx`;
|
||||
} else {
|
||||
const estimation = response.body;
|
||||
if (!estimation) {
|
||||
this.estimate = response.body;
|
||||
if (!this.estimate) {
|
||||
this.error = `cannot_accelerate_tx`;
|
||||
return;
|
||||
}
|
||||
// Make min extra fee at least 50% of the current tx fee
|
||||
const minExtraBoost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee));
|
||||
const minExtraBoost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee));
|
||||
const DEFAULT_BID_RATIO = 1.5;
|
||||
this.maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO;
|
||||
this.cost = this.maxBidBoost + estimation.mempoolBaseFee + estimation.vsizeFee;
|
||||
this.cost = this.maxBidBoost + this.estimate.mempoolBaseFee + this.estimate.vsizeFee;
|
||||
this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate);
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
width: 120px;
|
||||
margin-left: 4em;
|
||||
margin-right: 1.5em;
|
||||
padding-bottom: 63px;
|
||||
|
||||
.column {
|
||||
width: 100%;
|
||||
|
||||
@@ -32,54 +32,56 @@
|
||||
<div class="alert alert-mempool">You are currently on the waitlist</div>
|
||||
</div>
|
||||
|
||||
<h5 i18n="accelerator.your-transaction">Your transaction</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
|
||||
<ng-container i18n="accelerator.plus-unconfirmed-ancestors">Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor(s)</ng-container>
|
||||
</small>
|
||||
<table class="table table-borderless table-border table-dark table-background table-accelerator">
|
||||
<tbody>
|
||||
<tr class="group-first">
|
||||
<td class="item" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||
<td style="text-align: end;" [innerHTML]="'‎' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info" colspan=3>
|
||||
<i><small i18n="accelerator.transaction-vbytes-size-description">Size in vbytes of this transaction (including unconfirmed ancestors)</small></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="item" i18n="accelerator.in-band-fees">In-band fees</td>
|
||||
<td style="text-align: end;">
|
||||
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats">sats</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
<td class="info" colspan=3>
|
||||
<i><small i18n="accelerator.fees-already-paid-description">Fees already paid by this transaction (including unconfirmed ancestors)</small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ng-template [ngIf]="showDetails">
|
||||
<h5 i18n="accelerator.your-transaction">Your transaction</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small *ngIf="hasAncestors" class="form-text text-muted mb-2">
|
||||
<ng-container i18n="accelerator.plus-unconfirmed-ancestors">Plus {{ estimate.txSummary.ancestorCount - 1 }} unconfirmed ancestor(s)</ng-container>
|
||||
</small>
|
||||
<table class="table table-borderless table-border table-dark table-background table-accelerator">
|
||||
<tbody>
|
||||
<tr class="group-first">
|
||||
<td class="item" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||
<td style="text-align: end;" [innerHTML]="'‎' + (estimate.txSummary.effectiveVsize | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info" colspan=3>
|
||||
<i><small i18n="accelerator.transaction-vbytes-size-description">Size in vbytes of this transaction (including unconfirmed ancestors)</small></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="item" i18n="accelerator.in-band-fees">In-band fees</td>
|
||||
<td style="text-align: end;">
|
||||
{{ estimate.txSummary.effectiveFee | number : '1.0-0' }} <span class="symbol" i18n="shared.sats">sats</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
<td class="info" colspan=3>
|
||||
<i><small i18n="accelerator.fees-already-paid-description">Fees already paid by this transaction (including unconfirmed ancestors)</small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
</ng-template>
|
||||
<h5 *ngIf="estimate?.pools?.length" i18n="accelerator.how-much-faster">How much faster?</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to {{ hashratePercentage | number : '1.1-1' }}% of miners.</small>
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.time-estimate-description">This will reduce your expected waiting time until the first confirmation to <app-time kind="within" [time]="acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time></small>
|
||||
<ng-container *ngIf="(etaInfo$ | async) as etaInfo; else loadingEstimate">
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to <strong>{{ etaInfo.hashratePercentage | number : '1.1-1' }}%</strong> of miners.</small>
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.time-estimate-description">This will reduce your expected waiting time until the first confirmation to <strong><app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time></strong></small>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="col pie">
|
||||
<app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<h5 i18n="accelerator.pay-how-much">How much more are you willing to pay?</h5>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<small class="form-text text-muted mb-2" i18n="accelerator.transaction-fee-description">Choose the maximum extra transaction fee you're willing to pay.</small>
|
||||
<div class="form-group">
|
||||
<div class="fee-card">
|
||||
<div class="d-flex mb-0">
|
||||
@@ -95,13 +97,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>Acceleration summary</h5>
|
||||
<div class="row mb-3">
|
||||
<h5>Summary</h5>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-borderless table-border table-dark table-background table-accelerator">
|
||||
<tbody>
|
||||
<!-- ESTIMATED FEE -->
|
||||
<ng-container>
|
||||
<ng-template [ngIf]="showDetails">
|
||||
<tr class="group-first">
|
||||
<td class="item" i18n="accelerator.next-block-rate">Next block market rate</td>
|
||||
<td class="amt" style="font-size: 16px">
|
||||
@@ -121,25 +123,24 @@
|
||||
<span class="fiat ml-1"><app-fiat [value]="math.max(0, estimate.nextBlockFee - estimate.txSummary.effectiveFee)"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
<!-- MEMPOOL BASE FEE -->
|
||||
<tr>
|
||||
<td class="item" i18n="accelerator.mempool-accelerator-fees">Mempool Accelerator™ fees</td>
|
||||
</tr>
|
||||
<tr class="info">
|
||||
<td class="info">
|
||||
<i><small i18n="accelerator.service-fee">Accelerator Service Fee</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
+{{ estimate.mempoolBaseFee | number }}
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats">sats</span>
|
||||
<span class="fiat ml-1"><app-fiat [value]="estimate.mempoolBaseFee"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
|
||||
<!-- MEMPOOL BASE FEE -->
|
||||
<tr>
|
||||
<td class="item" i18n="accelerator.mempool-accelerator-fees">Mempool Accelerator™ fees</td>
|
||||
</tr>
|
||||
<tr class="info" [class.group-last]="!estimate.vsizeFee" [class.dashed-bottom]="!estimate.vsizeFee">
|
||||
<td class="info">
|
||||
<i><small i18n="accelerator.service-fee">Accelerator Service Fee</small></i>
|
||||
</td>
|
||||
<td class="amt">
|
||||
+{{ estimate.mempoolBaseFee | number }}
|
||||
</td>
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats">sats</span>
|
||||
<span class="fiat ml-1"><app-fiat [value]="estimate.mempoolBaseFee"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last dashed-bottom" *ngIf="estimate.vsizeFee">
|
||||
<td class="info">
|
||||
<i><small i18n="accelerator.tx-size-surcharge">Transaction Size Surcharge</small></i>
|
||||
</td>
|
||||
@@ -151,13 +152,13 @@
|
||||
<span class="fiat ml-1"><app-fiat [value]="estimate.vsizeFee"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</ng-template>
|
||||
|
||||
<!-- NEXT BLOCK ESTIMATE -->
|
||||
<ng-container>
|
||||
<tr class="group-first" style="border-top: 1px dashed grey; border-collapse: collapse;">
|
||||
<tr class="group-first">
|
||||
<td class="item">
|
||||
<b style="background-color: #5E35B1" class="p-1 pl-0" i18n="accelerator.estimated-cost">Estimated acceleration cost</b>
|
||||
<b style="background-color: #5E35B1" class="p-1 pl-0" i18n="accelerator.estimated-cost">Estimated acceleration cost</b> ~{{ estimate.targetFeeRate | number : '1.0-0' }} sat/vB
|
||||
</td>
|
||||
<td class="amt">
|
||||
<span style="background-color: #5E35B1" class="p-1 pl-0">
|
||||
@@ -169,18 +170,13 @@
|
||||
<span class="fiat ml-1"><app-fiat [value]="estimate.cost + estimate.mempoolBaseFee + estimate.vsizeFee"></app-fiat></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last" style="border-bottom: 1px solid lightgrey">
|
||||
<td class="info" colspan=3>
|
||||
<i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: estimate.targetFeeRate }"></ng-container></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
<!-- MAX COST -->
|
||||
<ng-container>
|
||||
<tr class="group-first">
|
||||
<tr class="group-first" [class.group-last]="!isLoggedIn() || estimate.userBalance >= maxCost">
|
||||
<td class="item">
|
||||
<b style="background-color: var(--primary);" class="p-1 pl-0" i18n="accelerator.maximum-cost">Maximum acceleration cost</b>
|
||||
<b style="background-color: var(--primary);" class="p-1 pl-0" i18n="accelerator.maximum-cost">Maximum acceleration cost</b>
|
||||
</td>
|
||||
<td class="amt">
|
||||
<span style="background-color: var(--primary)" class="p-1 pl-0">
|
||||
@@ -190,20 +186,15 @@
|
||||
<td class="units">
|
||||
<span class="symbol" i18n="shared.sats">sats</span>
|
||||
<span class="fiat ml-1">
|
||||
<app-fiat [value]="maxCost" [colorClass]="estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||
<app-fiat [value]="maxCost" [colorClass]="isLoggedIn() && estimate.userBalance < maxCost ? 'red-color' : 'green-color'"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="info group-last">
|
||||
<td class="info" colspan=3>
|
||||
<i><small><ng-container *ngTemplateOutlet="acceleratedTo; context: {$implicit: (estimate.txSummary.effectiveFee + userBid) / estimate.txSummary.effectiveVsize }"></ng-container></small></i>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
<!-- USER BALANCE -->
|
||||
<ng-container *ngIf="isLoggedIn() && estimate.userBalance < maxCost">
|
||||
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
||||
<tr class="group-first group-last dashed-top">
|
||||
<td class="item" i18n="accelerator.available-balance">Available balance</td>
|
||||
<td class="amt">
|
||||
{{ estimate.userBalance | number }}
|
||||
@@ -216,23 +207,21 @@
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
<!-- LOGIN CTA -->
|
||||
<ng-container *ngIf="stateService.isMempoolSpaceBuild && !isLoggedIn()">
|
||||
<ng-container *ngIf="user && estimate?.hasAccess || !isLoggedIn()">
|
||||
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
||||
<td class="item"></td>
|
||||
<td class="amt"></td>
|
||||
<td class="units d-flex">
|
||||
<a [routerLink]="['/login']" [queryParams]="{redirectTo: '/tx/' + tx.txid + '#accelerate'}" class="btn btn-purple flex-grow-1" i18n="shared.sign-in">Sign In</a>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!stateService.isMempoolSpaceBuild">
|
||||
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
||||
<td class="item"></td>
|
||||
<td class="amt"></td>
|
||||
<td class="units d-flex">
|
||||
<a [href]="'https://mempool.space/tx/' + tx.txid + '#accelerate'" class="btn btn-purple flex-grow-1" i18n="accelerator.accelerate-on-mempoolspace">Accelerate on mempool.space</a>
|
||||
<td colspan="2">
|
||||
<div class="d-flex">
|
||||
@if (isLoggedIn()) {
|
||||
@if (user && estimate.hasAccess) {
|
||||
<button class="btn btn-sm btn-primary btn-success flex-grow-1" style="width: 150px" (click)="accelerate()" i18n="transaction.accelerate|Accelerate button label">Accelerate</button>
|
||||
}
|
||||
} @else if (stateService.isMempoolSpaceBuild) {
|
||||
<a [routerLink]="['/login']" [queryParams]="{redirectTo: '/tx/' + tx.txid + '#accelerate'}" class="btn btn-purple flex-grow-1" i18n="shared.sign-in">Sign In</a>
|
||||
} @else {
|
||||
<a [href]="'https://mempool.space/tx/' + tx.txid + '#accelerate'" class="btn btn-purple flex-grow-1" i18n="accelerator.accelerate-on-mempoolspace">Accelerate on mempool.space</a>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
@@ -240,15 +229,6 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3" *ngIf="isLoggedIn()">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess">
|
||||
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()" i18n="transaction.accelerate|Accelerate button label">Accelerate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
@@ -256,6 +236,4 @@
|
||||
<ng-template #loadingEstimate>
|
||||
<div class="skeleton-loader"></div>
|
||||
<br>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #acceleratedTo let-i i18n="accelerator.accelerated-to-description">If your tx is accelerated to ~{{ i | number : '1.0-0' }} sat/vB</ng-template>
|
||||
</ng-template>
|
||||
@@ -72,11 +72,17 @@
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
}
|
||||
&.group-last {
|
||||
&.group-last, &:last-child {
|
||||
td {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
}
|
||||
&.dashed-top {
|
||||
border-top: 1px dashed grey;
|
||||
}
|
||||
&.dashed-bottom {
|
||||
border-bottom: 1px dashed grey
|
||||
}
|
||||
}
|
||||
td {
|
||||
&:first-child {
|
||||
@@ -118,4 +124,9 @@
|
||||
|
||||
.table-background {
|
||||
background-color: var(--bg);
|
||||
}
|
||||
|
||||
.col.pie {
|
||||
position: relative;
|
||||
top: -15px;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core';
|
||||
import { Subscription, catchError, of, tap } from 'rxjs';
|
||||
import { Observable, Subscription, catchError, of, tap } from 'rxjs';
|
||||
import { StorageService } from '../../services/storage.service';
|
||||
import { Transaction } from '../../interfaces/electrs.interface';
|
||||
import { nextRoundNumber } from '../../shared/common.utils';
|
||||
@@ -8,7 +8,6 @@ import { AudioService } from '../../services/audio.service';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { MiningStats } from '../../services/mining.service';
|
||||
import { EtaService } from '../../services/eta.service';
|
||||
import { DifficultyAdjustment, MempoolPosition, SinglePoolStats } from '../../interfaces/node-api.interface';
|
||||
|
||||
export type AccelerationEstimate = {
|
||||
txSummary: TxSummary;
|
||||
@@ -19,6 +18,7 @@ export type AccelerationEstimate = {
|
||||
cost: number;
|
||||
mempoolBaseFee: number;
|
||||
vsizeFee: number;
|
||||
pools: number[]
|
||||
}
|
||||
export type TxSummary = {
|
||||
txid: string; // txid of the current transaction
|
||||
@@ -44,9 +44,9 @@ export const MAX_BID_RATIO = 4;
|
||||
})
|
||||
export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() tx: Transaction;
|
||||
@Input() mempoolPosition: MempoolPosition;
|
||||
@Input() miningStats: MiningStats;
|
||||
@Input() scrollEvent: boolean;
|
||||
@Input() showDetails: boolean;
|
||||
|
||||
math = Math;
|
||||
error = '';
|
||||
@@ -54,11 +54,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
estimateSubscription: Subscription;
|
||||
accelerationSubscription: Subscription;
|
||||
difficultySubscription: Subscription;
|
||||
da: DifficultyAdjustment;
|
||||
estimate: any;
|
||||
hashratePercentage?: number;
|
||||
ETA?: number;
|
||||
acceleratedETA?: number;
|
||||
etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>;
|
||||
hasAncestors: boolean = false;
|
||||
minExtraCost = 0;
|
||||
minBidAllowed = 0;
|
||||
@@ -87,27 +84,19 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
if (this.estimateSubscription) {
|
||||
this.estimateSubscription.unsubscribe();
|
||||
}
|
||||
this.difficultySubscription.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ngOnInit(): void {
|
||||
this.accelerationUUID = window.crypto.randomUUID();
|
||||
this.difficultySubscription = this.stateService.difficultyAdjustment$.subscribe(da => {
|
||||
this.da = da;
|
||||
this.updateETA();
|
||||
})
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.scrollEvent) {
|
||||
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
||||
}
|
||||
if (changes.miningStats || changes.mempoolPosition) {
|
||||
this.updateETA();
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
ngAfterViewInit(): void {
|
||||
this.user = this.storageService.getAuth()?.user ?? null;
|
||||
|
||||
this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe(
|
||||
@@ -132,7 +121,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
}
|
||||
}
|
||||
|
||||
this.updateETA();
|
||||
this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate, this.miningStats);
|
||||
|
||||
this.hasAncestors = this.estimate.txSummary.ancestorCount > 1;
|
||||
|
||||
@@ -178,40 +167,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
updateETA(): void {
|
||||
if (!this.mempoolPosition || !this.estimate?.pools?.length || !this.miningStats || !this.da) {
|
||||
this.hashratePercentage = undefined;
|
||||
this.ETA = undefined;
|
||||
this.acceleratedETA = undefined;
|
||||
return;
|
||||
}
|
||||
const pools: { [id: number]: SinglePoolStats } = {};
|
||||
for (const pool of this.miningStats.pools) {
|
||||
pools[pool.poolUniqueId] = pool;
|
||||
}
|
||||
|
||||
let totalAcceleratedHashrate = 0;
|
||||
for (const poolId of this.estimate.pools) {
|
||||
const pool = pools[poolId];
|
||||
if (!pool) {
|
||||
continue;
|
||||
}
|
||||
totalAcceleratedHashrate += pool.lastEstimatedHashrate;
|
||||
}
|
||||
const acceleratingHashrateFraction = (totalAcceleratedHashrate / this.miningStats.lastEstimatedHashrate)
|
||||
this.hashratePercentage = acceleratingHashrateFraction * 100;
|
||||
|
||||
this.ETA = Date.now() + this.da.timeAvg * this.mempoolPosition.block;
|
||||
this.acceleratedETA = this.etaService.calculateETAFromShares([
|
||||
{ block: this.mempoolPosition.block, hashrateShare: (1 - acceleratingHashrateFraction) },
|
||||
{ block: 0, hashrateShare: acceleratingHashrateFraction },
|
||||
], this.da).time;
|
||||
}
|
||||
|
||||
/**
|
||||
* User changed his bid
|
||||
*/
|
||||
setUserBid({ fee, index }: { fee: number, index: number}) {
|
||||
setUserBid({ fee, index }: { fee: number, index: number}): void {
|
||||
if (this.estimate) {
|
||||
this.selectFeeRateIndex = index;
|
||||
this.userBid = Math.max(0, fee);
|
||||
@@ -222,12 +181,12 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
/**
|
||||
* Scroll to element id with or without setTimeout
|
||||
*/
|
||||
scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition) {
|
||||
scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition): void {
|
||||
setTimeout(() => {
|
||||
this.scrollToPreview(id, position);
|
||||
}, 100);
|
||||
}
|
||||
scrollToPreview(id: string, position: ScrollLogicalPosition) {
|
||||
scrollToPreview(id: string, position: ScrollLogicalPosition): void {
|
||||
const acceleratePreviewAnchor = document.getElementById(id);
|
||||
if (acceleratePreviewAnchor) {
|
||||
this.cd.markForCheck();
|
||||
@@ -242,7 +201,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
/**
|
||||
* Send acceleration request
|
||||
*/
|
||||
accelerate() {
|
||||
accelerate(): void {
|
||||
if (this.accelerationSubscription) {
|
||||
this.accelerationSubscription.unsubscribe();
|
||||
}
|
||||
@@ -268,7 +227,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
});
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
isLoggedIn(): boolean {
|
||||
const auth = this.storageService.getAuth();
|
||||
return auth !== null;
|
||||
}
|
||||
@@ -280,7 +239,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
||||
|
||||
|
||||
@HostListener('window:scroll', ['$event']) // for window scroll events
|
||||
onScroll() {
|
||||
onScroll(): void {
|
||||
if (this.estimate) {
|
||||
setTimeout(() => {
|
||||
this.onScroll();
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, catchError, of, switchMap, tap } from 'rxjs';
|
||||
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnDestroy, Inject, LOCALE_ID } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, Subscription, catchError, of, switchMap, tap, throttleTime } from 'rxjs';
|
||||
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
import { WebsocketService } from '../../../services/websocket.service';
|
||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||
import { SeoService } from '../../../services/seo.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-accelerations-list',
|
||||
@@ -25,25 +27,44 @@ export class AccelerationsListComponent implements OnInit, OnDestroy {
|
||||
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||
skeletonLines: number[] = [];
|
||||
pageSubject: BehaviorSubject<number> = new BehaviorSubject(this.page);
|
||||
keyNavigationSubscription: Subscription;
|
||||
dir: 'rtl' | 'ltr' = 'ltr';
|
||||
paramSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private servicesApiService: ServicesApiServices,
|
||||
private websocketService: WebsocketService,
|
||||
public stateService: StateService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private seoService: SeoService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
) {
|
||||
if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) {
|
||||
this.dir = 'rtl';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.widget) {
|
||||
this.websocketService.want(['blocks']);
|
||||
this.seoService.setTitle($localize`:@@02573b6980a2d611b4361a2595a4447e390058cd:Accelerations`);
|
||||
}
|
||||
|
||||
this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()];
|
||||
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||
|
||||
this.paramSubscription = this.route.params.pipe(
|
||||
tap(params => {
|
||||
this.page = +params['page'] || 1;
|
||||
this.pageSubject.next(this.page);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.accelerationList$ = this.pageSubject.pipe(
|
||||
switchMap((page) => {
|
||||
this.isLoading = true;
|
||||
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.stateService.liveAccelerations$ : this.servicesApiService.getAccelerationHistoryObserveResponse$({ page: page }));
|
||||
if (!this.accelerations$ && this.pending) {
|
||||
this.websocketService.ensureTrackAccelerations();
|
||||
@@ -79,10 +100,30 @@ export class AccelerationsListComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
this.keyNavigationSubscription = this.stateService.keyNavigation$.pipe(
|
||||
tap((event) => {
|
||||
const prevKey = this.dir === 'ltr' ? 'ArrowLeft' : 'ArrowRight';
|
||||
const nextKey = this.dir === 'ltr' ? 'ArrowRight' : 'ArrowLeft';
|
||||
if (event.key === prevKey && this.page > 1) {
|
||||
this.page--;
|
||||
this.isLoading = true;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
if (event.key === nextKey && this.page * 15 < this.accelerationCount) {
|
||||
this.page++;
|
||||
this.isLoading = true;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}),
|
||||
throttleTime(1000, undefined, { leading: true, trailing: true }),
|
||||
).subscribe(() => {
|
||||
this.pageChange(this.page);
|
||||
});
|
||||
}
|
||||
|
||||
pageChange(page: number): void {
|
||||
this.pageSubject.next(page);
|
||||
this.router.navigate(['acceleration', 'list', page]);
|
||||
}
|
||||
|
||||
trackByBlock(index: number, block: BlockExtended): number {
|
||||
@@ -91,5 +132,7 @@ export class AccelerationsListComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.websocketService.stopTrackAccelerations();
|
||||
this.paramSubscription?.unsubscribe();
|
||||
this.keyNavigationSubscription?.unsubscribe();
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,11 @@
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="td-width field-label" i18n="transaction.accelerated-to-feerate|Accelerated to feerate">Accelerated to</td>
|
||||
<td class="field-value">
|
||||
<td class="td-width field-label" [class]="chartPositionLeft ? 'chart-left' : ''" i18n="transaction.accelerated-to-feerate|Accelerated to feerate">Accelerated to</td>
|
||||
<td class="pie-chart" rowspan="2" *ngIf="chartPositionLeft">
|
||||
<ng-container *ngTemplateOutlet="pieChart"></ng-container>
|
||||
</td>
|
||||
<td class="field-value" [class]="chartPositionLeft ? 'chart-left' : ''">
|
||||
<div class="effective-fee-container">
|
||||
@if (accelerationInfo?.acceleratedFeeRate && (!tx.effectiveFeePerVsize || accelerationInfo.acceleratedFeeRate >= tx.effectiveFeePerVsize)) {
|
||||
<app-fee-rate [fee]="accelerationInfo.acceleratedFeeRate"></app-fee-rate>
|
||||
@@ -14,7 +17,7 @@
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td class="pie-chart" rowspan="2">
|
||||
<td class="pie-chart" rowspan="2" *ngIf="!chartPositionLeft">
|
||||
<ng-container *ngTemplateOutlet="pieChart"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
width: auto;
|
||||
min-width: auto;
|
||||
}
|
||||
&.chart-left {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.field-value {
|
||||
@@ -23,6 +26,10 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.chart-left {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.hashrate-label {
|
||||
@media (max-width: 420px) {
|
||||
display: none;
|
||||
@@ -47,4 +54,11 @@
|
||||
@media (max-width: 420px) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .chart {
|
||||
overflow: visible;
|
||||
& > div, & > div > svg {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,17 @@ import { Acceleration, SinglePoolStats } from '../../../interfaces/node-api.inte
|
||||
import { EChartsOption, PieSeriesOption } from '../../../graphs/echarts';
|
||||
import { MiningStats } from '../../../services/mining.service';
|
||||
|
||||
function lighten(color, p): { r, g, b } {
|
||||
return {
|
||||
r: color.r + ((255 - color.r) * p),
|
||||
g: color.g + ((255 - color.g) * p),
|
||||
b: color.b + ((255 - color.b) * p),
|
||||
};
|
||||
}
|
||||
|
||||
function toRGB({r,g,b}): string {
|
||||
return `rgb(${r},${g},${b})`;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-active-acceleration-box',
|
||||
@@ -17,6 +28,7 @@ export class ActiveAccelerationBox implements OnChanges {
|
||||
@Input() miningStats: MiningStats;
|
||||
@Input() pools: number[];
|
||||
@Input() chartOnly: boolean = false;
|
||||
@Input() chartPositionLeft: boolean = false;
|
||||
|
||||
acceleratedByPercentage: string = '';
|
||||
|
||||
@@ -43,57 +55,33 @@ export class ActiveAccelerationBox implements OnChanges {
|
||||
pools[pool.poolUniqueId] = pool;
|
||||
}
|
||||
|
||||
const getDataItem = (value, color, tooltip) => ({
|
||||
const getDataItem = (value, color, tooltip, emphasis) => ({
|
||||
value,
|
||||
name: tooltip,
|
||||
itemStyle: {
|
||||
color,
|
||||
borderColor: 'rgba(0,0,0,0)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
disabled: true,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
backgroundColor: 'rgba(17, 19, 31, 1)',
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: 'var(--tooltip-grey)',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: () => {
|
||||
return tooltip;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let totalAcceleratedHashrate = 0;
|
||||
for (const poolId of poolList || []) {
|
||||
const acceleratingPools = (poolList || []).filter(id => pools[id]).sort((a,b) => pools[a].lastEstimatedHashrate - pools[b].lastEstimatedHashrate);
|
||||
const totalAcceleratedHashrate = acceleratingPools.reduce((total, pool) => total + pools[pool].lastEstimatedHashrate, 0);
|
||||
acceleratingPools.forEach((poolId, index) => {
|
||||
const pool = pools[poolId];
|
||||
if (!pool) {
|
||||
continue;
|
||||
}
|
||||
totalAcceleratedHashrate += pool.lastEstimatedHashrate;
|
||||
}
|
||||
const poolShare = ((pool.lastEstimatedHashrate / this.miningStats.lastEstimatedHashrate) * 100).toFixed(1);
|
||||
data.push(getDataItem(
|
||||
pool.lastEstimatedHashrate,
|
||||
toRGB(lighten({ r: 147, g: 57, b: 244 }, index * .08)),
|
||||
`<b style="color: white">${pool.name} (${poolShare}%)</b>`,
|
||||
true,
|
||||
) as PieSeriesOption);
|
||||
})
|
||||
this.acceleratedByPercentage = ((totalAcceleratedHashrate / this.miningStats.lastEstimatedHashrate) * 100).toFixed(1) + '%';
|
||||
data.push(getDataItem(
|
||||
totalAcceleratedHashrate,
|
||||
'var(--mainnet-alt)',
|
||||
`${this.acceleratedByPercentage} accelerating`,
|
||||
) as PieSeriesOption);
|
||||
const notAcceleratedByPercentage = ((1 - (totalAcceleratedHashrate / this.miningStats.lastEstimatedHashrate)) * 100).toFixed(1) + '%';
|
||||
data.push(getDataItem(
|
||||
(this.miningStats.lastEstimatedHashrate - totalAcceleratedHashrate),
|
||||
'rgba(127, 127, 127, 0.3)',
|
||||
`${notAcceleratedByPercentage} not accelerating`,
|
||||
`not accelerating (${notAcceleratedByPercentage})`,
|
||||
false,
|
||||
) as PieSeriesOption);
|
||||
|
||||
return data;
|
||||
@@ -111,11 +99,28 @@ export class ActiveAccelerationBox implements OnChanges {
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'item',
|
||||
backgroundColor: 'rgba(17, 19, 31, 1)',
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: 'var(--tooltip-grey)',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: (item) => {
|
||||
return item.name;
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: '100%',
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
animationDuration: 0,
|
||||
data: this.getChartData(pools),
|
||||
}
|
||||
]
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
<tbody>
|
||||
<tr><ng-container *ngTemplateOutlet="balanceRow"></ng-container></tr>
|
||||
<tr><ng-container *ngTemplateOutlet="pendingBalanceRow"></ng-container></tr>
|
||||
<tr><ng-container *ngTemplateOutlet="utxoRow"></ng-container></tr>
|
||||
<tr><ng-container *ngTemplateOutlet="pendingUtxoRow"></ng-container></tr>
|
||||
@if (!address.electrum) {
|
||||
<tr><ng-container *ngTemplateOutlet="utxoRow"></ng-container></tr>
|
||||
<tr><ng-container *ngTemplateOutlet="pendingUtxoRow"></ng-container></tr>
|
||||
}
|
||||
@if (network === 'liquid' || network === 'liquidtestnet') {
|
||||
<tr><ng-container *ngTemplateOutlet="liquidRow"></ng-container></tr>
|
||||
} @else {
|
||||
} @else if (!address.electrum) {
|
||||
<tr><ng-container *ngTemplateOutlet="volumeRow"></ng-container></tr>
|
||||
}
|
||||
<tr><ng-container *ngTemplateOutlet="typeRow"></ng-container></tr>
|
||||
@@ -46,17 +48,21 @@
|
||||
<ng-container *ngTemplateOutlet="spacerCell"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="pendingBalanceRow"></ng-container>
|
||||
</tr>
|
||||
@if (!address.electrum) {
|
||||
<tr>
|
||||
<ng-container *ngTemplateOutlet="utxoRow"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="spacerCell"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="pendingUtxoRow"></ng-container>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
@if (network === 'liquid' || network === 'liquidtestnet') {
|
||||
<ng-container *ngTemplateOutlet="liquidRow"></ng-container>
|
||||
} @else {
|
||||
} @else if (!address.electrum) {
|
||||
<ng-container *ngTemplateOutlet="volumeRow"></ng-container>
|
||||
}
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="emptyTd"></ng-container>
|
||||
}
|
||||
<ng-container *ngTemplateOutlet="spacerCell"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="typeRow"></ng-container>
|
||||
</tr>
|
||||
@@ -232,6 +238,11 @@
|
||||
<td class="spacer"></td>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #emptyTd>
|
||||
<td class="spacer"></td>
|
||||
<td class="spacer"></td>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #balanceRow>
|
||||
<td i18n="address.confirmed-balance">Confirmed balance</td>
|
||||
<td *ngIf="chainStats.funded_txo_sum !== undefined; else confidentialTd" class="wrap-cell"><app-amount [satoshis]="chainStats.balance" [noFiat]="true"></app-amount> <span class="fiat"><app-fiat [value]="chainStats.balance"></app-fiat></span></td>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ng-container *ngIf="!noFiat && (viewAmountMode$ | async) === 'fiat' && (conversions$ | async) as conversions; else viewFiatVin">
|
||||
<ng-container *ngIf="!noFiat && ignoreViewMode === false && (viewAmountMode$ | async) === 'fiat' && (conversions$ | async) as conversions; else viewFiatVin">
|
||||
<span class="fiat" *ngIf="blockConversion; else noblockconversion">
|
||||
{{ addPlus && satoshis >= 0 ? '+' : '' }}{{
|
||||
(
|
||||
@@ -21,7 +21,7 @@
|
||||
<span i18n="shared.confidential">Confidential</span>
|
||||
</ng-template>
|
||||
<ng-template #default>
|
||||
@if ((viewAmountMode$ | async) === 'btc' || (viewAmountMode$ | async) === 'fiat') {
|
||||
@if ((viewAmountMode$ | async) === 'btc' || (viewAmountMode$ | async) === 'fiat' || ignoreViewMode === true) {
|
||||
‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis / 100000000 | number : digitsInfo }}
|
||||
<span class="symbol">
|
||||
<ng-container *ngTemplateOutlet="prefix"></ng-container>BTC
|
||||
|
||||
@@ -24,6 +24,7 @@ export class AmountComponent implements OnInit, OnDestroy {
|
||||
@Input() addPlus = false;
|
||||
@Input() blockConversion: Price;
|
||||
@Input() forceBtc: boolean = false;
|
||||
@Input() ignoreViewMode: boolean = false;
|
||||
@Input() forceBlockConversion: boolean = false; // true = displays fiat price as 0 if blockConversion is undefined instead of falling back to conversions
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -73,27 +73,27 @@ export class BlocksList implements OnInit {
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.blocks:See the most recent Bitcoin${seoDescriptionNetwork(this.stateService.network)} blocks along with basic stats such as block height, block reward, block size, and more.`);
|
||||
}
|
||||
|
||||
this.blocksCountInitializedSubscription = combineLatest([this.blocksCountInitialized$, this.route.queryParams]).pipe(
|
||||
this.blocksCountInitializedSubscription = combineLatest([this.blocksCountInitialized$, this.route.params]).pipe(
|
||||
filter(([blocksCountInitialized, _]) => blocksCountInitialized),
|
||||
tap(([_, params]) => {
|
||||
this.page = +params['page'] || 1;
|
||||
this.page === 1 ? this.fromHeightSubject.next(undefined) : this.fromHeightSubject.next((this.blocksCount - 1) - (this.page - 1) * 15);
|
||||
this.cd.markForCheck();
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.keyNavigationSubscription = this.stateService.keyNavigation$
|
||||
.pipe(
|
||||
tap((event) => {
|
||||
this.isLoading = true;
|
||||
const prevKey = this.dir === 'ltr' ? 'ArrowLeft' : 'ArrowRight';
|
||||
const nextKey = this.dir === 'ltr' ? 'ArrowRight' : 'ArrowLeft';
|
||||
if (event.key === prevKey && this.page > 1) {
|
||||
this.page--;
|
||||
this.isLoading = true;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
if (event.key === nextKey && this.page * 15 < this.blocksCount) {
|
||||
this.page++;
|
||||
this.isLoading = true;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}),
|
||||
@@ -118,6 +118,7 @@ export class BlocksList implements OnInit {
|
||||
if (this.blocksCount === undefined) {
|
||||
this.blocksCount = blocks[0].height + 1;
|
||||
this.blocksCountInitialized$.next(true);
|
||||
this.blocksCountInitialized$.complete();
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.lastBlockHeight = Math.max(...blocks.map(o => o.height));
|
||||
@@ -179,7 +180,7 @@ export class BlocksList implements OnInit {
|
||||
}
|
||||
|
||||
pageChange(page: number): void {
|
||||
this.router.navigate([], { queryParams: { page: page } });
|
||||
this.router.navigate(['blocks', page]);
|
||||
}
|
||||
|
||||
trackByBlock(index: number, block: BlockExtended): number {
|
||||
|
||||
@@ -36,7 +36,7 @@ export class RecentPegsListComponent implements OnInit {
|
||||
lastPegBlockUpdate: number = 0;
|
||||
lastPegAmount: string = '';
|
||||
isLoad: boolean = true;
|
||||
queryParamSubscription: Subscription;
|
||||
paramSubscription: Subscription;
|
||||
keyNavigationSubscription: Subscription;
|
||||
dir: 'rtl' | 'ltr' = 'ltr';
|
||||
|
||||
@@ -66,7 +66,7 @@ export class RecentPegsListComponent implements OnInit {
|
||||
this.seoService.setTitle($localize`:@@a8b0889ea1b41888f1e247f2731cc9322198ca04:Recent Peg-In / Out's`);
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.queryParamSubscription = this.route.queryParams.pipe(
|
||||
this.paramSubscription = this.route.params.pipe(
|
||||
tap((params) => {
|
||||
this.page = +params['page'] || 1;
|
||||
this.startingIndexSubject.next((this.page - 1) * 15);
|
||||
@@ -76,15 +76,16 @@ export class RecentPegsListComponent implements OnInit {
|
||||
this.keyNavigationSubscription = this.stateService.keyNavigation$
|
||||
.pipe(
|
||||
tap((event) => {
|
||||
this.isLoading = true;
|
||||
const prevKey = this.dir === 'ltr' ? 'ArrowLeft' : 'ArrowRight';
|
||||
const nextKey = this.dir === 'ltr' ? 'ArrowRight' : 'ArrowLeft';
|
||||
if (event.key === prevKey && this.page > 1) {
|
||||
this.page--;
|
||||
this.isLoading = true;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
if (event.key === nextKey && this.page < this.pegsCount / this.pageSize) {
|
||||
this.page++;
|
||||
this.isLoading = true;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}),
|
||||
@@ -172,12 +173,12 @@ export class RecentPegsListComponent implements OnInit {
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next(1);
|
||||
this.destroy$.complete();
|
||||
this.queryParamSubscription?.unsubscribe();
|
||||
this.paramSubscription?.unsubscribe();
|
||||
this.keyNavigationSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
pageChange(page: number): void {
|
||||
this.router.navigate([], { queryParams: { page: page } });
|
||||
this.router.navigate(['audit', 'pegs', page]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<td class="text-center"><app-amount [satoshis]="poolStats.totalReward" digitsInfo="1.0-0" [noFiat]="true"></app-amount></td>
|
||||
<td class="text-center"><app-amount [satoshis]="poolStats.totalReward" digitsInfo="1.0-0" [noFiat]="true" [ignoreViewMode]="true"></app-amount></td>
|
||||
<td class="text-center">{{ poolStats.estimatedHashrate | amountShortener : 1 : 'H/s' }}</td>
|
||||
<td class="text-center" *ngIf="auditAvailable; else emptyTd"><span class="health-badge badge" [class.badge-success]="poolStats.avgBlockHealth >= 99"
|
||||
[class.badge-warning]="poolStats.avgBlockHealth >= 75 && poolStats.avgBlockHealth < 99" [class.badge-danger]="poolStats.avgBlockHealth < 75"
|
||||
@@ -146,7 +146,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<td *ngFor="let total of oob" class="text-center clip"><app-amount [satoshis]="total.cost" [digitsInfo]="isMobile() ? '1.2-4' : '1.8-8'"></app-amount></td>
|
||||
<td *ngFor="let total of oob" class="text-center clip"><app-amount [satoshis]="total.cost" [digitsInfo]="isMobile() ? '1.2-4' : '1.8-8'" [ignoreViewMode]="true"></app-amount></td>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
@@ -219,10 +219,10 @@
|
||||
</ng-template>
|
||||
</td>
|
||||
<td class="reward text-right">
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-2" [noFiat]="true" [ignoreViewMode]="true"></app-amount>
|
||||
</td>
|
||||
<td *ngIf="!auditAvailable" class="fees text-right">
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-2" [noFiat]="true" [ignoreViewMode]="true"></app-amount>
|
||||
</td>
|
||||
<td class="txs text-right">
|
||||
{{ block.tx_count | number }}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="rbf-trees" style="min-height: 295px">
|
||||
<div class="rbf-trees" [ngStyle]="{ 'min-height': '295px', 'opacity': isLoading ? '0.75' : '1' }">
|
||||
<ng-container *ngIf="rbfTrees$ | async as trees">
|
||||
<div *ngFor="let tree of trees" class="tree">
|
||||
<p class="info">
|
||||
|
||||
@@ -38,6 +38,7 @@ export class RbfList implements OnInit, OnDestroy {
|
||||
this.fullRbf = (fragment === 'fullrbf');
|
||||
this.websocketService.startTrackRbf(this.fullRbf ? 'fullRbf' : 'all');
|
||||
this.nextRbfSubject.next(null);
|
||||
this.isLoading = true;
|
||||
});
|
||||
|
||||
this.rbfTrees$ = merge(
|
||||
|
||||
@@ -78,6 +78,10 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
calculate() {
|
||||
if (!this.time) {
|
||||
return;
|
||||
}
|
||||
|
||||
let seconds: number;
|
||||
switch (this.kind) {
|
||||
case 'since':
|
||||
|
||||
@@ -116,7 +116,7 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
||||
|
||||
hasEffectiveFeeRate: boolean;
|
||||
accelerateCtaType: 'alert' | 'button' = 'button';
|
||||
acceleratorAvailable: boolean = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
accelerationEligible: boolean = false;
|
||||
showAccelerationSummary = false;
|
||||
accelerationFlowCompleted = false;
|
||||
|
||||
@@ -80,10 +80,12 @@
|
||||
<div class="title float-left">
|
||||
<h2 i18n="transaction.accelerate|Accelerate button label">Accelerate</h2>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-info flow-toggle btn-sm float-right" (click)="showAccelerationDetails = !showAccelerationDetails" i18n="transaction.details|Transaction Details">Details</button>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="box">
|
||||
<app-accelerate-preview [tx]="tx" [miningStats]="miningStats" [mempoolPosition]="mempoolPosition" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview>
|
||||
<app-accelerate-preview [tx]="tx" [miningStats]="miningStats" [scrollEvent]="scrollIntoAccelPreview" [showDetails]="showAccelerationDetails"></app-accelerate-preview>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -648,7 +650,7 @@
|
||||
<ng-template #acceleratingRow>
|
||||
<tr>
|
||||
<td rowspan="2" colspan="2" style="padding: 0;">
|
||||
<app-active-acceleration-box [tx]="tx" [accelerationInfo]="accelerationInfo" [miningStats]="miningStats"></app-active-acceleration-box>
|
||||
<app-active-acceleration-box [tx]="tx" [accelerationInfo]="accelerationInfo" [miningStats]="miningStats" [chartPositionLeft]="isMobile"></app-active-acceleration-box>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
|
||||
@@ -136,8 +136,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
taprootEnabled: boolean;
|
||||
hasEffectiveFeeRate: boolean;
|
||||
accelerateCtaType: 'alert' | 'button' = 'button';
|
||||
acceleratorAvailable: boolean = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
acceleratorAvailable: boolean = this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
showAccelerationSummary = false;
|
||||
showAccelerationDetails = false;
|
||||
scrollIntoAccelPreview = false;
|
||||
auditEnabled: boolean = this.stateService.env.AUDIT && this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true;
|
||||
|
||||
@@ -166,15 +167,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
|
||||
this.enterpriseService.page();
|
||||
|
||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||
this.stateService.networkChanged$.subscribe(
|
||||
(network) => {
|
||||
this.network = network;
|
||||
this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
this.acceleratorAvailable = this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
}
|
||||
);
|
||||
|
||||
@@ -748,6 +747,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.tx.acceleratedBy = cpfpInfo.acceleratedBy;
|
||||
this.setIsAccelerated(firstCpfp);
|
||||
}
|
||||
|
||||
if (!this.isAcceleration && this.fragmentParams.has('accelerate')) {
|
||||
this.onAccelerateClicked();
|
||||
}
|
||||
|
||||
this.txChanged$.next(true);
|
||||
|
||||
this.cpfpInfo = cpfpInfo;
|
||||
@@ -885,14 +889,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
// simulate normal anchor fragment behavior
|
||||
applyFragment(): void {
|
||||
const anchor = Array.from(this.fragmentParams.entries()).find(([frag, value]) => value === '');
|
||||
if (anchor?.length) {
|
||||
if (anchor[0] === 'accelerate') {
|
||||
setTimeout(this.onAccelerateClicked.bind(this), 100);
|
||||
} else {
|
||||
const anchorElement = document.getElementById(anchor[0]);
|
||||
if (anchorElement) {
|
||||
anchorElement.scrollIntoView();
|
||||
}
|
||||
if (anchor?.length && anchor[0] !== 'accelerate') {
|
||||
const anchorElement = document.getElementById(anchor[0]);
|
||||
if (anchorElement) {
|
||||
anchorElement.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ import { AcceleratePreviewComponent } from '../accelerate-preview/accelerate-pre
|
||||
import { AccelerateFeeGraphComponent } from '../accelerate-preview/accelerate-fee-graph.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: TransactionComponent,
|
||||
|
||||
@@ -72,7 +72,7 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
||||
this.auditEnabled = this.env.AUDIT;
|
||||
this.network$ = merge(of(''), this.stateService.networkChanged$).pipe(
|
||||
tap((network: string) => {
|
||||
if (this.env.BASE_MODULE === 'mempool' && network !== '') {
|
||||
if (this.env.BASE_MODULE === 'mempool' && network !== '' && this.env.ROOT_NETWORK === '') {
|
||||
this.baseNetworkUrl = `/${network}`;
|
||||
} else if (this.env.BASE_MODULE === 'liquid') {
|
||||
if (!['', 'liquid'].includes(network)) {
|
||||
@@ -195,6 +195,10 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
if (network === this.env.ROOT_NETWORK) {
|
||||
curlNetwork = '';
|
||||
}
|
||||
|
||||
let text = code.codeTemplate.curl;
|
||||
for (let index = 0; index < curlResponse.length; index++) {
|
||||
const curlText = curlResponse[index];
|
||||
|
||||
@@ -284,7 +284,7 @@ yarn add @mempool/liquid.js`;
|
||||
const headersString = code.headers ? ` -H "${code.headers}"` : ``;
|
||||
|
||||
if (this.env.BASE_MODULE === 'mempool') {
|
||||
if (this.network === 'main' || this.network === '') {
|
||||
if (this.network === 'main' || this.network === '' || this.network === this.env.ROOT_NETWORK) {
|
||||
if (this.method === 'POST') {
|
||||
return `curl${headersString} -X POST -sSLd "${text}"`;
|
||||
}
|
||||
@@ -296,7 +296,7 @@ yarn add @mempool/liquid.js`;
|
||||
return `curl${headersString} -sSL "${this.hostname}/${this.network}${text}"`;
|
||||
} else if (this.env.BASE_MODULE === 'liquid') {
|
||||
if (this.method === 'POST') {
|
||||
if (this.network !== 'liquid') {
|
||||
if (this.network !== 'liquid' || this.network === this.env.ROOT_NETWORK) {
|
||||
text = text.replace('/api', `/${this.network}/api`);
|
||||
}
|
||||
return `curl${headersString} -X POST -sSLd "${text}"`;
|
||||
|
||||
@@ -60,10 +60,14 @@ const routes: Routes = [
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'acceleration/list',
|
||||
path: 'acceleration/list/:page',
|
||||
data: { networks: ['bitcoin'] },
|
||||
component: AccelerationsListComponent,
|
||||
},
|
||||
{
|
||||
path: 'acceleration/list',
|
||||
redirectTo: 'acceleration/list/1',
|
||||
},
|
||||
{
|
||||
path: 'mempool-block/:id',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
|
||||
@@ -84,10 +84,14 @@ const routes: Routes = [
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'audit/pegs',
|
||||
path: 'audit/pegs/:page',
|
||||
data: { networks: ['liquid'] },
|
||||
component: RecentPegsListComponent,
|
||||
},
|
||||
{
|
||||
path: 'audit/pegs',
|
||||
redirectTo: 'audit/pegs/1'
|
||||
},
|
||||
{
|
||||
path: 'assets',
|
||||
data: { networks: ['liquid'] },
|
||||
|
||||
@@ -45,9 +45,13 @@ const routes: Routes = [
|
||||
loadChildren: () => import('./components/about/about.module').then(m => m.AboutModule),
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
path: 'blocks/:page',
|
||||
component: BlocksList,
|
||||
},
|
||||
{
|
||||
path: 'blocks',
|
||||
redirectTo: 'blocks/1',
|
||||
},
|
||||
{
|
||||
path: 'rbf',
|
||||
component: RbfList,
|
||||
|
||||
@@ -3,8 +3,10 @@ import { AccelerationPosition, CpfpInfo, DifficultyAdjustment, MempoolPosition,
|
||||
import { StateService } from './state.service';
|
||||
import { MempoolBlock } from '../interfaces/websocket.interface';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { MiningStats } from './mining.service';
|
||||
import { MiningService, MiningStats } from './mining.service';
|
||||
import { getUnacceleratedFeeRate } from '../shared/transaction.utils';
|
||||
import { AccelerationEstimate } from '../components/accelerate-preview/accelerate-preview.component';
|
||||
import { Observable, combineLatest, map, of } from 'rxjs';
|
||||
|
||||
export interface ETA {
|
||||
now: number, // time at which calculation performed
|
||||
@@ -19,8 +21,50 @@ export interface ETA {
|
||||
export class EtaService {
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private miningService: MiningService,
|
||||
) { }
|
||||
|
||||
getProjectedEtaObservable(estimate: AccelerationEstimate, miningStats?: MiningStats): Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }> {
|
||||
return combineLatest([
|
||||
this.stateService.mempoolTxPosition$.pipe(map(p => p?.position)),
|
||||
this.stateService.difficultyAdjustment$,
|
||||
miningStats ? of(miningStats) : this.miningService.getMiningStats('1w'),
|
||||
]).pipe(
|
||||
map(([mempoolPosition, da, miningStats]) => {
|
||||
if (!mempoolPosition || !estimate?.pools?.length || !miningStats || !da) {
|
||||
return {
|
||||
hashratePercentage: undefined,
|
||||
ETA: undefined,
|
||||
acceleratedETA: undefined,
|
||||
};
|
||||
}
|
||||
const pools: { [id: number]: SinglePoolStats } = {};
|
||||
for (const pool of miningStats.pools) {
|
||||
pools[pool.poolUniqueId] = pool;
|
||||
}
|
||||
|
||||
let totalAcceleratedHashrate = 0;
|
||||
for (const poolId of estimate.pools) {
|
||||
const pool = pools[poolId];
|
||||
if (!pool) {
|
||||
continue;
|
||||
}
|
||||
totalAcceleratedHashrate += pool.lastEstimatedHashrate;
|
||||
}
|
||||
const acceleratingHashrateFraction = (totalAcceleratedHashrate / miningStats.lastEstimatedHashrate);
|
||||
|
||||
return {
|
||||
hashratePercentage: acceleratingHashrateFraction * 100,
|
||||
ETA: Date.now() + da.timeAvg * mempoolPosition.block,
|
||||
acceleratedETA: this.calculateETAFromShares([
|
||||
{ block: mempoolPosition.block, hashrateShare: (1 - acceleratingHashrateFraction) },
|
||||
{ block: 0, hashrateShare: acceleratingHashrateFraction },
|
||||
], da).time,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
mempoolPositionFromFees(feerate: number, mempoolBlocks: MempoolBlock[]): MempoolPosition {
|
||||
for (let txInBlockIndex = 0; txInBlockIndex < mempoolBlocks.length; txInBlockIndex++) {
|
||||
const block = mempoolBlocks[txInBlockIndex];
|
||||
@@ -41,7 +85,7 @@ export class EtaService {
|
||||
return {
|
||||
block: txInBlockIndex,
|
||||
vsize: (1 - feePosition) * blockedFilledPercentage * this.stateService.blockVSize,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if (feerate >= block.feeRange[block.feeRange.length - 1]) {
|
||||
@@ -49,14 +93,14 @@ export class EtaService {
|
||||
return {
|
||||
block: txInBlockIndex,
|
||||
vsize: 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// at the very back of the last block
|
||||
return {
|
||||
block: mempoolBlocks.length - 1,
|
||||
vsize: mempoolBlocks[mempoolBlocks.length - 1].blockVSize,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
calculateETA(
|
||||
@@ -88,7 +132,7 @@ export class EtaService {
|
||||
time: now + (60_000 * (mempoolPosition.block + 1)),
|
||||
wait: (60_000 * (mempoolPosition.block + 1)),
|
||||
blocks: mempoolPosition.block + 1,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// difficulty adjustment estimate is required to know avg block time on non-Liquid networks
|
||||
@@ -104,7 +148,7 @@ export class EtaService {
|
||||
time: wait + now + da.timeOffset,
|
||||
wait,
|
||||
blocks,
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// accelerated transactions
|
||||
|
||||
@@ -121,7 +165,7 @@ export class EtaService {
|
||||
pools[pool.poolUniqueId] = pool;
|
||||
}
|
||||
const unacceleratedPosition = this.mempoolPositionFromFees(getUnacceleratedFeeRate(tx, true), mempoolBlocks);
|
||||
let totalAcceleratedHashrate = accelerationPositions.reduce((total, pos) => total + (pools[pos.poolId].lastEstimatedHashrate), 0);
|
||||
const totalAcceleratedHashrate = accelerationPositions.reduce((total, pos) => total + (pools[pos.poolId].lastEstimatedHashrate), 0);
|
||||
const shares = [
|
||||
{
|
||||
block: unacceleratedPosition.block,
|
||||
@@ -163,7 +207,7 @@ export class EtaService {
|
||||
// find H_i
|
||||
const H = shares.reduce((total, share) => total + (share.block <= i ? share.hashrateShare : 0), 0);
|
||||
// find S_i
|
||||
let S = H * (1 - tailProb);
|
||||
const S = H * (1 - tailProb);
|
||||
// accumulate sum (S_i x i)
|
||||
Q += (S * (i + 1));
|
||||
// accumulate sum (S_j)
|
||||
@@ -178,6 +222,6 @@ export class EtaService {
|
||||
time: eta + now + da.timeOffset,
|
||||
wait: eta,
|
||||
blocks: Math.ceil(eta / da.adjustedTimeAvg),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ export class StateService {
|
||||
utxoSpent$ = new Subject<object>();
|
||||
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
|
||||
mempoolTransactions$ = new Subject<Transaction>();
|
||||
mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null, accelerationPositions?: AccelerationPosition[] }>();
|
||||
mempoolTxPosition$ = new BehaviorSubject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null, accelerationPositions?: AccelerationPosition[] }>(null);
|
||||
mempoolRemovedTransactions$ = new Subject<Transaction>();
|
||||
multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>();
|
||||
blockTransactions$ = new Subject<Transaction>();
|
||||
|
||||
BIN
frontend/src/resources/profile/coldcard.png
Normal file
BIN
frontend/src/resources/profile/coldcard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
1811
unfurler/package-lock.json
generated
1811
unfurler/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@
|
||||
"express": "^4.19.2",
|
||||
"ejs": "^3.1.10",
|
||||
"node-fetch-commonjs": "^3.3.1",
|
||||
"puppeteer": "^22.12.0",
|
||||
"puppeteer": "^15.3.2",
|
||||
"puppeteer-cluster": "^0.23.0",
|
||||
"typescript": "~4.7.4"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user