Compare commits

...

38 Commits

Author SHA1 Message Date
wiz
5d360d4156 Merge pull request #1003 from mempool/simon/database-migration-feature
Automated database creation and migration
2021-12-23 21:50:10 +00:00
softsimon
91e30fbc3c Merge branch 'master' into simon/database-migration-feature
# Conflicts:
#	backend/src/index.ts
2021-12-24 00:26:33 +04:00
wiz
5b22e2a000 Merge pull request #1010 from mempool/simon/liquid-icons-api
Liquid icons api
2021-12-23 12:28:54 +00:00
softsimon
533653e54a Change Asset Icon API example to only show HTML 2021-12-23 15:35:17 +04:00
wiz
3dc0dc13ad Merge pull request #1038 from nymkappa/feature/increase-resolution-24h
Switch the 24h chart to 1 min data ticks
2021-12-22 18:33:32 +00:00
softsimon
e332789afc Bumping mempool.js to 2.3.0-dev1 and removing unused tsc build step 2021-12-22 19:33:10 +04:00
nymkappa
e4a9fd06b4 Switch the 24h chart to 1 min data ticks 2021-12-22 23:01:32 +09:00
softsimon
5845f2380e Adding sync external assets feature. 2021-12-21 02:00:50 +04:00
softsimon
c29311d831 Upgrading mempool-js with separated Liquid and Bisq 2021-12-20 23:48:26 +04:00
softsimon
252db109bc Adding icons.json to .gitignore 2021-12-20 04:01:40 +04:00
softsimon
b1c9334119 Changing API path and updating API Docs for asset icons. 2021-12-20 04:01:04 +04:00
wiz
ab04247726 Merge pull request #1033 from mempool/simon/extract-i18n
Extracting i18n string
2021-12-19 19:06:11 +00:00
softsimon
e94a85b989 Extracting i18n string 2021-12-19 22:57:31 +04:00
softsimon
a4569788f8 Liquid icons api 2021-12-19 22:09:49 +04:00
wiz
b455814e90 Merge pull request #1027 from hunicus/change-docs-layout
Revamp docs layout
2021-12-19 17:48:26 +00:00
softsimon
7afd0f3fe7 Merge pull request #1032 from mempool/wiz/update-about-page-links
Update links on About page
2021-12-19 21:36:10 +04:00
hunicus
a2a85469cf Streamline api-docs-navs conditionals 2021-12-19 12:29:52 -05:00
wiz
94488a6029 Merge pull request #1031 from mempool/wiz/update-issue-templates
Update GitHub issue templates to redirect support requests to chat
2021-12-19 17:02:57 +00:00
wiz
8e4829146a Rename About page section: Project Staff -> Project Members 2021-12-20 01:54:06 +09:00
wiz
08f185525c Update About page chat links: replace telegram with matrix 2021-12-20 01:52:58 +09:00
wiz
d6b00fe39e Update GitHub issue templates to redirect support requests to chat 2021-12-20 01:31:29 +09:00
softsimon
cec3baeaa4 Merge pull request #1024 from nymkappa/feature/blocks-mouse-scroll
User can drag the blockchain blocks horizontally with the mouse
2021-12-19 12:59:04 +04:00
nymkappa
6e59733cac User can drag the blockchain blocks horizontally with the mouse 2021-12-19 15:20:21 +09:00
hunicus
c5b705ede7 Adjust bisq cypress tests 2021-12-17 16:22:16 -05:00
hunicus
2819e24efe Remove unnecessary file change 2021-12-17 15:08:21 -05:00
hunicus
5f9bc4497a Customize mobile nav button appearance point
Since there are different numbers of topics across
bitcoin, liquid, bisq, faq, etc.
2021-12-17 14:42:21 -05:00
hunicus
086b14e816 Add various ux improvements for mobile doc menu 2021-12-17 11:42:54 -05:00
hunicus
958bfe6d25 Separate docs-nav into new component
Since same markup will be needed for desktop
and mobile menus.
2021-12-16 19:11:54 -05:00
hunicus
e01ab449cf Add skeleton for mobile docs nav 2021-12-16 18:44:39 -05:00
hunicus
9a18019d9d Add :before element for space before anchors
@angular-router's `scrollOffset` property seems to be
global, so it might mess up something else, and it also
wasn't taking effect when navigating directly to an
anchor anyway (i.e. from browser's address bar).
2021-12-16 13:20:30 -05:00
hunicus
5d8c970351 Update anchor links and add on-page links 2021-12-16 11:30:03 -05:00
hunicus
89fede9e48 Fix inconsistencies in api-docs markup 2021-12-16 09:54:07 -05:00
hunicus
f8a54784d0 Improve styling + switch section headings for tags 2021-12-16 09:54:07 -05:00
hunicus
010381aac4 Convert accordions to plain html 2021-12-16 08:46:51 -05:00
hunicus
1a8fd23b05 Add links and styling to fixed desktop docs nav 2021-12-16 00:04:47 -05:00
hunicus
3ae46e6ba1 Make desktop docs-nav sticky on scroll 2021-12-15 22:57:10 -05:00
hunicus
40f1949654 Create skeleton layout for desktop docs nav
This includes switching to a 2-column layout
(1 for nav and 1 for content) and moving footer
links to docs component so floats can be cleared
properly.
2021-12-15 13:17:37 -05:00
softsimon
2281116504 Automated database creation and migration
fixes #1002
2021-12-13 11:32:04 +04:00
38 changed files with 1931 additions and 1544 deletions

View File

@@ -1,7 +1,14 @@
---
name: 🐛 Bug Report
about: Report bugs or other issues to us on GitHub
---
<!--
SUPPORT REQUESTS: This is for reporting bugs in Mempool.
If you have a support request, please join our Keybase group:
SUPPORT REQUESTS:
This is for reporting bugs in Mempool, not for support requests.
If you have a support request, please join our Keybase or Matrix:
https://keybase.io/team/mempool
https://matrix.to/#/#mempool:bitcoin.kyoto
-->
### Description
@@ -14,11 +21,11 @@
### Steps to reproduce
<!--if you can reliably reproduce the bug, list the steps here -->
<!-- if you can reliably reproduce the bug, list the steps here -->
### Expected behaviour
<!--description of the expected behavior -->
<!-- description of the expected behavior -->
### Actual behaviour
@@ -26,7 +33,7 @@
### Screenshots
<!--Screenshots if gui related, drag and drop to add to the issue -->
<!-- Screenshots if gui related, drag and drop to add to the issue -->
#### Device or machine

View File

@@ -0,0 +1,28 @@
---
name: ✨ Feature Request
about: Request a feature or suggest other enhancements 💡
---
<!--
SUPPORT REQUESTS:
This is for requesting features in Mempool, not for support requests.
If you have a support request, please join our Keybase or Matrix:
https://keybase.io/team/mempool
https://matrix.to/#/#mempool:bitcoin.kyoto
-->
### Description
<!-- brief description of the feature request -->
### Problem to be solved
<!-- description of the the problem you're having -->
### Proposed solution
<!-- explain how you think we should solve the problem -->
#### Additional info
<!-- Additional information useful for implementing (e.g. docs, links, etc.) -->

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Need help? Chat with us on Matrix
url: https://matrix.to/#/#mempool:bitcoin.kyoto
about: For support requests or general questions
- name: 💬 Need help? Chat with us on Keybase
url: https://keybase.io/team/mempool
about: For support requests or general questions

7
backend/.gitignore vendored
View File

@@ -1,7 +1,10 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# production config
mempool-config.json
# production config and external assets
*.json
!mempool-config.sample.json
icons.json
# compiled output
/dist

View File

@@ -13,7 +13,8 @@
"INITIAL_BLOCKS_AMOUNT": 8,
"MEMPOOL_BLOCKS_AMOUNT": 8,
"PRICE_FEED_UPDATE_INTERVAL": 3600,
"USE_SECOND_NODE_FOR_MINFEE": false
"USE_SECOND_NODE_FOR_MINFEE": false,
"EXTERNAL_ASSETS": []
},
"CORE_RPC": {
"HOST": "127.0.0.1",

View File

@@ -0,0 +1,178 @@
import config from '../config';
import { DB } from '../database';
import logger from '../logger';
class DatabaseMigration {
private static currentVersion = 1;
private queryTimeout = 120000;
constructor() { }
public async $initializeOrMigrateDatabase(): Promise<void> {
if (!await this.$checkIfTableExists('statistics')) {
await this.$initializeDatabaseTables();
}
if (await this.$checkIfTableExists('state')) {
const databaseSchemaVersion = await this.$getSchemaVersionFromDatabase();
if (DatabaseMigration.currentVersion > databaseSchemaVersion) {
await this.$migrateTableSchemaFromVersion(databaseSchemaVersion);
}
} else {
await this.$migrateTableSchemaFromVersion(0);
}
}
private async $initializeDatabaseTables(): Promise<void> {
const connection = await DB.pool.getConnection();
for (const query of this.getInitializeTableQueries()) {
await connection.query<any>({ sql: query, timeout: this.queryTimeout });
}
connection.release();
logger.info(`Initial database tables have been created`);
}
private async $migrateTableSchemaFromVersion(version: number): Promise<void> {
const connection = await DB.pool.getConnection();
for (const query of this.getMigrationQueriesFromVersion(version)) {
await connection.query<any>({ sql: query, timeout: this.queryTimeout });
}
connection.release();
await this.$updateToLatestSchemaVersion();
logger.info(`Database schema have been migrated from version ${version} to ${DatabaseMigration.currentVersion} (latest version)`);
}
private async $getSchemaVersionFromDatabase(): Promise<number> {
const connection = await DB.pool.getConnection();
const query = `SELECT number FROM state WHERE name = 'schema_version';`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return rows[0]['number'];
}
private async $updateToLatestSchemaVersion(): Promise<void> {
const connection = await DB.pool.getConnection();
const query = `UPDATE state SET number = ${DatabaseMigration.currentVersion} WHERE name = 'schema_version'`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
}
private async $checkIfTableExists(table: string): Promise<boolean> {
const connection = await DB.pool.getConnection();
const query = `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${config.DATABASE.DATABASE}' AND TABLE_NAME = '${table}'`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return rows[0]['COUNT(*)'] === 1;
}
private getInitializeTableQueries(): string[] {
const queries: string[] = [];
queries.push(`CREATE TABLE IF NOT EXISTS statistics (
id int(11) NOT NULL,
added datetime NOT NULL,
unconfirmed_transactions int(11) UNSIGNED NOT NULL,
tx_per_second float UNSIGNED NOT NULL,
vbytes_per_second int(10) UNSIGNED NOT NULL,
mempool_byte_weight int(10) UNSIGNED NOT NULL,
fee_data longtext NOT NULL,
total_fee double UNSIGNED NOT NULL,
vsize_1 int(11) NOT NULL,
vsize_2 int(11) NOT NULL,
vsize_3 int(11) NOT NULL,
vsize_4 int(11) NOT NULL,
vsize_5 int(11) NOT NULL,
vsize_6 int(11) NOT NULL,
vsize_8 int(11) NOT NULL,
vsize_10 int(11) NOT NULL,
vsize_12 int(11) NOT NULL,
vsize_15 int(11) NOT NULL,
vsize_20 int(11) NOT NULL,
vsize_30 int(11) NOT NULL,
vsize_40 int(11) NOT NULL,
vsize_50 int(11) NOT NULL,
vsize_60 int(11) NOT NULL,
vsize_70 int(11) NOT NULL,
vsize_80 int(11) NOT NULL,
vsize_90 int(11) NOT NULL,
vsize_100 int(11) NOT NULL,
vsize_125 int(11) NOT NULL,
vsize_150 int(11) NOT NULL,
vsize_175 int(11) NOT NULL,
vsize_200 int(11) NOT NULL,
vsize_250 int(11) NOT NULL,
vsize_300 int(11) NOT NULL,
vsize_350 int(11) NOT NULL,
vsize_400 int(11) NOT NULL,
vsize_500 int(11) NOT NULL,
vsize_600 int(11) NOT NULL,
vsize_700 int(11) NOT NULL,
vsize_800 int(11) NOT NULL,
vsize_900 int(11) NOT NULL,
vsize_1000 int(11) NOT NULL,
vsize_1200 int(11) NOT NULL,
vsize_1400 int(11) NOT NULL,
vsize_1600 int(11) NOT NULL,
vsize_1800 int(11) NOT NULL,
vsize_2000 int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
queries.push(`ALTER TABLE statistics ADD PRIMARY KEY (id);`);
queries.push(`ALTER TABLE statistics MODIFY id int(11) NOT NULL AUTO_INCREMENT;`);
return queries;
}
private getMigrationQueriesFromVersion(version: number): string[] {
const queries: string[] = [];
if (version < 1) {
if (config.MEMPOOL.NETWORK !== 'liquid') {
queries.push(`UPDATE statistics SET
vsize_1 = vsize_1 + vsize_2, vsize_2 = vsize_3,
vsize_3 = vsize_4, vsize_4 = vsize_5,
vsize_5 = vsize_6, vsize_6 = vsize_8,
vsize_8 = vsize_10, vsize_10 = vsize_12,
vsize_12 = vsize_15, vsize_15 = vsize_20,
vsize_20 = vsize_30, vsize_30 = vsize_40,
vsize_40 = vsize_50, vsize_50 = vsize_60,
vsize_60 = vsize_70, vsize_70 = vsize_80,
vsize_80 = vsize_90, vsize_90 = vsize_100,
vsize_100 = vsize_125, vsize_125 = vsize_150,
vsize_150 = vsize_175, vsize_175 = vsize_200,
vsize_200 = vsize_250, vsize_250 = vsize_300,
vsize_300 = vsize_350, vsize_350 = vsize_400,
vsize_400 = vsize_500, vsize_500 = vsize_600,
vsize_600 = vsize_700, vsize_700 = vsize_800,
vsize_800 = vsize_900, vsize_900 = vsize_1000,
vsize_1000 = vsize_1200, vsize_1200 = vsize_1400,
vsize_1400 = vsize_1800, vsize_1800 = vsize_2000, vsize_2000 = 0`);
}
queries.push(`CREATE TABLE IF NOT EXISTS elements_pegs (
block int(11) NOT NULL,
datetime int(11) NOT NULL,
amount bigint(20) NOT NULL,
txid varchar(65) NOT NULL,
txindex int(11) NOT NULL,
bitcoinaddress varchar(100) NOT NULL,
bitcointxid varchar(65) NOT NULL,
bitcoinindex int(11) NOT NULL,
final_tx int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
queries.push(`CREATE TABLE IF NOT EXISTS state (
name varchar(25) NOT NULL,
number int(11) NULL,
string varchar(100) NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`);
queries.push(`INSERT INTO state VALUES('schema_version', 0, NULL);`);
queries.push(`INSERT INTO state VALUES('last_elements_block', 0, NULL);`);
}
return queries;
}
}
export default new DatabaseMigration();

View File

@@ -18,12 +18,12 @@ class ElementsParser {
this.isRunning = true;
const result = await bitcoinClient.getChainTips();
const tip = result[0].height;
const latestBlock = await this.$getLatestBlockFromDatabase();
for (let height = latestBlock.block + 1; height <= tip; height++) {
const latestBlockHeight = await this.$getLatestBlockHeightFromDatabase();
for (let height = latestBlockHeight + 1; height <= tip; height++) {
const blockHash: IBitcoinApi.ChainTips = await bitcoinClient.getBlockHash(height);
const block: IBitcoinApi.Block = await bitcoinClient.getBlock(blockHash, 2);
await this.$parseBlock(block);
await this.$saveLatestBlockToDatabase(block.height, block.time, block.hash);
await this.$saveLatestBlockToDatabase(block.height);
}
this.isRunning = false;
} catch (e) {
@@ -92,18 +92,18 @@ class ElementsParser {
logger.debug(`Saved L-BTC peg from block height #${height} with TXID ${txid}.`);
}
protected async $getLatestBlockFromDatabase(): Promise<any> {
protected async $getLatestBlockHeightFromDatabase(): Promise<number> {
const connection = await DB.pool.getConnection();
const query = `SELECT block, datetime, block_hash FROM last_elements_block`;
const query = `SELECT number FROM state WHERE name = 'last_elements_block'`;
const [rows] = await connection.query<any>(query);
connection.release();
return rows[0];
return rows[0]['number'];
}
protected async $saveLatestBlockToDatabase(blockHeight: number, datetime: number, blockHash: string) {
protected async $saveLatestBlockToDatabase(blockHeight: number) {
const connection = await DB.pool.getConnection();
const query = `UPDATE last_elements_block SET block = ?, datetime = ?, block_hash = ?`;
await connection.query<any>(query, [blockHeight, datetime, blockHash]);
const query = `UPDATE state SET number = ? WHERE name = 'last_elements_block'`;
await connection.query<any>(query, [blockHeight]);
connection.release();
}
}

View File

@@ -0,0 +1,39 @@
import * as fs from 'fs';
import config from '../../config';
import logger from '../../logger';
class Icons {
private static FILE_NAME = './icons.json';
private iconIds: string[] = [];
private icons: { [assetId: string]: string; } = {};
constructor() {}
public loadIcons() {
if (!fs.existsSync(Icons.FILE_NAME)) {
logger.warn(`${Icons.FILE_NAME} does not exist. No Liquid icons loaded.`);
return;
}
const cacheData = fs.readFileSync(Icons.FILE_NAME, 'utf8');
this.icons = JSON.parse(cacheData);
for (const i in this.icons) {
this.iconIds.push(i);
}
logger.debug(`Liquid icons has been loaded.`);
}
public getIconByAssetId(assetId: string): Buffer | undefined {
const icon = this.icons[assetId];
if (icon) {
return Buffer.from(icon, 'base64');
}
}
public getAllIconIds() {
return this.iconIds;
}
}
export default new Icons();

View File

@@ -394,7 +394,7 @@ class Statistics {
public async $list24H(): Promise<OptimizedStatistic[]> {
try {
const connection = await DB.pool.getConnection();
const query = this.getQueryForDaysAvg(120, '1 DAY'); // 2m interval
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY id DESC LIMIT 1440`;
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
connection.release();
return this.mapStatisticToOptimizedStatistic(rows);

View File

@@ -16,6 +16,7 @@ interface IConfig {
MEMPOOL_BLOCKS_AMOUNT: number;
PRICE_FEED_UPDATE_INTERVAL: number;
USE_SECOND_NODE_FOR_MINFEE: boolean;
EXTERNAL_ASSETS: string[];
};
ESPLORA: {
REST_API_URL: string;
@@ -78,6 +79,7 @@ const defaults: IConfig = {
'MEMPOOL_BLOCKS_AMOUNT': 8,
'PRICE_FEED_UPDATE_INTERVAL': 3600,
'USE_SECOND_NODE_FOR_MINFEE': false,
'EXTERNAL_ASSETS': [],
},
'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000',

View File

@@ -21,6 +21,9 @@ import backendInfo from './api/backend-info';
import loadingIndicators from './api/loading-indicators';
import mempool from './api/mempool';
import elementsParser from './api/liquid/elements-parser';
import databaseMigration from './api/database-migration';
import syncAssets from './sync-assets';
import icons from './api/liquid/icons';
class Server {
private wss: WebSocket.Server | undefined;
@@ -77,16 +80,26 @@ class Server {
this.setUpWebsocketHandling();
await syncAssets.syncAssets();
diskCache.loadMempoolCache();
if (config.DATABASE.ENABLED) {
await checkDbConnection();
try {
await databaseMigration.$initializeOrMigrateDatabase();
} catch (e) {
throw new Error(e instanceof Error ? e.message : 'Error');
}
}
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && cluster.isMaster) {
statistics.startStatistics();
}
if (config.MEMPOOL.NETWORK === 'liquid') {
icons.loadIcons();
}
fiatConversion.startService();
this.setUpHttpApiRoutes();
@@ -270,6 +283,13 @@ class Server {
;
}
if (config.MEMPOOL.NETWORK === 'liquid') {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
.get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
;
}
if (config.MEMPOOL.NETWORK === 'liquid' && config.DATABASE.ENABLED) {
this.app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)

View File

@@ -19,6 +19,7 @@ import loadingIndicators from './api/loading-indicators';
import { Common } from './api/common';
import bitcoinClient from './api/bitcoin/bitcoin-client';
import elementsParser from './api/liquid/elements-parser';
import icons from './api/liquid/icons';
class Routes {
constructor() {}
@@ -807,6 +808,26 @@ class Routes {
: (e.message || 'Error'));
}
}
public getLiquidIcon(req: Request, res: Response) {
const result = icons.getIconByAssetId(req.params.assetId);
if (result) {
res.setHeader('content-type', 'image/png');
res.setHeader('content-length', result.length);
res.send(result);
} else {
res.status(404).send('Asset icon not found');
}
}
public getAllLiquidIcon(req: Request, res: Response) {
const result = icons.getAllIconIds();
if (result) {
res.json(result);
} else {
res.status(404).send('Asset icons not found');
}
}
}
export default new Routes();

View File

@@ -0,0 +1,32 @@
import axios from 'axios';
import * as fs from 'fs';
const fsPromises = fs.promises;
import config from './config';
import logger from './logger';
const PATH = './';
class SyncAssets {
constructor() { }
public async syncAssets() {
for (const url of config.MEMPOOL.EXTERNAL_ASSETS) {
await this.downloadFile(url);
}
}
private async downloadFile(url: string) {
const fileName = url.split('/').slice(-1)[0];
logger.info(`Downloading external asset: ${fileName}...`);
try {
const response = await axios.get(url, {
responseType: 'stream', timeout: 30000
});
await fsPromises.writeFile(PATH + fileName, response.data);
} catch (e: any) {
throw new Error(`Failed to download external asset. ` + e);
}
}
}
export default new SyncAssets();

View File

@@ -59,9 +59,8 @@ describe('Bisq', () => {
cy.visit(`${basePath}`);
cy.waitForSkeletonGone();
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.card').should('have.length.at.least', 1);
cy.get('.card').first().click();
cy.get('.card-body');
cy.get('.section-header').should('have.length.at.least', 1);
cy.get('.endpoint-container').should('have.length.at.least', 1);
});
});

View File

@@ -26,7 +26,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@juggle/resize-observer": "^3.3.1",
"@mempool/mempool.js": "^2.2.4",
"@mempool/mempool.js": "2.3.0-dev1",
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
"@nguniversal/express-engine": "11.2.1",
"@types/qrcode": "1.4.1",
@@ -3230,12 +3230,40 @@
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
},
"node_modules/@mempool/mempool.js": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
"integrity": "sha512-G9Ga2jHLfAuU/qXikRBtTecYr7BhLJH1WbIahefnGpgP48DCQaj1jizvuRZHhoElUvUT5flRt/O9kLjlbToqhw==",
"version": "2.3.0-dev1",
"resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.3.0-dev1.tgz",
"integrity": "sha512-+UYGuG8qqdgrtC4J94hCs7+Dry8OprIixEarIC6rww1Nb5POz8n3NTDH8to1r0XLjPr+Du6/OX8fEN1QW94rNA==",
"dependencies": {
"axios": "^0.21.1",
"ws": "^7.4.3"
"axios": "0.24.0",
"ws": "8.3.0"
}
},
"node_modules/@mempool/mempool.js/node_modules/axios": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"dependencies": {
"follow-redirects": "^1.14.4"
}
},
"node_modules/@mempool/mempool.js/node_modules/ws": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
"integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@ng-bootstrap/ng-bootstrap": {
@@ -4541,6 +4569,7 @@
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"devOptional": true,
"dependencies": {
"follow-redirects": "^1.14.0"
}
@@ -7264,7 +7293,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">=0.3.1"
}
@@ -9122,7 +9151,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"devOptional": true,
"dev": true,
"engines": {
"node": ">=4"
}
@@ -9131,7 +9160,7 @@
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"devOptional": true,
"dev": true,
"dependencies": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
@@ -9416,7 +9445,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"devOptional": true,
"dev": true,
"dependencies": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -10385,7 +10414,7 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"devOptional": true
"dev": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
@@ -10453,7 +10482,7 @@
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"devOptional": true,
"dev": true,
"dependencies": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@@ -17962,6 +17991,7 @@
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"devOptional": true,
"engines": {
"node": ">=8.3.0"
}
@@ -20409,12 +20439,28 @@
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
},
"@mempool/mempool.js": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.2.4.tgz",
"integrity": "sha512-G9Ga2jHLfAuU/qXikRBtTecYr7BhLJH1WbIahefnGpgP48DCQaj1jizvuRZHhoElUvUT5flRt/O9kLjlbToqhw==",
"version": "2.3.0-dev1",
"resolved": "https://registry.npmjs.org/@mempool/mempool.js/-/mempool.js-2.3.0-dev1.tgz",
"integrity": "sha512-+UYGuG8qqdgrtC4J94hCs7+Dry8OprIixEarIC6rww1Nb5POz8n3NTDH8to1r0XLjPr+Du6/OX8fEN1QW94rNA==",
"requires": {
"axios": "^0.21.1",
"ws": "^7.4.3"
"axios": "0.24.0",
"ws": "8.3.0"
},
"dependencies": {
"axios": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"requires": {
"follow-redirects": "^1.14.4"
}
},
"ws": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz",
"integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==",
"requires": {}
}
}
},
"@ng-bootstrap/ng-bootstrap": {
@@ -21532,6 +21578,7 @@
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"devOptional": true,
"requires": {
"follow-redirects": "^1.14.0"
}
@@ -23795,7 +23842,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"devOptional": true
"dev": true
},
"diffie-hellman": {
"version": "5.0.3",
@@ -25290,13 +25337,13 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"devOptional": true
"dev": true
},
"har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"devOptional": true,
"dev": true,
"requires": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
@@ -25544,7 +25591,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"devOptional": true,
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -26286,7 +26333,7 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"devOptional": true
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -26339,7 +26386,7 @@
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"devOptional": true,
"dev": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@@ -32158,7 +32205,8 @@
"ws": {
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
"devOptional": true
},
"xhr2": {
"version": "0.2.0",

View File

@@ -34,7 +34,10 @@
"sync-assets": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
"sync-assets-dev": "node sync-assets.js dev",
"generate-config": "node generate-config.js",
"build-mempool.js": "tsc | browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
"build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js",
"build-mempool-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index.js --standalone mempoolJS > ./dist/mempool/browser/en-US/mempool.js",
"build-mempool-bisq-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-bisq.js --standalone bisqJS > ./dist/mempool/browser/en-US/bisq.js",
"build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
"test": "ng test",
"lint": "ng lint",
"e2e": "npm run generate-config && ng e2e",
@@ -70,7 +73,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@juggle/resize-observer": "^3.3.1",
"@mempool/mempool.js": "^2.2.4",
"@mempool/mempool.js": "2.3.0-dev1",
"@ng-bootstrap/ng-bootstrap": "^10.0.0",
"@nguniversal/express-engine": "11.2.1",
"@types/qrcode": "1.4.1",

View File

@@ -48,9 +48,10 @@ import { FeesBoxComponent } from './components/fees-box/fees-box.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook } from '@fortawesome/free-solid-svg-icons';
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons';
import { ApiDocsComponent } from './components/docs/api-docs.component';
import { DocsComponent } from './components/docs/docs.component';
import { ApiDocsNavComponent } from './components/docs/api-docs-nav.component';
import { CodeTemplateComponent } from './components/docs/code-template.component';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
@@ -59,6 +60,7 @@ import { StorageService } from './services/storage.service';
import { HttpCacheInterceptor } from './services/http-cache.interceptor';
import { SponsorComponent } from './components/sponsor/sponsor.component';
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [
@@ -102,6 +104,7 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
SponsorComponent,
PushTransactionComponent,
DocsComponent,
ApiDocsNavComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'serverApp' }),
@@ -111,6 +114,7 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
BrowserAnimationsModule,
InfiniteScrollModule,
NgbTypeaheadModule,
NgbModule,
FontAwesomeModule,
SharedModule,
NgxEchartsModule.forRoot({
@@ -161,5 +165,6 @@ export class AppModule {
library.addIcons(faAngleRight);
library.addIcons(faAngleLeft);
library.addIcons(faBook);
library.addIcons(faListUl);
}
}

View File

@@ -23,12 +23,12 @@
<a target="_blank" href="https://twitter.com/mempool">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
</a>
<a target="_blank" href="https://t.me/mempoolspace">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="telegram-plane" class="svg-inline--fa fa-telegram-plane fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M446.7 98.6l-67.6 318.8c-5.1 22.5-18.4 28.1-37.3 17.5l-103-75.9-49.7 47.8c-5.5 5.5-10.1 10.1-20.7 10.1l7.4-104.9 190.9-172.5c8.3-7.4-1.8-11.5-12.9-4.1L117.8 284 16.2 252.2c-22.1-6.9-22.5-22.1 4.6-32.7L418.2 66.4c18.4-6.9 34.5 4.1 28.5 32.2z"></path></svg>
</a>
<a target="_blank" href="https://keybase.io/team/mempool">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="keybase" class="svg-inline--fa fa-keybase fa-w-14 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M286.17 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18zm111.92-147.6c-9.5-14.62-39.37-52.45-87.26-73.71q-9.1-4.06-18.38-7.27a78.43 78.43 0 0 0-47.88-104.13c-12.41-4.1-23.33-6-32.41-5.77-.6-2-1.89-11 9.4-35L198.66 32l-5.48 7.56c-8.69 12.06-16.92 23.55-24.34 34.89a51 51 0 0 0-8.29-1.25c-41.53-2.45-39-2.33-41.06-2.33-50.61 0-50.75 52.12-50.75 45.88l-2.36 36.68c-1.61 27 19.75 50.21 47.63 51.85l8.93.54a214 214 0 0 0-46.29 35.54C14 304.66 14 374 14 429.77v33.64l23.32-29.8a148.6 148.6 0 0 0 14.56 37.56c5.78 10.13 14.87 9.45 19.64 7.33 4.21-1.87 10-6.92 3.75-20.11a178.29 178.29 0 0 1-15.76-53.13l46.82-59.83-24.66 74.11c58.23-42.4 157.38-61.76 236.25-38.59 34.2 10.05 67.45.69 84.74-23.84.72-1 1.2-2.16 1.85-3.22a156.09 156.09 0 0 1 2.8 28.43c0 23.3-3.69 52.93-14.88 81.64-2.52 6.46 1.76 14.5 8.6 15.74 7.42 1.57 15.33-3.1 18.37-11.15C429 443 434 414 434 382.32c0-38.58-13-77.46-35.91-110.92zM142.37 128.58l-15.7-.93-1.39 21.79 13.13.78a93 93 0 0 0 .32 19.57l-22.38-1.34a12.28 12.28 0 0 1-11.76-12.79L107 119c1-12.17 13.87-11.27 13.26-11.32l29.11 1.73a144.35 144.35 0 0 0-7 19.17zm148.42 172.18a10.51 10.51 0 0 1-14.35-1.39l-9.68-11.49-34.42 27a8.09 8.09 0 0 1-11.13-1.08l-15.78-18.64a7.38 7.38 0 0 1 1.34-10.34l34.57-27.18-14.14-16.74-17.09 13.45a7.75 7.75 0 0 1-10.59-1s-3.72-4.42-3.8-4.53a7.38 7.38 0 0 1 1.37-10.34L214 225.19s-18.51-22-18.6-22.14a9.56 9.56 0 0 1 1.74-13.42 10.38 10.38 0 0 1 14.3 1.37l81.09 96.32a9.58 9.58 0 0 1-1.74 13.44zM187.44 419a18 18 0 1 0 18 18 18 18 0 0 0-18-18z"></path></svg>
</a>
<a target="_blank" href="https://matrix.to/#/#mempool:bitcoin.kyoto">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="matrix" class="svg-inline--fa fa-matrix fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 1792"><path fill="currentColor" d="M40.467 163.152v1465.696H145.92V1664H0V128h145.92v35.152zm450.757 464.64v74.14h2.069c19.79-28.356 43.717-50.215 71.483-65.575 27.765-15.656 59.963-23.336 96-23.336 34.56 0 66.165 6.795 94.818 20.086 28.652 13.293 50.216 37.22 65.28 70.893 16.246-23.926 38.4-45.194 66.166-63.507 27.766-18.314 60.848-27.472 98.954-27.472 28.948 0 55.828 3.545 80.64 10.635 24.812 7.088 45.785 18.314 63.508 33.968 17.722 15.656 31.31 35.742 41.354 60.85 9.747 25.107 14.768 55.236 14.768 90.683v366.573h-150.35V865.28c0-18.314-.59-35.741-2.068-51.987-1.476-16.247-5.316-30.426-11.52-42.24-6.499-12.112-15.656-21.563-28.062-28.653-12.405-7.088-29.242-10.634-50.214-10.634-21.268 0-38.4 4.135-51.397 12.112-12.997 8.27-23.336 18.608-30.72 31.901-7.386 12.997-12.407 27.765-14.77 44.602-2.363 16.542-3.84 33.379-3.84 50.216v305.133H692.971v-307.2c0-16.247-.294-32.197-1.18-48.149-.591-15.95-3.84-30.424-9.157-44.011-5.317-13.293-14.178-24.223-26.585-32.197-12.406-7.976-30.425-12.112-54.646-12.112-7.088 0-16.542 1.478-28.062 4.726-11.52 3.25-23.04 9.157-33.968 18.02-10.93 8.86-20.383 21.563-28.063 38.103-7.68 16.543-11.52 38.4-11.52 65.28v317.834H349.44V627.792zm1004.309 1001.056V163.152H1390.08V128H1536v1536h-145.92v-35.152z"/></svg>
</a>
</div>
<div class="enterprise-sponsor">
@@ -178,7 +178,7 @@
</div>
<div class="maintainers" *ngIf="contributors.core.length">
<h3 i18n="about.project_staff">Project Staff</h3>
<h3 i18n="about.project_members">Project Members</h3>
<div class="wrapper">
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
<a [href]="'https://github.com/' + contributor.name" target="_blank" [title]="contributor.name">

View File

@@ -1,7 +1,8 @@
<div class="blocks-container blockchain-blocks-container" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
<div class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink">&nbsp;</a>
<a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div class="block-height">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
</div>

View File

@@ -15,6 +15,10 @@
text-decoration: none;
}
.blockLink.disabled {
pointer-events: none;
}
.mined-block {
position: absolute;
top: 0px;

View File

@@ -41,7 +41,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
};
constructor(
private stateService: StateService,
public stateService: StateService,
private router: Router,
private cd: ChangeDetectorRef,
) { }

View File

@@ -18,6 +18,11 @@
.blockchain-wrapper {
overflow: hidden;
height: 250px;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */
}
.position-container {

View File

@@ -0,0 +1,76 @@
<ng-template [ngIf]="network.val !== 'bisq' && network.val !== 'liquid'">
<p>General</p>
<a [routerLink]="['./']" fragment="get-difficulty-adjustment" (click)="collapseItem.toggle()">GET Difficulty Adjustment</a>
</ng-template>
<ng-template [ngIf]="network.val === 'bisq'">
<p>Markets</p>
<a [routerLink]="['./']" fragment="get-market-currencies" (click)="collapseItem.toggle()">GET Market Currencies</a>
<a [routerLink]="['./']" fragment="get-market-depth" (click)="collapseItem.toggle()">GET Market Depth</a>
<a [routerLink]="['./']" fragment="get-market-hloc" (click)="collapseItem.toggle()">GET Market HLOC</a>
<a [routerLink]="['./']" fragment="get-markets" (click)="collapseItem.toggle()">GET Markets</a>
<a [routerLink]="['./']" fragment="get-market-offers" (click)="collapseItem.toggle()">GET Market Offers</a>
<a [routerLink]="['./']" fragment="get-market-ticker" (click)="collapseItem.toggle()">GET Market Ticker</a>
<a [routerLink]="['./']" fragment="get-market-trades" (click)="collapseItem.toggle()">GET Market Trades</a>
<a [routerLink]="['./']" fragment="get-market-volumes" (click)="collapseItem.toggle()">GET Market Volumes</a>
</ng-template>
<ng-template [ngIf]="network.val === 'bisq'">
<p>General</p>
<a [routerLink]="['./']" fragment="get-stats" (click)="collapseItem.toggle()">GET Stats</a>
</ng-template>
<p>Addresses</p>
<a [routerLink]="['./']" fragment="get-address" (click)="collapseItem.toggle()">GET Address</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions" (click)="collapseItem.toggle()">GET Address Transactions</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions-chain" (click)="collapseItem.toggle()">GET Address Transactions Chain</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-transactions-mempool" (click)="collapseItem.toggle()">GET Address Transactions Mempool</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-address-utxo" (click)="collapseItem.toggle()">GET Address UTXO</a>
<ng-template [ngIf]="network.val === 'liquid'">
<p>Assets</p>
<a [routerLink]="['./']" fragment="get-assets" (click)="collapseItem.toggle()">GET Assets</a>
<a [routerLink]="['./']" fragment="get-assets-icons" (click)="collapseItem.toggle()">GET Assets Icons</a>
<a [routerLink]="['./']" fragment="get-asset-transactions" (click)="collapseItem.toggle()">GET Asset Transactions</a>
<a [routerLink]="['./']" fragment="get-asset-supply" (click)="collapseItem.toggle()">GET Asset Supply</a>
<a [routerLink]="['./']" fragment="get-asset-icon" (click)="collapseItem.toggle()">GET Asset Icon</a>
</ng-template>
<p>Blocks</p>
<a [routerLink]="['./']" fragment="get-block" (click)="collapseItem.toggle()">GET Block</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-header" (click)="collapseItem.toggle()">GET Block Header</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-height" (click)="collapseItem.toggle()">GET Block Height</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-raw" (click)="collapseItem.toggle()">GET Block Raw</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-status" (click)="collapseItem.toggle()">GET Block Status</a>
<a [routerLink]="['./']" fragment="get-block-tip-height" (click)="collapseItem.toggle()">GET Block Tip Height</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-tip-hash" (click)="collapseItem.toggle()">GET Block Tip Hash</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transaction-id" (click)="collapseItem.toggle()">GET Block Transaction ID</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transaction-ids" (click)="collapseItem.toggle()">GET Block Transaction IDs</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-block-transactions" (click)="collapseItem.toggle()">GET Block Transactions</a>
<a [routerLink]="['./']" fragment="get-blocks" (click)="collapseItem.toggle()">GET Blocks</a>
<ng-template [ngIf]="network.val !== 'bisq'">
<p>Fees</p>
<a [routerLink]="['./']" fragment="get-mempool-blocks-fees" (click)="collapseItem.toggle()">GET Mempool Blocks Fees</a>
<a [routerLink]="['./']" fragment="get-recommended-fees" (click)="collapseItem.toggle()">GET Recommended Fees</a>
</ng-template>
<ng-template [ngIf]="network.val !== 'bisq'">
<p>Mempool</p>
<a [routerLink]="['./']" fragment="get-mempool" (click)="collapseItem.toggle()">GET Mempool</a>
<a [routerLink]="['./']" fragment="get-mempool-transaction-ids" (click)="collapseItem.toggle()">GET Mempool Transaction IDs</a>
<a [routerLink]="['./']" fragment="get-mempool-recent" (click)="collapseItem.toggle()">GET Mempool Recent</a>
</ng-template>
<p>Transactions</p>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-cpfp" (click)="collapseItem.toggle()">GET Children Pay for Parent</a>
<a [routerLink]="['./']" fragment="get-transaction" (click)="collapseItem.toggle()">GET Transaction</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-hex" (click)="collapseItem.toggle()">GET Transaction Hex</a>
<a *ngIf="network.val !== 'bisq' && network.val !== 'liquid'" [routerLink]="['./']" fragment="get-transaction-merkleblock-proof" (click)="collapseItem.toggle()">GET Transaction Merkleblock Proof</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-merkle-proof" (click)="collapseItem.toggle()">GET Transaction Merkle Proof</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-outspend" (click)="collapseItem.toggle()">GET Transaction Outspend</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-outspends" (click)="collapseItem.toggle()">GET Transaction Outspends</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-raw" (click)="collapseItem.toggle()">GET Transaction Raw</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="get-transaction-status" (click)="collapseItem.toggle()">GET Transaction Status</a>
<a *ngIf="network.val === 'bisq'" [routerLink]="['./']" fragment="get-transactions" (click)="collapseItem.toggle()">GET Transactions</a>
<a *ngIf="network.val !== 'bisq'" [routerLink]="['./']" fragment="post-transaction" (click)="collapseItem.toggle()">POST Transaction</a>

View File

@@ -0,0 +1,17 @@
p {
color: #4a68b9;
font-weight: 700;
margin: 10px 0;
margin: 15px 0 10px 0;
}
p:first-child {
margin-top: 0
}
a {
color: #fff;
text-decoration: none;
display: block;
margin: 5px 0;
}

View File

@@ -0,0 +1,18 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-api-docs-nav',
templateUrl: './api-docs-nav.component.html',
styleUrls: ['./api-docs-nav.component.scss']
})
export class ApiDocsNavComponent implements OnInit {
@Input() network: any;
@Input() collapseItem: any = { toggle: () => {} };
constructor() { }
ngOnInit(): void {
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,10 @@ li.nav-item {
}
}
.no-bottom-space {
margin-bottom: 0;
}
.nav-tabs .nav-link.active {
border-bottom: 1px solid #fff;
@media (min-width: 676px){
@@ -72,10 +76,131 @@ li.nav-item {
padding: 15px;
}
#restAPI .api-category {
margin: 30px 0;
#doc-nav-desktop {
width: 300px;
}
.api-category h4 {
margin-bottom: 15px;
#doc-nav-desktop.relative {
float: left;
overflow: hidden;
}
#doc-nav-desktop.fixed {
float: unset;
position: fixed;
top: 20px;
overflow-y: auto;
height: calc(100vh - 50px);
scrollbar-color: #2d3348 #11131f;
scrollbar-width: thin;
}
::-webkit-scrollbar {
width: 3px;
}
::-webkit-scrollbar-track {
background: #11131f;
}
::-webkit-scrollbar-thumb {
background-color: #2d3348;
border-radius: 5px;
border: none;
}
.doc-content {
width: calc(100% - 330px);
float: right;
}
.endpoint-container:before {
display: block;
content: " ";
height: 1px;
margin-top: -1px;
visibility: hidden;
}
.endpoint-container .section-header {
display: block;
background-color: #2d3348;
color: #1bd8f4;
padding: 1rem 1.3rem 1rem 1.3rem;
font-weight: bold;
border-radius: 0.25rem;
margin: 20px 0 20px 0;
font-size: 24px;
position: relative;
}
.endpoint-container .section-header:hover {
text-decoration: none;
}
.endpoint-container .section-header span {
color: #fff;
background-color: #653b9c;
font-size: 12px;
text-transform: uppercase;
font-weight: 400;
padding: 8px 10px;
letter-spacing: 1px;
border-radius: 0.25rem;
font-family: monospace;
float: right;
}
#doc-nav-mobile {
position: fixed;
top: 20px;
width: calc(100% - 60px);
z-index: 100;
}
#doc-nav-mobile > div {
background-color: #2d3348;
z-index: 100;
border-radius: 0 0 0.5rem 0.5rem;
height: 55vh;
overflow-y: auto;
}
#doc-nav-mobile button {
width: 100%;
background-color: #105fb0;
color: #fff;
border-color: #105fb0;
border-radius: 0.5rem 0.5rem 0 0;
}
@media (max-width: 992px) {
.hide-on-mobile {
display: none;
}
.doc-content {
width: 100%;
}
.endpoint-container .section-header {
margin: 40px 0 70px 0;
}
.endpoint-container .section-header span {
float: none;
position: absolute;
top: unset;
left: 0;
bottom: -50px;
}
.endpoint-container:before {
height: 30px;
margin-top: -12px;
}
}
@media (min-width: 992px) {
.hide-on-desktop {
display: none;
}
}

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, Input } from '@angular/core';
import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';
import { Env, StateService } from 'src/app/services/state.service';
import { Observable, merge, of } from 'rxjs';
import { SeoService } from 'src/app/services/seo.service';
@@ -17,12 +17,26 @@ export class ApiDocsComponent implements OnInit {
code: any;
baseNetworkUrl = '';
@Input() restTabActivated: Boolean;
@ViewChild( "mobileFixedApiNav", { static: false } ) mobileFixedApiNav: ElementRef;
desktopDocsNavPosition = "relative";
showFloatingDocsNav = false;
mobileMenuOpen = true;
constructor(
private stateService: StateService,
private seoService: SeoService,
) { }
ngAfterViewInit() {
const that = this;
setTimeout( () => {
window.addEventListener('scroll', function() {
that.desktopDocsNavPosition = ( window.pageYOffset > 182 ) ? "fixed" : "relative";
that.showFloatingDocsNav = ( window.pageYOffset > ( that.mobileFixedApiNav.nativeElement.offsetHeight + 188 ) ) ? true : false;
});
}, 1 );
}
ngOnInit(): void {
this.env = this.stateService.env;
this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`);
@@ -628,24 +642,6 @@ export class ApiDocsComponent implements OnInit {
console.log(asset);
`,
},
codeSampleMainnet: {
esModule: [],
commonJS: [],
curl: [],
response: ''
},
codeSampleTestnet: {
esModule: [],
commonJS: [],
curl: [],
response: ''
},
codeSampleSignet: {
esModule: [],
commonJS: [],
curl: [],
response: ''
},
codeSampleLiquid: {
esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
@@ -679,6 +675,47 @@ export class ApiDocsComponent implements OnInit {
response: ''
},
},
assetIcons: {
codeTemplate: {
curl: `/api/v1/assets/icons`,
commonJS: `
const { %{0}: { assets } } = mempoolJS();
const assetsIcons = await assets.getAssetsIcons();
document.getElementById("result").textContent = JSON.stringify(assetsIcons, undefined, 2);
`,
esModule: `
const { %{0}: { assets } } = mempoolJS();
const assetsIcons = await assets.getAssetsIcons();
console.log(assetsIcons);
`,
},
codeSampleLiquid: {
esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
curl: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
response: `[
"6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d",
"ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"
...
]`,
},
},
assetIcon: {
noWrap: true,
codeTemplate: {
curl: `/api/v1/asset/%{1}/icon`,
commonJS: `<img src="https://liquid.place/api/v1/asset/%{1}/icon">`,
},
codeSampleLiquid: {
esModule: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
commonJS: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
curl: [`6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`],
response: `PNG`,
},
},
assetTransactions: {
codeTemplate: {
curl: `/api/asset/%{1}/txs`,

View File

@@ -164,6 +164,10 @@ init();`;
codeText = this.replaceJSPlaceholder(codeText, code.codeSampleBisq.esModule);
}
if (code.noWrap) {
return codeText;
}
let importText = `<script src="https://mempool.space/mempool.js"></script>`;
if (this.env.BASE_MODULE === 'bisq') {
importText = `<script src="https://bisq.markets/bisq.js"></script>`;

View File

@@ -27,5 +27,13 @@
<div id="main-tab-content" [ngbNavOutlet]="nav" class="mt-2"></div>
<br>
<div id="footer" class="text-center">
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
</div>
</div>
</div>

View File

@@ -1,4 +1,9 @@
#main-tab-content {
text-align: left;
padding-top: 10px;
scroll-behavior: smooth;
}
#footer {
clear: both;
}

View File

@@ -3,7 +3,8 @@
<div class="flashing">
<ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn">
<div class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink">
<a [routerLink]="['/mempool-block/' | relativeUrl, i]" class="blockLink">&nbsp;</a>
<a draggable="false" [routerLink]="['/mempool-block/' | relativeUrl, i]"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div class="block-body">
<div class="fees">
~{{ projectedBlock.medianFee | number:feeRounding }} <span i18n="shared.sat-vbyte|sat/vB">sat/vB</span>

View File

@@ -117,6 +117,10 @@
z-index: 10;
}
.blockLink.disabled {
pointer-events: none;
}
.blockLink:hover {
text-decoration: none;
}

View File

@@ -8,8 +8,11 @@
<div *ngIf="countdown > 0" class="warning-label">{{ eventName }} in {{ countdown | number }} block{{ countdown === 1 ? '' : 's' }}!</div>
<div id="blockchain-container" dir="ltr">
<app-blockchain></app-blockchain>
<div id="blockchain-container" dir="ltr" #blockchainContainer
(mousedown)="onMouseDown($event)"
(dragstart)="onDragStart($event)"
>
<app-blockchain></app-blockchain>
</div>
<router-outlet></router-outlet>

View File

@@ -1,8 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
import { WebsocketService } from 'src/app/services/websocket.service';
import { StateService } from 'src/app/services/state.service';
import { specialBlocks } from 'src/app/app.constants';
import { takeLast } from 'rxjs/operators';
@Component({
selector: 'app-start',
@@ -16,6 +15,9 @@ export class StartComponent implements OnInit {
countdown = 0;
specialEvent = false;
eventName = '';
mouseDragStartX: number;
blockchainScrollLeftInit: number;
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
constructor(
private websocketService: WebsocketService,
@@ -50,4 +52,27 @@ export class StartComponent implements OnInit {
});
}
onMouseDown(event: MouseEvent) {
this.mouseDragStartX = event.clientX;
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
}
onDragStart(event: MouseEvent) { // Ignore Firefox annoying default drag behavior
event.preventDefault();
}
// We're catching the whole page event here because we still want to scroll blocks
// even if the mouse leave the blockchain blocks container. Same idea for mouseup below.
@HostListener('document:mousemove', ['$event'])
onMouseMove(event: MouseEvent): void {
if (this.mouseDragStartX != null) {
this.stateService.setBlockScrollingInProgress(true);
this.blockchainContainer.nativeElement.scrollLeft =
this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX
}
}
@HostListener('document:mouseup', [])
onMouseUp() {
this.mouseDragStartX = null;
this.stateService.setBlockScrollingInProgress(false);
}
}

View File

@@ -89,6 +89,8 @@ export class StateService {
markBlock$ = new ReplaySubject<MarkBlockState>();
keyNavigation$ = new Subject<KeyboardEvent>();
blockScrolling$: Subject<boolean> = new Subject<boolean>();
constructor(
@Inject(PLATFORM_ID) private platformId: any,
private router: Router,
@@ -176,4 +178,8 @@ export class StateService {
if (!prop) { return false; }
return document[prop];
}
setBlockScrollingInProgress(value: boolean) {
this.blockScrolling$.next(value);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,106 +0,0 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
CREATE TABLE `blocks` (
`height` int(11) NOT NULL,
`hash` varchar(65) NOT NULL,
`size` int(11) NOT NULL,
`weight` int(11) NOT NULL,
`minFee` int(11) NOT NULL,
`maxFee` int(11) NOT NULL,
`time` int(11) NOT NULL,
`fees` double NOT NULL,
`nTx` int(11) NOT NULL,
`medianFee` double NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `statistics` (
`id` int(11) NOT NULL,
`added` datetime NOT NULL,
`unconfirmed_transactions` int(11) UNSIGNED NOT NULL,
`tx_per_second` float UNSIGNED NOT NULL,
`vbytes_per_second` int(10) UNSIGNED NOT NULL,
`mempool_byte_weight` int(10) UNSIGNED NOT NULL,
`fee_data` longtext NOT NULL,
`total_fee` double UNSIGNED NOT NULL,
`vsize_1` int(11) NOT NULL,
`vsize_2` int(11) NOT NULL,
`vsize_3` int(11) NOT NULL,
`vsize_4` int(11) NOT NULL,
`vsize_5` int(11) NOT NULL,
`vsize_6` int(11) NOT NULL,
`vsize_8` int(11) NOT NULL,
`vsize_10` int(11) NOT NULL,
`vsize_12` int(11) NOT NULL,
`vsize_15` int(11) NOT NULL,
`vsize_20` int(11) NOT NULL,
`vsize_30` int(11) NOT NULL,
`vsize_40` int(11) NOT NULL,
`vsize_50` int(11) NOT NULL,
`vsize_60` int(11) NOT NULL,
`vsize_70` int(11) NOT NULL,
`vsize_80` int(11) NOT NULL,
`vsize_90` int(11) NOT NULL,
`vsize_100` int(11) NOT NULL,
`vsize_125` int(11) NOT NULL,
`vsize_150` int(11) NOT NULL,
`vsize_175` int(11) NOT NULL,
`vsize_200` int(11) NOT NULL,
`vsize_250` int(11) NOT NULL,
`vsize_300` int(11) NOT NULL,
`vsize_350` int(11) NOT NULL,
`vsize_400` int(11) NOT NULL,
`vsize_500` int(11) NOT NULL,
`vsize_600` int(11) NOT NULL,
`vsize_700` int(11) NOT NULL,
`vsize_800` int(11) NOT NULL,
`vsize_900` int(11) NOT NULL,
`vsize_1000` int(11) NOT NULL,
`vsize_1200` int(11) NOT NULL,
`vsize_1400` int(11) NOT NULL,
`vsize_1600` int(11) NOT NULL,
`vsize_1800` int(11) NOT NULL,
`vsize_2000` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `transactions` (
`blockheight` int(11) NOT NULL,
`txid` varchar(65) NOT NULL,
`fee` double NOT NULL,
`feePerVsize` double NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `blocks`
ADD PRIMARY KEY (`height`);
ALTER TABLE `statistics`
ADD PRIMARY KEY (`id`);
ALTER TABLE `transactions`
ADD PRIMARY KEY (`txid`),
ADD KEY `blockheight` (`blockheight`);
ALTER TABLE `statistics`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
CREATE TABLE `last_elements_block` (
`block` int(11) NOT NULL,
`datetime` int(11) NOT NULL,
`block_hash` varchar(65) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `last_elements_block` VALUES(0, 0, '');
CREATE TABLE `elements_pegs` (
`block` int(11) NOT NULL,
`datetime` int(11) NOT NULL,
`amount` bigint(20) NOT NULL,
`txid` varchar(65) NOT NULL,
`txindex` int(11) NOT NULL,
`bitcoinaddress` varchar(100) NOT NULL,
`bitcointxid` varchar(65) NOT NULL,
`bitcoinindex` int(11) NOT NULL,
`final_tx` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;