diff --git a/packages/api/README.md b/packages/api/README.md index 30c5f42b7..7de82320a 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -106,15 +106,55 @@ HTTP /status hash: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", timestamp: "2024-06-06T21:50:20.949Z" } + "epoch": { + "number": 3640, + "firstBlockHeight": 72534, + "firstCoreBlockHeight": 1160707, + "startTime": 1734457229495, + "feeMultiplier": 1, + "endTime": 1734460829495 + }, + "transactionsCount": 25912, + "totalCredits": 7288089799960610, + "totalCollectedFeesDay": 12733263640, + "transfersCount": 1849, + "dataContractsCount": 630, + "documentsCount": 15384, + "identitiesCount": 712, + "network": "dash-testnet-51", + "api": { + "version": "1.0.7", + "block": { + "height": 72555, + "hash": "EDA1CDF601224CD3ED168D35B4699DE2796F774B526103C64D371EF3AAFD8274", + "timestamp": "2024-12-17T17:57:08.758Z" + } + }, + "tenderdash": { + "version": "1.4.0", + "block": { + "height": 72555, + "hash": "EDA1CDF601224CD3ED168D35B4699DE2796F774B526103C64D371EF3AAFD8274", + "timestamp": "2024-12-17T17:57:08.758Z" + } + }, + "versions": { + "software": { + "dapi": "1.5.1", + "drive": "1.6.2", + "tenderdash": "1.4.0" }, - tenderdash: { - version: "0.14.0-dev.6", - block: { - height: 20154, - hash: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", - timestamp: "2024-06-06T21:53:27.947Z" - } - } + "protocol": { + "tenderdash": { + "p2p": 10, + "block": 14 + }, + "drive": { + "latest": 6, + "current": 6 + } + } + } } ``` --- @@ -762,6 +802,9 @@ GET /identity/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec "topUpsGasSpent": 46350660, "withdrawalsGasSpent": 0, "lastWithdrawalHash": null, + "lastWithdrawalTimestamp": null, + "totalTopUps": 0, + "totalWithdrawals": 0, "publicKeys": [ { "keyId": 0, @@ -1034,8 +1077,9 @@ Response codes: ### Transfers by Identity Return all transfers made by the given identity * `limit` cannot be more then 100 +* `type` cannot be less, then 0 and more then 8 ``` -GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?page=1&limit=10&order=asc&type=1 +GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?hash=445E6F081DEE877867816AD3EF492E2C0BD1DDCCDC9C793B23DDDAF8AEA23118&page=1&limit=10&order=asc&type=6 { pagination: { @@ -1052,7 +1096,7 @@ GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?page=1&li txHash: "445E6F081DEE877867816AD3EF492E2C0BD1DDCCDC9C793B23DDDAF8AEA23118", type: 6, blockHash: "73171E0A8DCC10C6DA501E1C70A9C1E0BD6F1F8F834C2A1E787AF19B1F361D5E" - }, ... + } ] } ``` diff --git a/packages/api/package.json b/packages/api/package.json index 8b460ef85..570b94c37 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,9 +12,9 @@ "lint": "standard ." }, "dependencies": { + "@dashevo/wasm-dpp": "github:owl352/wasm-dpp", "@dashevo/dapi-client": "github:owl352/dapi-client", "@dashevo/dashd-rpc": "19.0.0", - "@dashevo/wasm-dpp": "github:owl352/wasm-dpp", "@fastify/cors": "^8.3.0", "@scure/base": "^1.1.5", "bs58": "^6.0.0", diff --git a/packages/api/src/DAPI.js b/packages/api/src/DAPI.js index 8649b4ad2..db7e425d7 100644 --- a/packages/api/src/DAPI.js +++ b/packages/api/src/DAPI.js @@ -106,6 +106,10 @@ class DAPI { } }) } + + async getStatus () { + return this.dapi.platform.getStatus() + } } module.exports = DAPI diff --git a/packages/api/src/controllers/IdentitiesController.js b/packages/api/src/controllers/IdentitiesController.js index 4644d2807..91c985f8b 100644 --- a/packages/api/src/controllers/IdentitiesController.js +++ b/packages/api/src/controllers/IdentitiesController.js @@ -72,9 +72,16 @@ class IdentitiesController { getTransfersByIdentity = async (request, response) => { const { identifier } = request.params - const { page = 1, limit = 10, order = 'asc', type = undefined } = request.query - - const transfers = await this.identitiesDAO.getTransfersByIdentity(identifier, Number(page ?? 1), Number(limit ?? 10), order, type) + const { page = 1, limit = 10, order = 'asc', type, hash } = request.query + + const transfers = await this.identitiesDAO.getTransfersByIdentity( + identifier, + hash, + Number(page ?? 1), + Number(limit ?? 10), + order, + type + ) response.send(transfers) } diff --git a/packages/api/src/controllers/MainController.js b/packages/api/src/controllers/MainController.js index b6a4883cc..ef25c2c46 100644 --- a/packages/api/src/controllers/MainController.js +++ b/packages/api/src/controllers/MainController.js @@ -11,20 +11,21 @@ const { base58 } = require('@scure/base') const API_VERSION = require('../../package.json').version class MainController { - constructor (knex, dapi) { + constructor (knex, dapi, client) { this.blocksDAO = new BlocksDAO(knex, dapi) this.dataContractsDAO = new DataContractsDAO(knex) this.documentsDAO = new DocumentsDAO(knex) this.transactionsDAO = new TransactionsDAO(knex, dapi) - this.identitiesDAO = new IdentitiesDAO(knex, dapi) + this.identitiesDAO = new IdentitiesDAO(knex, dapi, client) this.validatorsDAO = new ValidatorsDAO(knex) this.dapi = dapi } getStatus = async (request, response) => { - const [blocks, stats, tdStatus, epochsInfo, totalCredits, totalCollectedFeesDay] = (await Promise.allSettled([ + const [blocks, stats, status, tdStatus, epochsInfo, totalCredits, totalCollectedFeesDay] = (await Promise.allSettled([ this.blocksDAO.getBlocks(1, 1, 'desc'), this.blocksDAO.getStats(), + this.dapi.getStatus(), TenderdashRPC.getStatus(), this.dapi.getEpochsInfo(1), this.dapi.getTotalCredits(), @@ -56,12 +57,29 @@ class MainController { } }, tenderdash: { - version: tdStatus?.version ?? null, + version: status?.version?.software.tenderdash ?? null, block: { height: tdStatus?.highestBlock?.height ?? null, hash: tdStatus?.highestBlock?.hash ?? null, timestamp: tdStatus?.highestBlock?.timestamp ?? null } + }, + versions: { + software: { + dapi: status?.version?.software.dapi ?? null, + drive: status?.version?.software.drive ?? null, + tenderdash: status?.version?.software.tenderdash ?? null + }, + protocol: { + tenderdash: { + p2p: status?.version?.protocol.tenderdash?.p2p ?? null, + block: status?.version?.protocol.tenderdash?.block ?? null + }, + drive: { + latest: status?.version?.protocol.drive?.latest ?? null, + current: status?.version?.protocol.drive?.current ?? null + } + } } }) } diff --git a/packages/api/src/dao/IdentitiesDAO.js b/packages/api/src/dao/IdentitiesDAO.js index 09e350c81..a06c25ff3 100644 --- a/packages/api/src/dao/IdentitiesDAO.js +++ b/packages/api/src/dao/IdentitiesDAO.js @@ -71,6 +71,10 @@ module.exports = class IdentitiesDAO { .from('with_alias') .limit(1) + const statisticSubquery = this.knex('state_transitions') + .whereRaw(`owner = identifier and type=${IDENTITY_CREDIT_WITHDRAWAL}`) + .as('statistic') + const rows = await this.knex.with('with_alias', mainQuery) .select( 'identifier', 'owner', 'revision', @@ -91,13 +95,28 @@ module.exports = class IdentitiesDAO { .where('owner', identifier) .andWhere('type', IDENTITY_CREDIT_WITHDRAWAL) .as('withdrawals_gas_spent')) - .select(this.knex('state_transitions') + .select(this.knex(statisticSubquery) .select('hash') - .where('owner', identifier) - .andWhere('type', IDENTITY_CREDIT_WITHDRAWAL) + .where('type', IDENTITY_CREDIT_WITHDRAWAL) .orderBy('id', 'desc') .limit(1) .as('last_withdrawal_hash')) + .select(this.knex(statisticSubquery) + .select('timestamp') + .where('type', IDENTITY_CREDIT_WITHDRAWAL) + .orderBy('id', 'desc') + .limit(1) + .as('last_withdrawal_timestamp')) + .select( + this.knex(statisticSubquery) + .count('id') + .whereRaw(`type=${IDENTITY_CREDIT_WITHDRAWAL}`) + .as('total_withdrawals')) + .select( + this.knex(statisticSubquery) + .count('id') + .whereRaw(`type=${IDENTITY_TOP_UP}`) + .as('total_top_ups')) .from('with_alias') if (!rows.length) { @@ -125,7 +144,7 @@ module.exports = class IdentitiesDAO { if (row.tx_data) { const { assetLockProof } = await decodeStateTransition(this.client, row.tx_data) - fundingCoreTx = assetLockProof?.txid + fundingCoreTx = assetLockProof?.fundingCoreTx } return Identity.fromObject({ @@ -356,24 +375,31 @@ module.exports = class IdentitiesDAO { return new PaginatedResultSet(rows.map(row => Transaction.fromRow(row)), page, limit, totalCount) } - getTransfersByIdentity = async (identifier, page, limit, order, type) => { + getTransfersByIdentity = async (identifier, hash, page, limit, order, type) => { const fromRank = (page - 1) * limit + 1 const toRank = fromRank + limit - 1 + let searchQuery = `(transfers.sender = '${identifier}' OR transfers.recipient = '${identifier}')` + + if (typeof type === 'number') { + searchQuery = searchQuery + ` AND state_transitions.type = ${type}` + } + + if (hash) { + searchQuery = searchQuery + ` AND state_transitions.hash = '${hash}'` + } + const subquery = this.knex('transfers') .select( 'transfers.id as id', 'transfers.amount as amount', 'transfers.sender as sender', 'transfers.recipient as recipient', 'transfers.state_transition_hash as tx_hash', 'state_transitions.block_hash as block_hash', - 'state_transitions.type as type' + 'state_transitions.type as type', + 'state_transitions.gas_used as gas_used' ) .select(this.knex.raw(`rank() over (order by transfers.id ${order}) rank`)) - .whereRaw(`(transfers.sender = '${identifier}' OR transfers.recipient = '${identifier}') ${ - typeof type === 'number' - ? `AND state_transitions.type = ${type}` - : '' - }`) + .whereRaw(searchQuery) .leftJoin('state_transitions', 'state_transitions.hash', 'transfers.state_transition_hash') const rows = await this.knex.with('with_alias', subquery) @@ -381,7 +407,7 @@ module.exports = class IdentitiesDAO { 'rank', 'amount', 'block_hash', 'type', 'sender', 'recipient', 'with_alias.id', 'tx_hash', 'blocks.timestamp as timestamp', - 'block_hash' + 'block_hash', 'gas_used' ) .select(this.knex('with_alias').count('*').as('total_count')) .leftJoin('blocks', 'blocks.hash', 'with_alias.block_hash') diff --git a/packages/api/src/models/Identity.js b/packages/api/src/models/Identity.js index 2e3f1901e..5675a0fa8 100644 --- a/packages/api/src/models/Identity.js +++ b/packages/api/src/models/Identity.js @@ -17,6 +17,9 @@ module.exports = class Identity { lastWithdrawalHash publicKeys fundingCoreTx + totalTopUps + totalWithdrawals + lastWithdrawalTimestamp constructor ( identifier, owner, revision, @@ -25,7 +28,9 @@ module.exports = class Identity { totalTransfers, txHash, isSystem, aliases, totalGasSpent, averageGasSpent, topUpsGasSpent, withdrawalsGasSpent, - lastWithdrawalHash, publicKeys, fundingCoreTx + lastWithdrawalHash, lastWithdrawalTimestamp, + totalTopUps, totalWithdrawals, publicKeys, + fundingCoreTx ) { this.identifier = identifier ? identifier.trim() : null this.owner = owner ? owner.trim() : null @@ -46,6 +51,9 @@ module.exports = class Identity { this.lastWithdrawalHash = lastWithdrawalHash ?? null this.publicKeys = publicKeys ?? [] this.fundingCoreTx = fundingCoreTx ?? null + this.totalTopUps = totalTopUps ?? null + this.totalWithdrawals = totalWithdrawals ?? null + this.lastWithdrawalTimestamp = lastWithdrawalTimestamp ?? null } static fromObject ({ @@ -55,7 +63,8 @@ module.exports = class Identity { totalTransfers, txHash, isSystem, aliases, totalGasSpent, averageGasSpent, topUpsGasSpent, withdrawalsGasSpent, - lastWithdrawalHash, publicKeys, fundingCoreTx + lastWithdrawalHash, publicKeys, fundingCoreTx, + totalTopUps, totalWithdrawals, lastWithdrawalTimestamp }) { return new Identity( identifier, @@ -75,6 +84,9 @@ module.exports = class Identity { topUpsGasSpent, withdrawalsGasSpent, lastWithdrawalHash, + lastWithdrawalTimestamp, + totalTopUps, + totalWithdrawals, publicKeys, fundingCoreTx ) @@ -88,7 +100,8 @@ module.exports = class Identity { total_transfers, tx_hash, is_system, aliases, total_gas_spent, average_gas_spent, top_ups_gas_spent, withdrawals_gas_spent, - last_withdrawal_hash + last_withdrawal_hash, last_withdrawal_timestamp, + total_top_ups, total_withdrawals }) { return new Identity( identifier?.trim(), @@ -107,7 +120,10 @@ module.exports = class Identity { Number(average_gas_spent), Number(top_ups_gas_spent), Number(withdrawals_gas_spent), - last_withdrawal_hash + last_withdrawal_hash, + last_withdrawal_timestamp, + Number(total_top_ups), + Number(total_withdrawals) ) } } diff --git a/packages/api/src/models/Transfer.js b/packages/api/src/models/Transfer.js index 48c815a9c..f6878c1b5 100644 --- a/packages/api/src/models/Transfer.js +++ b/packages/api/src/models/Transfer.js @@ -7,8 +7,9 @@ module.exports = class Transfer { txHash type blockHash + gasUsed - constructor (amount, sender, recipient, timestamp, txHash, type, blockHash) { + constructor (amount, sender, recipient, timestamp, txHash, type, blockHash, gasUsed) { this.amount = amount ?? null this.sender = sender ? sender.trim() : null this.recipient = recipient ? recipient.trim() : null @@ -16,10 +17,11 @@ module.exports = class Transfer { this.txHash = txHash ?? null this.type = type ?? null this.blockHash = blockHash ?? null + this.gasUsed = gasUsed ?? null } // eslint-disable-next-line camelcase - static fromRow ({ amount, sender, recipient, timestamp, tx_hash, type, block_hash }) { - return new Transfer(parseInt(amount), sender, recipient, timestamp, tx_hash, type, block_hash) + static fromRow ({ amount, sender, recipient, timestamp, tx_hash, type, block_hash, gas_used }) { + return new Transfer(parseInt(amount), sender, recipient, timestamp, tx_hash, type, block_hash, Number(gas_used)) } } diff --git a/packages/api/src/schemas.js b/packages/api/src/schemas.js index 675271b75..e30cdf061 100644 --- a/packages/api/src/schemas.js +++ b/packages/api/src/schemas.js @@ -32,6 +32,11 @@ const schemaTypes = [ minimum: 0, maximum: 8 }, + hash: { + type: 'string', + minLength: 64, + maxLength: 64 + }, transaction_type: { type: ['array', 'null'], items: { diff --git a/packages/api/src/server.js b/packages/api/src/server.js index e5261ba27..9f03e6741 100644 --- a/packages/api/src/server.js +++ b/packages/api/src/server.js @@ -76,7 +76,7 @@ module.exports = { await knex.raw('select 1+1') - const mainController = new MainController(knex, dapi) + const mainController = new MainController(knex, dapi, client) const epochController = new EpochController(knex, dapi) const blocksController = new BlocksController(knex, dapi) const transactionsController = new TransactionsController(client, knex, dapi) diff --git a/packages/api/src/utils.js b/packages/api/src/utils.js index 5e70bbc41..dc2aef542 100644 --- a/packages/api/src/utils.js +++ b/packages/api/src/utils.js @@ -117,14 +117,12 @@ const decodeStateTransition = async (client, base64) => { decoded.assetLockProof = { coreChainLockedHeight: assetLockProof instanceof ChainAssetLockProof ? assetLockProof.getCoreChainLockedHeight() : null, type: assetLockProof instanceof InstantAssetLockProof ? 'instantSend' : 'chainLock', - instantLock: assetLockProof instanceof InstantAssetLockProof ? assetLockProof.toJSON().instantLock : null, + instantLock: assetLockProof instanceof InstantAssetLockProof ? assetLockProof.getInstantLock().toString('base64') : null, fundingAmount: decodedTransaction?.outputAmount ?? null, - txid: Buffer.from(assetLockProof.getOutPoint().slice(0, 32).toReversed()).toString('hex'), - vout: assetLockProof.getOutPoint().readInt8(32), - fundingAddress: assetLockProof.getOutput - ? dashcorelib.Script(assetLockProof.getOutput().script).toAddress(NETWORK).toString() - : null + fundingCoreTx: Buffer.from(assetLockProof.getOutPoint().slice(0, 32).toReversed()).toString('hex'), + vout: assetLockProof.getOutPoint().readInt8(32) } + decoded.userFeeIncrease = stateTransition.getUserFeeIncrease() decoded.identityId = stateTransition.getIdentityId().toString() decoded.signature = stateTransition.getSignature()?.toString('hex') ?? null @@ -167,12 +165,10 @@ const decodeStateTransition = async (client, base64) => { coreChainLockedHeight: assetLockProof instanceof ChainAssetLockProof ? assetLockProof.getCoreChainLockedHeight() : null, type: assetLockProof instanceof InstantAssetLockProof ? 'instantSend' : 'chainLock', fundingAmount: decodedTransaction?.outputAmount ?? null, - txid: Buffer.from(assetLockProof.getOutPoint().slice(0, 32).toReversed()).toString('hex'), - vout: assetLockProof.getOutPoint().readInt8(32), - fundingAddress: assetLockProof.getOutput - ? dashcorelib.Script(assetLockProof.getOutput().script).toAddress(NETWORK).toString() - : null + fundingCoreTx: Buffer.from(assetLockProof.getOutPoint().slice(0, 32).toReversed()).toString('hex'), + vout: assetLockProof.getOutPoint().readInt8(32) } + decoded.identityId = stateTransition.getIdentityId().toString() decoded.amount = output.satoshis * 1000 decoded.signature = stateTransition.getSignature()?.toString('hex') ?? null @@ -213,7 +209,7 @@ const decodeStateTransition = async (client, base64) => { decoded.userFeeIncrease = stateTransition.getUserFeeIncrease() decoded.identityId = stateTransition.getOwnerId().toString() decoded.revision = stateTransition.getRevision() - // TODO: Add contract bounds + decoded.publicKeysToAdd = stateTransition.getPublicKeysToAdd() .map(key => { const { contractBounds } = key.toObject() @@ -264,7 +260,7 @@ const decodeStateTransition = async (client, base64) => { break } case StateTransitionEnum.IDENTITY_CREDIT_TRANSFER: { - decoded.identityContractNonce = Number(stateTransition.getIdentityContractNonce()) + decoded.nonce = Number(stateTransition.getNonce()) decoded.userFeeIncrease = stateTransition.getUserFeeIncrease() decoded.senderId = stateTransition.getIdentityId().toString() decoded.recipientId = stateTransition.getRecipientId().toString() @@ -284,7 +280,6 @@ const decodeStateTransition = async (client, base64) => { : null decoded.userFeeIncrease = stateTransition.getUserFeeIncrease() - decoded.identityContractNonce = Number(stateTransition.getIdentityContractNonce()) decoded.identityNonce = parseInt(stateTransition.getNonce()) decoded.senderId = stateTransition.getIdentityId().toString() decoded.amount = parseInt(stateTransition.getAmount()) @@ -306,6 +301,7 @@ const decodeStateTransition = async (client, base64) => { decoded.documentTypeName = stateTransition.getContestedDocumentResourceVotePoll().documentTypeName decoded.indexName = stateTransition.getContestedDocumentResourceVotePoll().indexName decoded.choice = stateTransition.getContestedDocumentResourceVotePoll().choice + decoded.userFeeIncrease = stateTransition.getUserFeeIncrease() decoded.raw = stateTransition.toBuffer().toString('hex') decoded.proTxHash = stateTransition.getProTxHash().toString('hex') diff --git a/packages/api/test/integration/identities.spec.js b/packages/api/test/integration/identities.spec.js index ab717dea1..0aefd9cdf 100644 --- a/packages/api/test/integration/identities.spec.js +++ b/packages/api/test/integration/identities.spec.js @@ -212,7 +212,8 @@ describe('Identities routes', () => { const { alias } = await fixtures.identity_alias(knex, { alias: 'test.dash', - identity + identity, + state_transition_hash: transaction.hash } ) @@ -242,6 +243,9 @@ describe('Identities routes', () => { topUpsGasSpent: 0, withdrawalsGasSpent: 0, lastWithdrawalHash: null, + lastWithdrawalTimestamp: null, + totalTopUps: 0, + totalWithdrawals: 0, publicKeys: [], fundingCoreTx: null } @@ -320,7 +324,7 @@ describe('Identities routes', () => { it('should return identity by dpns', async () => { const block = await fixtures.block(knex) const identity = await fixtures.identity(knex, { block_hash: block.hash }) - const { alias } = await fixtures.identity_alias(knex, { alias: 'test-name.1.dash', identity }) + const { alias } = await fixtures.identity_alias(knex, { alias: 'test-name.1.dash', identity, state_transition_hash: identity.transaction.hash }) const { body } = await client.get('/dpns/identity?dpns=test-name.1.dash') .expect(200) @@ -342,7 +346,7 @@ describe('Identities routes', () => { it('should return identity by dpns with any case', async () => { const block = await fixtures.block(knex) const identity = await fixtures.identity(knex, { block_hash: block.hash }) - const { alias } = await fixtures.identity_alias(knex, { alias: 'test-name.2.dash', identity }) + const { alias } = await fixtures.identity_alias(knex, { alias: 'test-name.2.dash', identity, state_transition_hash: identity.transaction.hash }) const { body } = await client.get('/dpns/identity?dpns=TeSt-NaME.2.DAsH') .expect(200) @@ -376,7 +380,7 @@ describe('Identities routes', () => { for (let i = 0; i < 30; i++) { block = await fixtures.block(knex, { height: i + 1 }) identity = await fixtures.identity(knex, { block_hash: block.hash }) - alias = await fixtures.identity_alias(knex, { alias: `#test$${i}`, identity }) + alias = await fixtures.identity_alias(knex, { alias: `#test$${i}`, identity, state_transition_hash: identity.transaction.hash }) identities.push({ identity, block }) aliases.push(alias) } @@ -411,7 +415,10 @@ describe('Identities routes', () => { withdrawalsGasSpent: null, lastWithdrawalHash: null, publicKeys: [], - fundingCoreTx: null + fundingCoreTx: null, + lastWithdrawalTimestamp: null, + totalTopUps: null, + totalWithdrawals: null })) assert.deepEqual(body.resultSet, expectedIdentities) @@ -423,7 +430,7 @@ describe('Identities routes', () => { for (let i = 0; i < 30; i++) { block = await fixtures.block(knex, { height: i + 1 }) identity = await fixtures.identity(knex, { block_hash: block.hash }) - alias = await fixtures.identity_alias(knex, { alias: `#test1$${i}`, identity }) + alias = await fixtures.identity_alias(knex, { alias: `#test1$${i}`, identity, state_transition_hash: identity.transaction.hash }) identities.push({ identity, block }) aliases.push(alias) } @@ -460,7 +467,10 @@ describe('Identities routes', () => { withdrawalsGasSpent: null, lastWithdrawalHash: null, publicKeys: [], - fundingCoreTx: null + fundingCoreTx: null, + lastWithdrawalTimestamp: null, + totalTopUps: null, + totalWithdrawals: null })) assert.deepEqual(body.resultSet, expectedIdentities) @@ -473,7 +483,7 @@ describe('Identities routes', () => { for (let i = 0; i < 30; i++) { block = await fixtures.block(knex, { height: i + 1 }) identity = await fixtures.identity(knex, { block_hash: block.hash }) - alias = await fixtures.identity_alias(knex, { alias: `#test2$${i}`, identity }) + alias = await fixtures.identity_alias(knex, { alias: `#test2$${i}`, identity, state_transition_hash: identity.transaction.hash }) identities.push({ identity, block }) aliases.push(alias) } @@ -510,7 +520,10 @@ describe('Identities routes', () => { withdrawalsGasSpent: null, lastWithdrawalHash: null, publicKeys: [], - fundingCoreTx: null + fundingCoreTx: null, + lastWithdrawalTimestamp: null, + totalTopUps: null, + totalWithdrawals: null })) assert.deepEqual(body.resultSet, expectedIdentities) @@ -523,7 +536,7 @@ describe('Identities routes', () => { for (let i = 0; i < 30; i++) { block = await fixtures.block(knex, { height: i + 1 }) identity = await fixtures.identity(knex, { block_hash: block.hash }) - alias = await fixtures.identity_alias(knex, { alias: `#test3$${i}`, identity }) + alias = await fixtures.identity_alias(knex, { alias: `#test3$${i}`, identity, state_transition_hash: identity.transaction.hash }) identities.push({ identity, block }) aliases.push(alias) } @@ -561,7 +574,10 @@ describe('Identities routes', () => { withdrawalsGasSpent: null, lastWithdrawalHash: null, publicKeys: [], - fundingCoreTx: null + fundingCoreTx: null, + lastWithdrawalTimestamp: null, + totalTopUps: null, + totalWithdrawals: null })) assert.deepEqual(body.resultSet, expectedIdentities) @@ -590,7 +606,7 @@ describe('Identities routes', () => { identity.transactions = transactions identities.push({ identity, block }) - alias = await fixtures.identity_alias(knex, { alias: `#test3$${i}`, identity }) + alias = await fixtures.identity_alias(knex, { alias: `#test3$${i}`, identity, state_transition_hash: identity.transaction.hash }) aliases.push(alias) } @@ -627,7 +643,10 @@ describe('Identities routes', () => { withdrawalsGasSpent: null, lastWithdrawalHash: null, publicKeys: [], - fundingCoreTx: null + fundingCoreTx: null, + lastWithdrawalTimestamp: null, + totalTopUps: null, + totalWithdrawals: null })) assert.deepEqual(body.resultSet, expectedIdentities) @@ -654,7 +673,7 @@ describe('Identities routes', () => { identity.balance = transfer.amount identities.push({ identity, block, transfer }) - alias = await fixtures.identity_alias(knex, { alias: `#test3$${i}`, identity }) + alias = await fixtures.identity_alias(knex, { alias: `#test3$${i}`, identity, state_transition_hash: identity.transaction.hash }) aliases.push(alias) } @@ -705,7 +724,10 @@ describe('Identities routes', () => { withdrawalsGasSpent: null, lastWithdrawalHash: null, publicKeys: [], - fundingCoreTx: null + fundingCoreTx: null, + lastWithdrawalTimestamp: null, + totalTopUps: null, + totalWithdrawals: null })) assert.deepEqual(body.resultSet, expectedIdentities) @@ -1315,7 +1337,8 @@ describe('Identities routes', () => { transaction = await fixtures.transaction(knex, { block_hash: block.hash, owner: identity.identifier, - type: StateTransitionEnum.IDENTITY_TOP_UP + type: StateTransitionEnum.IDENTITY_TOP_UP, + gas_used: 123 }) transfer = await fixtures.transfer(knex, { amount: 1000, @@ -1345,12 +1368,103 @@ describe('Identities routes', () => { timestamp: _transfer.block.timestamp.toISOString(), txHash: _transfer.transfer.state_transition_hash, type: _transfer.transaction.type, - blockHash: _transfer.block.hash + blockHash: _transfer.block.hash, + gasUsed: _transfer.transaction.gas_used + })) + + assert.deepEqual(body.resultSet, expectedTransfers) + }) + + it('should return default set of transfers by identity and type', async () => { + block = await fixtures.block(knex, { height: 1 }) + identity = await fixtures.identity(knex, { block_hash: block.hash }) + transfers = [] + + for (let i = 1; i < 31; i++) { + block = await fixtures.block(knex, { height: i + 1 }) + transaction = await fixtures.transaction(knex, { + block_hash: block.hash, + owner: identity.identifier, + type: i % 2 === 0 ? 5 : 6, + gas_used: 123 + }) + transfer = await fixtures.transfer(knex, { + amount: 1000, + recipient: identity.identifier, + sender: null, + state_transition_hash: transaction.hash + }) + transfers.push({ transfer, transaction, block }) + } + + const { body } = await client.get(`/identity/${identity.identifier}/transfers?type=5`) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.resultSet.length, 10) + assert.equal(body.pagination.total, 15) + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + + const expectedTransfers = transfers + .filter(_transfer => _transfer.transaction.type === 5) + .sort((a, b) => a.block.height - b.block.height) + .slice(0, 10) + .map((_transfer) => ({ + amount: parseInt(_transfer.transfer.amount), + sender: _transfer.transfer.sender, + recipient: _transfer.transfer.recipient, + timestamp: _transfer.block.timestamp.toISOString(), + txHash: _transfer.transfer.state_transition_hash, + type: _transfer.transaction.type, + blockHash: _transfer.block.hash, + gasUsed: _transfer.transaction.gas_used })) assert.deepEqual(body.resultSet, expectedTransfers) }) + it('should return transfer by identity and tx hash', async () => { + block = await fixtures.block(knex, { height: 1 }) + identity = await fixtures.identity(knex, { block_hash: block.hash }) + transfers = [] + + transaction = await fixtures.transaction(knex, { + block_hash: block.hash, + owner: identity.identifier, + type: StateTransitionEnum.IDENTITY_TOP_UP, + gas_used: 123 + }) + transfer = await fixtures.transfer(knex, { + amount: 1000, + recipient: identity.identifier, + sender: null, + state_transition_hash: transaction.hash + }) + + const { body } = await client.get(`/identity/${identity.identifier}/transfers?hash=${transaction.hash}`) + .expect(200) + .expect('Content-Type', 'application/json; charset=utf-8') + + assert.equal(body.resultSet.length, 1) + assert.equal(body.pagination.total, 1) + assert.equal(body.pagination.page, 1) + assert.equal(body.pagination.limit, 10) + + const expectedTransfers = { + amount: transfer.amount, + sender: null, + recipient: identity.identifier, + timestamp: block.timestamp.toISOString(), + txHash: transaction.hash, + type: transaction.type, + blockHash: block.hash, + gasUsed: transaction.gas_used + } + + assert.deepEqual(body.resultSet, [expectedTransfers]) + }) + it('should return default set of transfers by identity desc', async () => { block = await fixtures.block(knex, { height: 1 }) identity = await fixtures.identity(knex, { block_hash: block.hash }) @@ -1361,7 +1475,8 @@ describe('Identities routes', () => { transaction = await fixtures.transaction(knex, { block_hash: block.hash, owner: identity.identifier, - type: StateTransitionEnum.IDENTITY_TOP_UP + type: StateTransitionEnum.IDENTITY_TOP_UP, + gas_used: 12 }) transfer = await fixtures.transfer(knex, { amount: 1000, @@ -1391,7 +1506,8 @@ describe('Identities routes', () => { timestamp: _transfer.block.timestamp.toISOString(), txHash: _transfer.transfer.state_transition_hash, type: _transfer.transaction.type, - blockHash: _transfer.block.hash + blockHash: _transfer.block.hash, + gasUsed: _transfer.transaction.gas_used })) assert.deepEqual(body.resultSet, expectedTransfers) @@ -1407,7 +1523,8 @@ describe('Identities routes', () => { transaction = await fixtures.transaction(knex, { block_hash: block.hash, owner: identity.identifier, - type: StateTransitionEnum.IDENTITY_TOP_UP + type: StateTransitionEnum.IDENTITY_TOP_UP, + gas_used: 22 }) transfer = await fixtures.transfer(knex, { amount: 1000, @@ -1437,7 +1554,8 @@ describe('Identities routes', () => { timestamp: _transfer.block.timestamp.toISOString(), txHash: _transfer.transfer.state_transition_hash, type: _transfer.transaction.type, - blockHash: _transfer.block.hash + blockHash: _transfer.block.hash, + gasUsed: _transfer.transaction.gas_used })) assert.deepEqual(body.resultSet, expectedTransfers) @@ -1453,7 +1571,8 @@ describe('Identities routes', () => { transaction = await fixtures.transaction(knex, { block_hash: block.hash, owner: identity.identifier, - type: StateTransitionEnum.IDENTITY_TOP_UP + type: StateTransitionEnum.IDENTITY_TOP_UP, + gas_used: 33 }) transfer = await fixtures.transfer(knex, { amount: 1000, @@ -1483,7 +1602,8 @@ describe('Identities routes', () => { timestamp: _transfer.block.timestamp.toISOString(), txHash: _transfer.transfer.state_transition_hash, type: _transfer.transaction.type, - blockHash: _transfer.block.hash + blockHash: _transfer.block.hash, + gasUsed: _transfer.transaction.gas_used })) assert.deepEqual(body.resultSet, expectedTransfers) diff --git a/packages/api/test/integration/main.spec.js b/packages/api/test/integration/main.spec.js index 81cbc9796..106a07b2b 100644 --- a/packages/api/test/integration/main.spec.js +++ b/packages/api/test/integration/main.spec.js @@ -43,6 +43,8 @@ describe('Other routes', () => { mock.method(DAPI.prototype, 'getIdentityKeys', async () => null) + mock.method(DAPI.prototype, 'getStatus', async () => null) + mock.method(tenderdashRpc, 'getBlockByHeight', async () => ({ block: { header: { @@ -82,7 +84,8 @@ describe('Other routes', () => { identityAlias = await fixtures.identity_alias(knex, { alias: 'dpns.dash', - identity + identity, + state_transition_hash: identityTransaction.hash }) dataContractTransaction = await fixtures.transaction(knex, { @@ -367,7 +370,10 @@ describe('Other routes', () => { withdrawalsGasSpent: 0, lastWithdrawalHash: null, publicKeys: [], - fundingCoreTx: null + fundingCoreTx: null, + lastWithdrawalTimestamp: null, + totalTopUps: 0, + totalWithdrawals: 0 } assert.deepEqual({ identity: expectedIdentity }, body) @@ -384,8 +390,29 @@ describe('Other routes', () => { timestamp: new Date().toISOString() } } + const mockDapiStatus = { + version: { + software: { + dapi: '1.5.1', + drive: '1.6.2', + tenderdash: '1.4.0' + }, + protocol: { + tenderdash: { + p2p: 10, + block: 14 + }, + drive: { + latest: 6, + current: 6 + } + } + } + } + mock.reset() mock.method(DAPI.prototype, 'getTotalCredits', async () => 0) + mock.method(DAPI.prototype, 'getStatus', async () => mockDapiStatus) mock.method(DAPI.prototype, 'getEpochsInfo', async () => [{ number: 0, firstBlockHeight: 0, @@ -433,12 +460,29 @@ describe('Other routes', () => { } }, tenderdash: { - version: mockTDStatus?.version ?? null, + version: mockDapiStatus.version.software.tenderdash ?? null, block: { height: mockTDStatus?.highestBlock?.height, hash: mockTDStatus?.highestBlock?.hash, timestamp: mockTDStatus?.highestBlock?.timestamp } + }, + versions: { + software: { + dapi: mockDapiStatus.version.software.dapi ?? null, + drive: mockDapiStatus.version.software.drive ?? null, + tenderdash: mockDapiStatus.version.software.tenderdash ?? null + }, + protocol: { + tenderdash: { + p2p: mockDapiStatus.version.protocol.tenderdash.p2p ?? null, + block: mockDapiStatus.version.protocol.tenderdash.block ?? null + }, + drive: { + latest: mockDapiStatus.version.protocol.drive.latest ?? null, + current: mockDapiStatus.version.protocol.drive.current ?? null + } + } } } diff --git a/packages/api/test/integration/transactions.spec.js b/packages/api/test/integration/transactions.spec.js index 9bcf199d7..9db9b375f 100644 --- a/packages/api/test/integration/transactions.spec.js +++ b/packages/api/test/integration/transactions.spec.js @@ -42,7 +42,7 @@ describe('Transaction routes', () => { }) identity = await fixtures.identity(knex, { block_hash: block.hash }) - identityAlias = await fixtures.identity_alias(knex, { alias: 'test.dash', identity }) + identityAlias = await fixtures.identity_alias(knex, { alias: 'test.dash', identity, state_transition_hash: identity.transaction.hash }) transactions = [{ transaction: identity.transaction, block }] diff --git a/packages/api/test/unit/utils.spec.js b/packages/api/test/unit/utils.spec.js index 02a84ef28..b8597e526 100644 --- a/packages/api/test/unit/utils.spec.js +++ b/packages/api/test/unit/utils.spec.js @@ -105,9 +105,8 @@ describe('Utils', () => { coreChainLockedHeight: null, type: 'instantSend', fundingAmount: 34999000, - txid: 'fc89dd4cbe2518da3cd9737043603e81665df58d4989a38b2942eec56bacad1d', vout: 0, - fundingAddress: 'yeMdYXBPum8RmHvrq5SsYE9zNYhMEimbUY', + fundingCoreTx: 'fc89dd4cbe2518da3cd9737043603e81665df58d4989a38b2942eec56bacad1d', instantLock: 'AQEKM9t1ICNzvddKryjM4enKn0Y5amBn3o6DwDoC4uk5SAAAAAAdraxrxe5CKYujiUmN9V1mgT5gQ3Bz2TzaGCW+TN2J/JQP49yOk0uJ6el6ls9CmNo++yPYoX1Sx1lWEZTTAAAAhXiuCBXgzawuboxMAXDiXQpJCCPi417VE4mdcYPgTa0/Hd+RCHLAR6H+MXhqKazlGddI7AdWxxLZ94ZvQu+qIpe7G9XRRjQWeYwroIyc6MqQF5mKpvV0AUMYUNMXjCsq' }, userFeeIncrease: 65, @@ -161,9 +160,8 @@ describe('Utils', () => { coreChainLockedHeight: null, type: 'instantSend', fundingAmount: 999000, - txid: '7734f498c5b59f64f73070e0a5ec4fa113065da00358223cf888c3c27317ea64', vout: 0, - fundingAddress: 'yWxCwVRgqRmePNPJxezgus1T7xSv5q17SU' + fundingCoreTx: '7734f498c5b59f64f73070e0a5ec4fa113065da00358223cf888c3c27317ea64' }, identityId: '4EfA9Jrvv3nnCFdSf7fad59851iiTRZ6Wcu6YVJ4iSeF', amount: 300000000, @@ -277,7 +275,7 @@ describe('Utils', () => { assert.deepEqual(decoded, { type: 7, - identityContractNonce: 3, + nonce: 3, userFeeIncrease: 2, senderId: '4CpFVPyU95ZxNeDnRWfkpjUa9J72i3nZ4YPsTnpdUudu', recipientId: 'GxdRSLivPDeACYU8Z6JSNvtrRPX7QG715JoumnctbwWN', @@ -295,7 +293,6 @@ describe('Utils', () => { type: 6, outputAddress: 'yZF5JqEgS9xT1xSkhhUQACdLLDbqSixL8i', userFeeIncrease: 2, - identityContractNonce: 1, senderId: 'FvqzjDyub72Hk51pcmJvd1JUACuor7vA3aJawiVG7Z17', amount: 1000000, identityNonce: 1, @@ -327,7 +324,8 @@ describe('Utils', () => { indexName: 'parentNameAndLabel', choice: 'TowardsIdentity(4VRAaVi8vq492FznoHKTsQd4odaXa7vDxdghpTSQBVSV)', raw: '0800bc77a5a2cec455c79fb92fb683dbd87a2a92b663c9a46d0c50d11889b4aeb121126fac34e15653f82356cffd3d37c5cd84c1f634d4043340dbae781d93d6b87e000000e668c659af66aee1e72c186dde7b5b7e0a1d712a09c40d5721f622bf53c5315506646f6d61696e12706172656e744e616d65416e644c6162656c02120464617368120874657374303130300033daa5a3e330b61e5a4416ab224f0a45ef4e4cab1357b5f4a86fae9314717a561000411f6c69fa9201b57bb7e7c24b392de9056cce5a66bcf2154d57631419e9c68efa8e4d1ca11e81c35de31dd52321d0fbb25f6ff17f5ff69a9cf47fce54746ee72644', - proTxHash: 'bc77a5a2cec455c79fb92fb683dbd87a2a92b663c9a46d0c50d11889b4aeb121' + proTxHash: 'bc77a5a2cec455c79fb92fb683dbd87a2a92b663c9a46d0c50d11889b4aeb121', + userFeeIncrease: 0 }) }) }) diff --git a/packages/api/test/utils/fixtures.js b/packages/api/test/utils/fixtures.js index c1ebf0da5..e088e5c30 100644 --- a/packages/api/test/utils/fixtures.js +++ b/packages/api/test/utils/fixtures.js @@ -84,14 +84,15 @@ const fixtures = { return { ...row, txHash: state_transition_hash ?? transaction.hash, id: result[0].id, transaction } }, - identity_alias: async (knex, { alias, identity, block_hash } = {}) => { + identity_alias: async (knex, { alias, identity, block_hash, state_transition_hash } = {}) => { if (!identity) { identity = this.identity(knex, { block_hash }) } const row = { identity_identifier: identity.identifier, - alias + alias, + state_transition_hash } await knex('identity_aliases').insert(row).returning('id') diff --git a/packages/frontend/public/images/christmas-hat.svg b/packages/frontend/public/images/christmas-hat.svg new file mode 100644 index 000000000..65e08852c --- /dev/null +++ b/packages/frontend/public/images/christmas-hat.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/frontend/public/images/grain-texture.jpg b/packages/frontend/public/images/grain-texture.jpg deleted file mode 100644 index 939e74492..000000000 Binary files a/packages/frontend/public/images/grain-texture.jpg and /dev/null differ diff --git a/packages/frontend/public/images/grain-texture.png b/packages/frontend/public/images/grain-texture.png new file mode 100644 index 000000000..f87fd6ca4 Binary files /dev/null and b/packages/frontend/public/images/grain-texture.png differ diff --git a/packages/frontend/public/images/icons/block-present.svg b/packages/frontend/public/images/icons/block-present.svg new file mode 100644 index 000000000..a4ae33008 --- /dev/null +++ b/packages/frontend/public/images/icons/block-present.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/frontend/src/app/api/content.md b/packages/frontend/src/app/api/content.md index 7696b75cb..fca956050 100644 --- a/packages/frontend/src/app/api/content.md +++ b/packages/frontend/src/app/api/content.md @@ -73,15 +73,55 @@ HTTP /status hash: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", timestamp: "2024-06-06T21:50:20.949Z" } + "epoch": { + "number": 3640, + "firstBlockHeight": 72534, + "firstCoreBlockHeight": 1160707, + "startTime": 1734457229495, + "feeMultiplier": 1, + "endTime": 1734460829495 + }, + "transactionsCount": 25912, + "totalCredits": 7288089799960610, + "totalCollectedFeesDay": 12733263640, + "transfersCount": 1849, + "dataContractsCount": 630, + "documentsCount": 15384, + "identitiesCount": 712, + "network": "dash-testnet-51", + "api": { + "version": "1.0.7", + "block": { + "height": 72555, + "hash": "EDA1CDF601224CD3ED168D35B4699DE2796F774B526103C64D371EF3AAFD8274", + "timestamp": "2024-12-17T17:57:08.758Z" + } + }, + "tenderdash": { + "version": "1.4.0", + "block": { + "height": 72555, + "hash": "EDA1CDF601224CD3ED168D35B4699DE2796F774B526103C64D371EF3AAFD8274", + "timestamp": "2024-12-17T17:57:08.758Z" + } + }, + "versions": { + "software": { + "dapi": "1.5.1", + "drive": "1.6.2", + "tenderdash": "1.4.0" }, - tenderdash: { - version: "0.14.0-dev.6", - block: { - height: 20154, - hash: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", - timestamp: "2024-06-06T21:53:27.947Z" - } - } + "protocol": { + "tenderdash": { + "p2p": 10, + "block": 14 + }, + "drive": { + "latest": 6, + "current": 6 + } + } + } } ``` --- @@ -707,7 +747,7 @@ Return identity by given identifier GET /identity/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec { - "identifier": "3igSMtXaaS9iRQHbWU1w4hHveKdxixwMpgmhLzjVhFZJ", + "identifier": "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec", "revision": 0, "balance": 49989647300, "timestamp": "2024-10-12T18:51:44.592Z", @@ -729,6 +769,9 @@ GET /identity/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec "topUpsGasSpent": 46350660, "withdrawalsGasSpent": 0, "lastWithdrawalHash": null, + "lastWithdrawalTimestamp": null, + "totalTopUps": 0, + "totalWithdrawals": 0, "publicKeys": [ { "keyId": 0, @@ -1002,7 +1045,7 @@ Response codes: Return all transfers made by the given identity * `limit` cannot be more then 100 ``` -GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?page=1&limit=10&order=asc&type=1 +GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?hash=445E6F081DEE877867816AD3EF492E2C0BD1DDCCDC9C793B23DDDAF8AEA23118&page=1&limit=10&order=asc&type=6 { pagination: { @@ -1019,7 +1062,7 @@ GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?page=1&li txHash: "445E6F081DEE877867816AD3EF492E2C0BD1DDCCDC9C793B23DDDAF8AEA23118", type: 6, blockHash: "73171E0A8DCC10C6DA501E1C70A9C1E0BD6F1F8F834C2A1E787AF19B1F361D5E" - }, ... + } ] } ``` diff --git a/packages/frontend/src/app/block/[hash]/Block.js b/packages/frontend/src/app/block/[hash]/Block.js index 51f8d8c55..362b86825 100644 --- a/packages/frontend/src/app/block/[hash]/Block.js +++ b/packages/frontend/src/app/block/[hash]/Block.js @@ -16,7 +16,6 @@ import { function Block ({ hash }) { const [block, setBlock] = useState({ data: {}, loading: true, error: false }) const [rate, setRate] = useState({ data: {}, loading: true, error: false }) - const txHashes = block.data?.txs || [] const tdTitleWidth = 250 const fetchData = () => { @@ -95,7 +94,7 @@ function Block ({ hash }) { Transactions count - {txHashes?.length} + {block.data?.txs?.length} @@ -111,7 +110,7 @@ function Block ({ hash }) { : } - {txHashes.length + {block.data?.txs?.length ? Transactions - ({ hash }))} - variant={'hashes'} - rate={rate.data} - /> + : null} diff --git a/packages/frontend/src/app/dataContract/[identifier]/DataContract.js b/packages/frontend/src/app/dataContract/[identifier]/DataContract.js index 185dd4e55..d2e7db1de 100644 --- a/packages/frontend/src/app/dataContract/[identifier]/DataContract.js +++ b/packages/frontend/src/app/dataContract/[identifier]/DataContract.js @@ -125,7 +125,7 @@ function DataContract ({ identifier }) {
{dataContract?.data?.name || ''} {dataContract.data?.identifier - ? + ? : }
diff --git a/packages/frontend/src/app/home/Home.js b/packages/frontend/src/app/home/Home.js index 449bd8622..19a094762 100644 --- a/packages/frontend/src/app/home/Home.js +++ b/packages/frontend/src/app/home/Home.js @@ -83,6 +83,7 @@ function Home () { documents={status.data?.documentsCount} identities={status.data?.identitiesCount} loading={status.loading} + event={'christmas'} /> diff --git a/packages/frontend/src/app/identity/[identifier]/Identity.js b/packages/frontend/src/app/identity/[identifier]/Identity.js index bb4e67f1d..7a3f986a4 100644 --- a/packages/frontend/src/app/identity/[identifier]/Identity.js +++ b/packages/frontend/src/app/identity/[identifier]/Identity.js @@ -123,7 +123,7 @@ function Identity ({ identifier }) { {identifier ?
- +
: } diff --git a/packages/frontend/src/app/validator/[hash]/BlocksChart.js b/packages/frontend/src/app/validator/[hash]/BlocksChart.js index 92dec1585..5124c20ef 100644 --- a/packages/frontend/src/app/validator/[hash]/BlocksChart.js +++ b/packages/frontend/src/app/validator/[hash]/BlocksChart.js @@ -1,44 +1,15 @@ -import { useState, useEffect, useRef } from 'react' -import useResizeObserver from '@react-hook/resize-observer' -import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates, getDynamicRange } from '../../../util' -import { LineChart, TimeframeSelector } from './../../../components/charts' import * as Api from '../../../util/Api' -import { ErrorMessageBlock } from '../../../components/Errors' +import { useState, useEffect } from 'react' +import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates } from '../../../util' +import TabsChartBlock from '../../../components/charts/TabsChartBlock' +import { defaultChartConfig } from '../../../components/charts/config' -const chartConfig = { - timespan: { - defaultIndex: 3, - values: [ - { - label: '24 hours', - range: getDynamicRange(24 * 60 * 60 * 1000) - }, - { - label: '3 days', - range: getDynamicRange(3 * 24 * 60 * 60 * 1000) - }, - { - label: '1 week', - range: getDynamicRange(7 * 24 * 60 * 60 * 1000) - }, - { - label: '1 Month', - range: getDynamicRange(30 * 24 * 60 * 60 * 1000) - } - ] - } -} - -export default function BlocksChart ({ hash, isActive }) { +export default function BlocksChart ({ hash, isActive, loading }) { const [blocksHistory, setBlocksHistory] = useState({ data: {}, loading: true, error: false }) - const [timespan, setTimespan] = useState(chartConfig.timespan.values[chartConfig.timespan.defaultIndex]) - const [customRange, setCustomRange] = useState({ start: null, end: null }) - const [menuIsOpen, setMenuIsOpen] = useState(false) - const TimeframeMenuRef = useRef(null) - const [selectorHeight, setSelectorHeight] = useState(0) + const [timespan, setTimespan] = useState(defaultChartConfig.timespan.values[defaultChartConfig.timespan.defaultIndex]) useEffect(() => { - const { start = null, end = null } = timespan.range + const { start = null, end = null } = timespan?.range if (!start || !end) return setBlocksHistory(state => ({ ...state, loading: true })) @@ -46,59 +17,30 @@ export default function BlocksChart ({ hash, isActive }) { Api.getBlocksStatsByValidator(hash, start, end) .then(res => fetchHandlerSuccess(setBlocksHistory, { resultSet: res })) .catch(err => fetchHandlerError(setBlocksHistory, err)) - }, [timespan, customRange]) - - const updateMenuHeight = () => { - if (menuIsOpen && TimeframeMenuRef?.current) { - const element = TimeframeMenuRef.current - const height = element.getBoundingClientRect().height - setSelectorHeight(height) - } else { - setSelectorHeight(0) - } - } - - useEffect(updateMenuHeight, [menuIsOpen, TimeframeMenuRef]) - - useResizeObserver(TimeframeMenuRef, updateMenuHeight) - - if (blocksHistory.error || (!blocksHistory.loading && !blocksHistory.data?.resultSet)) { - return () - } + }, [timespan, hash]) return ( -
- setCustomRange({ start, end })} - /> -
- ({ - x: new Date(item.timestamp), - y: item.data.blocksCount - })) || []} - dataLoading={blocksHistory.loading} - timespan={timespan.range} - xAxis={{ - type: (() => { - if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } - if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 3) return { axis: 'date', tooltip: 'datetime' } - return { axis: 'time' } - })() - }} - yAxis={{ - type: 'number', - abbreviation: 'blocks' - }} - height={'350px'} - /> -
-
+ ({ + x: new Date(item.timestamp), + y: item.data.blocksCount + })) || []} + loading={loading || blocksHistory.loading} + error={!hash || blocksHistory.error} + xAxis={{ + type: (() => { + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 3) return { axis: 'date', tooltip: 'datetime' } + return { axis: 'time' } + })() + }} + yAxis={{ + type: 'number', + abbreviation: 'blocks' + }} + /> ) } diff --git a/packages/frontend/src/app/validator/[hash]/RewardsChart.js b/packages/frontend/src/app/validator/[hash]/RewardsChart.js new file mode 100644 index 000000000..13b57b146 --- /dev/null +++ b/packages/frontend/src/app/validator/[hash]/RewardsChart.js @@ -0,0 +1,46 @@ +import * as Api from '../../../util/Api' +import { useState, useEffect } from 'react' +import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates } from '../../../util' +import TabsChartBlock from '../../../components/charts/TabsChartBlock' +import { defaultChartConfig } from '../../../components/charts/config' + +export default function RewardsChart ({ hash, isActive, loading }) { + const [rewardsHistory, setRewardsHistory] = useState({ data: {}, loading: true, error: false }) + const [timespan, setTimespan] = useState(defaultChartConfig.timespan.values[defaultChartConfig.timespan.defaultIndex]) + + useEffect(() => { + const { start = null, end = null } = timespan?.range + if (!start || !end) return + + setRewardsHistory(state => ({ ...state, loading: true })) + + Api.getRewardsStatsByValidator(hash, start, end) + .then(res => fetchHandlerSuccess(setRewardsHistory, { resultSet: res })) + .catch(err => fetchHandlerError(setRewardsHistory, err)) + }, [timespan, hash]) + + return ( + ({ + x: new Date(item.timestamp), + y: item.data.reward + })) || []} + loading={loading || rewardsHistory.loading} + error={!hash || rewardsHistory.error} + xAxis={{ + type: (() => { + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 3) return { axis: 'date', tooltip: 'datetime' } + return { axis: 'time' } + })() + }} + yAxis={{ + type: 'number', + abbreviation: 'Credits' + }} + /> + ) +} diff --git a/packages/frontend/src/app/validator/[hash]/Validator.js b/packages/frontend/src/app/validator/[hash]/Validator.js index 02a1dd965..dd1015235 100644 --- a/packages/frontend/src/app/validator/[hash]/Validator.js +++ b/packages/frontend/src/app/validator/[hash]/Validator.js @@ -9,6 +9,7 @@ import { ErrorMessageBlock } from '../../../components/Errors' import BlocksList from '../../../components/blocks/BlocksList' import TransactionsList from '../../../components/transactions/TransactionsList' import BlocksChart from './BlocksChart' +import RewardsChart from './RewardsChart' import Link from 'next/link' import { Identifier, DateBlock, Endpoint, IpAddress, InfoLine, Credits } from '../../../components/data' import { ValueContainer, PageDataContainer, InfoContainer } from '../../../components/ui/containers' @@ -412,19 +413,22 @@ function Validator ({ hash }) { setActiveChartTab(index)} index={activeChartTab}> Proposed Blocks - Reward Earned + Reward Earned - Reward Earned + diff --git a/packages/frontend/src/components/blocks/BlocksTotal.js b/packages/frontend/src/components/blocks/BlocksTotal.js index 2d0db4e1b..4f57c62c6 100644 --- a/packages/frontend/src/components/blocks/BlocksTotal.js +++ b/packages/frontend/src/components/blocks/BlocksTotal.js @@ -25,6 +25,7 @@ export default function BlocksTotal () { return ( { + if (menuIsOpen && TimeframeMenuRef?.current) { + const element = TimeframeMenuRef.current + const height = element.getBoundingClientRect().height + setSelectorHeight(height) + } else { + setSelectorHeight(0) + } + } + + useEffect(updateMenuHeight, [menuIsOpen, TimeframeMenuRef]) + + useResizeObserver(TimeframeMenuRef, updateMenuHeight) + + if (error || (!loading && !data)) { + return () + } + + return ( +
+ +
+ +
+
+ ) +} diff --git a/packages/frontend/src/styles/components/TabsChart.scss b/packages/frontend/src/components/charts/TabsChartBlock.scss similarity index 88% rename from packages/frontend/src/styles/components/TabsChart.scss rename to packages/frontend/src/components/charts/TabsChartBlock.scss index 08e9c001e..478f92a23 100644 --- a/packages/frontend/src/styles/components/TabsChart.scss +++ b/packages/frontend/src/components/charts/TabsChartBlock.scss @@ -1,4 +1,5 @@ -.TabsChart { +.TabsChartBlock { + container-type: inline-size; height: 100%; transition: all .2s; @@ -20,8 +21,8 @@ top: -36px; right: 10px; } - - @media screen and (max-width: 555px) { + + @container (max-width: 500px) { &__Button { top: 10px; right: 12px; diff --git a/packages/frontend/src/components/charts/TimeframeSelector.js b/packages/frontend/src/components/charts/TimeframeSelector.js index e423a85ab..3bfa4e4a3 100644 --- a/packages/frontend/src/components/charts/TimeframeSelector.js +++ b/packages/frontend/src/components/charts/TimeframeSelector.js @@ -1,12 +1,12 @@ import { useState, useEffect } from 'react' import TimeframeMenu from './TimeframeMenu' import { Button } from '@chakra-ui/react' -import { CalendarIcon2, CloseIcon } from '../../components/ui/icons' +import { CalendarIcon2, CloseIcon } from '../ui/icons' import './TimeframeSelector.scss' export default function TimeframeSelector ({ config, - isActive, + menuIsActive, changeCallback, openStateCallback, menuRef, @@ -22,12 +22,12 @@ export default function TimeframeSelector ({ } useEffect(() => { - if (!isActive) setMenuIsOpen(false) - }, [isActive]) + if (!menuIsActive) setMenuIsOpen(false) + }, [menuIsActive]) useEffect(() => { if (typeof openStateCallback === 'function') openStateCallback(menuIsOpen) - }, [menuIsOpen]) + }, [menuIsOpen, openStateCallback]) return (
diff --git a/packages/frontend/src/components/charts/config.js b/packages/frontend/src/components/charts/config.js new file mode 100644 index 000000000..18670c7d8 --- /dev/null +++ b/packages/frontend/src/components/charts/config.js @@ -0,0 +1,25 @@ +import { getDynamicRange } from '../../util' + +export const defaultChartConfig = { + timespan: { + defaultIndex: 3, + values: [ + { + label: '24 hours', + range: getDynamicRange(24 * 60 * 60 * 1000) + }, + { + label: '3 days', + range: getDynamicRange(3 * 24 * 60 * 60 * 1000) + }, + { + label: '1 week', + range: getDynamicRange(7 * 24 * 60 * 60 * 1000) + }, + { + label: '1 Month', + range: getDynamicRange(30 * 24 * 60 * 60 * 1000) + } + ] + } +} diff --git a/packages/frontend/src/components/charts/index.js b/packages/frontend/src/components/charts/index.js index b809eb198..5ac19c416 100644 --- a/packages/frontend/src/components/charts/index.js +++ b/packages/frontend/src/components/charts/index.js @@ -109,19 +109,18 @@ const LineGraph = ({ const xAxisFormatCode = typeof xAxis.type === 'string' ? xAxis.type : xAxis.type.axis const [chartWidth, setChartWidth] = useState(0) const svgRef = useRef(null) + const uniqueComponentId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` - const getFormatByCode = (code) => { - if (code === 'number') return d3.format(',.0f') - if (code === 'date') return d3.timeFormat('%B %d') - if (code === 'datetime') return d3.timeFormat('%B %d, %H:%M') - if (code === 'time') return d3.timeFormat('%H:%M') + const tickFormats = { + number: d3.format(',.0f'), + date: d3.timeFormat('%B %d'), + datetime: d3.timeFormat('%B %d, %H:%M'), + time: d3.timeFormat('%H:%M') } - const xTickFormat = (() => { - return getFormatByCode(xAxisFormatCode) - })() - - const y = d3.scaleLinear(d3.extent(data, d => d.y), [height - marginBottom, marginTop]) + const xTickFormat = tickFormats[xAxisFormatCode] + const filteredData = data.filter(d => typeof d.y === 'number' && !isNaN(d.y)) + const y = d3.scaleLinear(d3.extent(filteredData, d => d.y), [height - marginBottom, marginTop]) const [x, setX] = useState(() => { if (xAxisFormatCode === 'number') return d3.scaleLinear(d3.extent(data, d => d.x), [marginLeft, width - marginRight]) @@ -168,6 +167,15 @@ const LineGraph = ({ .y0(y(0)) .y1((d) => y(d.y))) + const valuesFormat = (value) => { + if (typeof value !== 'number' || isNaN(value)) return value + + if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B` + if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M` + if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k` + return value + } + useEffect(() => { d3.select(gx.current) .call((axis) => { @@ -204,6 +212,7 @@ const LineGraph = ({ .call(d3.axisLeft(y) .tickSize(0) .ticks(5) + .tickFormat(valuesFormat) .tickPadding(10) ) @@ -318,14 +327,14 @@ const LineGraph = ({ const infoLines = [] const xFormatCode = typeof xAxis.type.tooltip === 'string' ? xAxis.type.tooltip : xAxis.type.axis - const xFormat = getFormatByCode(xFormatCode) + const xFormat = tickFormats[xFormatCode] infoLines.push({ styles: ['inline', 'tiny'], value: `${xFormat(data[i].x)}: ` }, { styles: ['inline', 'bold'], - value: ` ${data[i].y} ` + value: ` ${new Intl.NumberFormat('fr-FR', { useGrouping: true, grouping: [3], minimumFractionDigits: 0 }).format(data[i].y)} ` }, { styles: ['inline', 'tiny'], value: ` ${yAxis.abbreviation}` @@ -379,7 +388,7 @@ const LineGraph = ({ overflow={'visible'} viewBox={`0 0 ${width} ${height}`} > - + @@ -413,11 +422,11 @@ const LineGraph = ({ - - - + + + - + - + - + @@ -441,7 +450,7 @@ const LineGraph = ({ - +
diff --git a/packages/frontend/src/components/data/Identifier.scss b/packages/frontend/src/components/data/Identifier.scss index 250ce7b7b..6a50eab67 100644 --- a/packages/frontend/src/components/data/Identifier.scss +++ b/packages/frontend/src/components/data/Identifier.scss @@ -35,6 +35,7 @@ } &__Avatar { + @include mixins.AvatarSize; flex-shrink: 0; margin-right: 10px; } diff --git a/packages/frontend/src/components/imageGenerator/ImageGenerator.scss b/packages/frontend/src/components/imageGenerator/ImageGenerator.scss new file mode 100644 index 000000000..16a920106 --- /dev/null +++ b/packages/frontend/src/components/imageGenerator/ImageGenerator.scss @@ -0,0 +1,22 @@ +.ImageGenerator { + position: relative; + + &__Hat { + position: absolute; + top: 0; + left: 50%; + + &--Christmas { + background: url('/images/christmas-hat.svg') center no-repeat; + background-size: contain; + transform: translate(-45%, -35%); + width: 100%; + height: 48%; + } + } + + &__Image { + width: 100%; + height: 100%; + } +} diff --git a/packages/frontend/src/components/imageGenerator/index.js b/packages/frontend/src/components/imageGenerator/index.js index b9eccf45f..20eb4b8d8 100644 --- a/packages/frontend/src/components/imageGenerator/index.js +++ b/packages/frontend/src/components/imageGenerator/index.js @@ -1,12 +1,24 @@ import Image from 'next/image' import { minidenticon } from 'minidenticons' import { useMemo } from 'react' +import './ImageGenerator.scss' -export default function ImageGenerator ({ username, saturation, lightness, ...props }) { +export default function ImageGenerator ({ username, className, hat = null, saturation, lightness, ...props }) { const svgURI = useMemo( () => 'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon(username, saturation, lightness)), [username, saturation, lightness] ) - return ({username) + const ImageElement = {username + + const hatClasses = { + christmas: 'ImageGenerator__Hat--Christmas' + } + + return ( +
+ {hat &&
} + {ImageElement} +
+ ) } diff --git a/packages/frontend/src/components/layout/Background.js b/packages/frontend/src/components/layout/Background.js index 9f20c5a54..0aa2680fc 100644 --- a/packages/frontend/src/components/layout/Background.js +++ b/packages/frontend/src/components/layout/Background.js @@ -1,9 +1,10 @@ 'use client' import { usePathname } from 'next/navigation' +import Snow from './Snow' import './Background.scss' -function Background () { +function Background ({ snow }) { const pathname = usePathname() const showOnRoutes = [ '/', @@ -17,7 +18,10 @@ function Background () { const showDecoration = showOnRoutes.includes(pathname) return ( -
+ <> +
+ {snow && } + ) } diff --git a/packages/frontend/src/components/layout/Background.scss b/packages/frontend/src/components/layout/Background.scss index ebf9bbd67..494205b87 100644 --- a/packages/frontend/src/components/layout/Background.scss +++ b/packages/frontend/src/components/layout/Background.scss @@ -1,8 +1,9 @@ @import '../../styles/variables.scss'; .Background { - background-color: #0F1315; + background-color: #171E21; overflow: hidden; + pointer-events: none; z-index: -1; &, &:after { @@ -15,12 +16,11 @@ &::after { content: ''; - background-image: url('/images/grain-texture.jpg'); + background-image: url('/images/grain-texture.png'); background-position: left; background-repeat: repeat; opacity: .5; z-index: -1; - mix-blend-mode: soft-light; } &--Light { diff --git a/packages/frontend/src/components/layout/RootComponent.js b/packages/frontend/src/components/layout/RootComponent.js index 7348a4773..e0089e396 100644 --- a/packages/frontend/src/components/layout/RootComponent.js +++ b/packages/frontend/src/components/layout/RootComponent.js @@ -16,7 +16,7 @@ export default function RootComponent ({ children }) { return ( - + {children} diff --git a/packages/frontend/src/components/layout/Snow.js b/packages/frontend/src/components/layout/Snow.js new file mode 100644 index 000000000..111e33bc5 --- /dev/null +++ b/packages/frontend/src/components/layout/Snow.js @@ -0,0 +1,22 @@ +'use client' + +import './Snow.scss' + +function Snow () { + return ( +
+
+ {Array.from({ length: 50 }).map((_, i) => ( +
+ ))} +
+
+ {Array.from({ length: 50 }).map((_, i) => ( +
+ ))} +
+
+ ) +} + +export default Snow diff --git a/packages/frontend/src/components/layout/Snow.scss b/packages/frontend/src/components/layout/Snow.scss new file mode 100644 index 000000000..f7e2618c5 --- /dev/null +++ b/packages/frontend/src/components/layout/Snow.scss @@ -0,0 +1,80 @@ +.Snow { + &__BottomContainer, &__TopContainer { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + overflow: hidden; + } + + &__TopContainer { + z-index: 9999; + } + + &__BottomContainer { + z-index: -1; + opacity: .3; + } + + &__Snowflake { + position: absolute; + top: -10%; + font-size: 1em; + color: white; + opacity: 0.8; + animation: fall linear infinite, drift infinite ease-in-out; + + &:nth-child(odd) { + font-size: 1.5em; + opacity: 0.9; + } + + &:nth-child(even) { + font-size: 0.8em; + opacity: 0.7; + } + } + + &__TopContainer & { + &__Snowflake { + &:nth-child(1) { left: 5%; animation-duration: 10s, 4s; animation-delay: 0s, 1s; } + &:nth-child(2) { left: 30%; animation-duration: 12s, 6s; animation-delay: 1s, 0s; } + &:nth-child(3) { left: 50%; animation-duration: 8s, 5s; animation-delay: 2s, 2s; } + &:nth-child(4) { left: 70%; animation-duration: 14s, 7s; animation-delay: 3s, 1s; } + &:nth-child(5) { left: 95%; animation-duration: 10s, 4s; animation-delay: 4s, 3s; } + } + } + + &__BottomContainer & { + &__Snowflake { + &:nth-child(1) { left: 2%; animation-duration: 17s, 7s; animation-delay: 1s, 2.5s; } + &:nth-child(2) { left: 32%; animation-duration: 13s, 4s; animation-delay: 3s, 3s; } + &:nth-child(3) { left: 50%; animation-duration: 15s, 6s; animation-delay: 0s, 0s; } + &:nth-child(4) { left: 72%; animation-duration: 17s, 5s; animation-delay: 1s, 2s; } + &:nth-child(5) { left: 98%; animation-duration: 20s, 7s; animation-delay: 0s, 1s; } + } + } + + @keyframes fall { + 0% { + top: calc(-10px); + } + 100% { + top: calc(100% + 10px); + } + } + + @keyframes drift { + 0% { + margin-left: 0; + } + 50% { + margin-left: 40px; + } + 100% { + margin-left: 0; + } + } +} diff --git a/packages/frontend/src/components/layout/footer/LocalTime.js b/packages/frontend/src/components/layout/footer/LocalTime.js index b3e2308e0..a71fdc511 100644 --- a/packages/frontend/src/components/layout/footer/LocalTime.js +++ b/packages/frontend/src/components/layout/footer/LocalTime.js @@ -1,27 +1,32 @@ 'use client' +import { useEffect, useState } from 'react' import './LocalTime.scss' function LocalTime ({ className }) { - const now = new Date() - const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone + const [time, setTime] = useState(null) + const [date, setDate] = useState(null) + const [timeZone, setTimeZone] = useState(null) - const time = now.toLocaleTimeString('en-US', { - hour: '2-digit', - minute: '2-digit' - }) - - const date = now.toLocaleDateString('en-US', { - weekday: 'short', - day: '2-digit', - month: 'short' - }) + useEffect(() => { + const now = new Date() + setTime(now.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit' + })) + setDate(now.toLocaleDateString('en-US', { + weekday: 'short', + day: '2-digit', + month: 'short' + })) + setTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone) + }, []) return (
- {time} - {date} - ({timeZone}) + {time && {time}} + {date && {date}} + {timeZone && ({timeZone})}
) } diff --git a/packages/frontend/src/components/networkStatus/NetworkStatus.scss b/packages/frontend/src/components/networkStatus/NetworkStatus.scss index dd5fd05f4..4c792d9e7 100644 --- a/packages/frontend/src/components/networkStatus/NetworkStatus.scss +++ b/packages/frontend/src/components/networkStatus/NetworkStatus.scss @@ -41,7 +41,6 @@ font-weight: bold; font-family: $font-heading; letter-spacing: 0.4px; - text-transform: uppercase; &, span { display: flex; diff --git a/packages/frontend/src/components/networkStatus/index.js b/packages/frontend/src/components/networkStatus/index.js index 5762cadc6..89f447c45 100644 --- a/packages/frontend/src/components/networkStatus/index.js +++ b/packages/frontend/src/components/networkStatus/index.js @@ -60,12 +60,12 @@ function NetworkStatus ({ className }) {
Platform version:
-
1.7.0
+
{status?.data?.versions?.software?.drive !== undefined ? `v${status?.data?.versions?.software?.drive}` : '-'}
Tenderdash version:
-
1.4.0
+
{status?.data?.versions?.software?.tenderdash ? `v${status?.data?.versions?.software?.tenderdash}` : '-'}
diff --git a/packages/frontend/src/components/total/TotalCards.js b/packages/frontend/src/components/total/TotalCards.js index cfda03461..dcbb04aac 100644 --- a/packages/frontend/src/components/total/TotalCards.js +++ b/packages/frontend/src/components/total/TotalCards.js @@ -3,7 +3,7 @@ import ValueBlock from './ValueBlock' import { Box } from '@chakra-ui/react' import './TotalCards.scss' -export default function TotalCards ({ cards, loading = false }) { +export default function TotalCards ({ cards, event = null, loading = false }) { return (
{cards.map((card, i) => ( @@ -14,6 +14,7 @@ export default function TotalCards ({ cards, loading = false }) { value={card.value} icon={card.icon} formats={card.format} + event={event} /> : } diff --git a/packages/frontend/src/components/total/TotalInfo.js b/packages/frontend/src/components/total/TotalInfo.js index 783c4f171..902b7c523 100644 --- a/packages/frontend/src/components/total/TotalInfo.js +++ b/packages/frontend/src/components/total/TotalInfo.js @@ -3,10 +3,22 @@ import './TotalInfoItem.scss' import { Container, Flex } from '@chakra-ui/react' import Link from 'next/link' -export default function TotalInfo ({ blocks, transactions, dataContracts, documents, identities, loading }) { +export default function TotalInfo ({ + blocks, + transactions, + dataContracts, + documents, + identities, + loading, + event = null +}) { + const eventClasses = { + christmas: 'TotalInfo--Christmas' + } + return ( { if (format === 'elipsed') return 'ValueBlock__Value--Elipsed' @@ -9,7 +12,7 @@ export default function ValueBlock ({ title, value, icon, formats = [], classNam }).join(' ') return ( -
+
{icon &&
} diff --git a/packages/frontend/src/components/total/ValueBlock.scss b/packages/frontend/src/components/total/ValueBlock.scss index 4a07a5426..85934980e 100644 --- a/packages/frontend/src/components/total/ValueBlock.scss +++ b/packages/frontend/src/components/total/ValueBlock.scss @@ -2,101 +2,113 @@ @import '../../styles/variables.scss'; .ValueBlock { - position: relative; - display: flex; - align-items: center; - padding-left: 44px; + position: relative; + display: flex; + align-items: center; + padding-left: 44px; + &::before { + content: ''; + display: block; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 28px; + height: 28px; + } + + &--Blocks { &::before { - content: ''; - display: block; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 28px; - height: 28px; + background: url('/images/icons/block.svg') no-repeat center; + background-size: contain; } + } - &--Blocks { - &::before { - background: url("/images/icons/transactions.svg") no-repeat center; - background-size: contain; - } + &--Transactions { + &::before { + background: url('/images/icons/transactions.svg') no-repeat center; + background-size: contain; } + } - &--Transactions { - &::before { - background: url('/images/icons/block.svg') no-repeat center; - background-size: contain; - } + &--Timer { + &::before { + background: url('/images/icons/timer.svg') no-repeat center; + background-size: contain; } + } - &--Timer { - &::before { - background: url('/images/icons/timer.svg') no-repeat center; - background-size: contain; - } + &--Epoch { + &::before { + background: url('/images/icons/timer.svg') no-repeat center; + background-size: contain; } + } - &--Epoch { - &::before { - background: url('/images/icons/timer.svg') no-repeat center; - background-size: contain; - } + &--Sandglass { + &::before { + background: url('/images/icons/sandglass.svg') no-repeat center; + background-size: contain; } + } - &--Sandglass { - &::before { - background: url('/images/icons/sandglass.svg') no-repeat center; - background-size: contain; - } + &--Nodes { + &::before { + background: url('/images/icons/nodes.svg') no-repeat center; + background-size: contain; } + } - &--Nodes { - &::before { - background: url('/images/icons/nodes.svg') no-repeat center; - background-size: contain; - } + &--Coins { + &::before { + background: url('/images/icons/coins.svg') no-repeat center; + background-size: contain; } + } - &--Coins { - &::before { - background: url('/images/icons/coins.svg') no-repeat center; - background-size: contain; - } + &--StarCheck { + &::before { + background: url('/images/icons/star-check.svg') no-repeat center; + background-size: contain; } + } - &--StarCheck { - &::before { - background: url('/images/icons/star-check.svg') no-repeat center; - background-size: contain; - } + &--Christmas.ValueBlock { + &--Blocks { + &::before { + background: url('/images/icons/block-present.svg') no-repeat center; + background-size: contain; + width: 30px; + height: 30px; + margin: -1px; + } } + } + + &__Title { + color: var(--chakra-colors-brand-deep); + font-size: 1rem; + margin-right: 10px; + white-space: nowrap; + } - &__Title { - color: var(--chakra-colors-brand-deep); - font-size: 1rem; - margin-right: 10px; - white-space: nowrap; + &__Value { + font-size: 1.125rem; + + &--Loading { + @include mixins.LoadingLine; + + opacity: .5; + border-radius: 5px; + animation-delay: 100ms; } - - &__Value { - font-size: 1.125rem; - - &--Loading { - @include mixins.LoadingLine; - - opacity: .5; - border-radius: 5px; - animation-delay: 100ms; - } - - &--Elipsed { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } + + &--Elipsed { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + } } \ No newline at end of file diff --git a/packages/frontend/src/components/transactions/TransactionsList.js b/packages/frontend/src/components/transactions/TransactionsList.js index f265b94ef..d886c4828 100644 --- a/packages/frontend/src/components/transactions/TransactionsList.js +++ b/packages/frontend/src/components/transactions/TransactionsList.js @@ -4,7 +4,7 @@ import { EmptyListMessage } from '../ui/lists' import { Grid, GridItem } from '@chakra-ui/react' import './TransactionsList.scss' -export default function TransactionsList ({ transactions = [], showMoreLink, variant = 'full', headerStyles = 'default', rate }) { +export default function TransactionsList ({ transactions = [], showMoreLink, headerStyles = 'default', rate }) { const headerExtraClass = { default: '', light: 'BlocksList__ColumnTitles--Light' @@ -12,25 +12,23 @@ export default function TransactionsList ({ transactions = [], showMoreLink, var return (
- {variant === 'full' && - - - Time - - - Hash - - - Gas used - - - Owner - - - Type - - - } + + + Time + + + Hash + + + Gas used + + + Owner + + + Type + + {transactions?.length > 0 ? transactions.map((transaction, key) => ( @@ -38,7 +36,6 @@ export default function TransactionsList ({ transactions = [], showMoreLink, var key={key} transaction={transaction} rate={rate} - variant={variant} /> )) : There are no transactions yet. diff --git a/packages/frontend/src/components/transactions/TransactionsListItem.js b/packages/frontend/src/components/transactions/TransactionsListItem.js index 25ca4fdf9..41368a783 100644 --- a/packages/frontend/src/components/transactions/TransactionsListItem.js +++ b/packages/frontend/src/components/transactions/TransactionsListItem.js @@ -11,26 +11,12 @@ import './TransactionsListItem.scss' import ImageGenerator from '../imageGenerator' import { useRouter } from 'next/navigation' -function TransactionsListItem ({ transaction, rate, variant = 'full' }) { +function TransactionsListItem ({ transaction, rate }) { const activeAlias = transaction?.owner?.aliases?.find(alias => alias.status === 'ok') const router = useRouter() - if (variant === 'hashes') { - return ( - - {transaction?.hash} - - ) - } - return ( - + {transaction?.timestamp diff --git a/packages/frontend/src/components/transactions/TransactionsListItem.scss b/packages/frontend/src/components/transactions/TransactionsListItem.scss index 01b39b86f..7a84ffc7c 100644 --- a/packages/frontend/src/components/transactions/TransactionsListItem.scss +++ b/packages/frontend/src/components/transactions/TransactionsListItem.scss @@ -79,6 +79,7 @@ .Identifier, .TransactionsListItem__AliasContainer, .Identifier__Avatar, + img, a { display: block !important; } diff --git a/packages/frontend/src/components/validators/ValidatorCard.js b/packages/frontend/src/components/validators/ValidatorCard.js index c5600e227..73f3f1db4 100644 --- a/packages/frontend/src/components/validators/ValidatorCard.js +++ b/packages/frontend/src/components/validators/ValidatorCard.js @@ -15,7 +15,9 @@ export default function ValidatorCard ({ validator, rate, className }) { lightness={50} saturation={50} width={88} - height={88}/> + height={88} + hat={'christmas'} + /> : 'n/a' }
diff --git a/packages/frontend/src/styles/components/Table.scss b/packages/frontend/src/styles/components/Table.scss index 46454e2b0..d36a87be9 100644 --- a/packages/frontend/src/styles/components/Table.scss +++ b/packages/frontend/src/styles/components/Table.scss @@ -47,6 +47,8 @@ &__Avatar { margin-left: 8px; + width: 32px; + height: 32px; } &__SortDirection { diff --git a/packages/frontend/src/styles/mixins.scss b/packages/frontend/src/styles/mixins.scss index 34cfc3e66..4fc2fa975 100644 --- a/packages/frontend/src/styles/mixins.scss +++ b/packages/frontend/src/styles/mixins.scss @@ -1,5 +1,10 @@ @import './variables.scss'; +@mixin AvatarSize() { + height: 24px; + width: 24px; +} + @mixin DefListItem($clickable: true) { display: flex; width: 100%; @@ -41,9 +46,9 @@ } &__Avatar { + @include AvatarSize; + flex-shrink: 0; margin-right: 12px; - height: 28px; - width: 28px; } &__NotActiveText { diff --git a/packages/frontend/src/styles/theme.scss b/packages/frontend/src/styles/theme.scss index effe112ea..85a986913 100644 --- a/packages/frontend/src/styles/theme.scss +++ b/packages/frontend/src/styles/theme.scss @@ -1,6 +1,5 @@ @import 'components/Table.scss'; @import 'components/InfoBlock.scss'; -@import 'components/TabsChart.scss'; @import './variables.scss'; * { diff --git a/packages/frontend/src/util/Api.js b/packages/frontend/src/util/Api.js index e76ce3577..9adb02855 100644 --- a/packages/frontend/src/util/Api.js +++ b/packages/frontend/src/util/Api.js @@ -119,6 +119,10 @@ const getBlocksStatsByValidator = (proTxHash, start, end) => { return call(`validator/${proTxHash}/stats?start=${start}&end=${end}`, 'GET') } +const getRewardsStatsByValidator = (proTxHash, start, end) => { + return call(`validator/${proTxHash}/rewards/stats?start=${start}&end=${end}`, 'GET') +} + const getStatus = () => { return call('status', 'GET') } @@ -159,6 +163,7 @@ export { getValidatorByProTxHash, getBlocksByValidator, getBlocksStatsByValidator, + getRewardsStatsByValidator, getEpoch, getRate } diff --git a/packages/indexer/Cargo.lock b/packages/indexer/Cargo.lock index 1ab801a20..adb1ea65b 100644 --- a/packages/indexer/Cargo.lock +++ b/packages/indexer/Cargo.lock @@ -594,7 +594,7 @@ dependencies = [ [[package]] name = "dashpay-contract" -version = "1.5.1" +version = "1.7.0" dependencies = [ "platform-value", "platform-version", @@ -604,7 +604,7 @@ dependencies = [ [[package]] name = "data-contracts" -version = "1.5.1" +version = "1.7.0" dependencies = [ "dashpay-contract", "dpns-contract", @@ -614,6 +614,7 @@ dependencies = [ "platform-version", "serde_json", "thiserror", + "wallet-utils-contract", "withdrawals-contract", ] @@ -711,7 +712,7 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "dpns-contract" -version = "1.5.1" +version = "1.7.0" dependencies = [ "platform-value", "platform-version", @@ -721,7 +722,7 @@ dependencies = [ [[package]] name = "dpp" -version = "1.5.1" +version = "1.7.0" dependencies = [ "anyhow", "async-trait", @@ -735,7 +736,7 @@ dependencies = [ "env_logger 0.11.5", "getrandom", "hex", - "indexmap 2.6.0", + "indexmap 2.7.0", "integer-encoding", "itertools", "json-schema-compatibility-validator", @@ -867,7 +868,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "feature-flags-contract" -version = "1.5.1" +version = "1.7.0" dependencies = [ "platform-value", "platform-version", @@ -1063,7 +1064,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -1144,11 +1145,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1305,9 +1306,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.0", @@ -1380,7 +1381,7 @@ dependencies = [ [[package]] name = "json-schema-compatibility-validator" -version = "1.5.1" +version = "1.7.0" dependencies = [ "json-patch", "once_cell", @@ -1419,12 +1420,12 @@ checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -1451,7 +1452,7 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "masternode-reward-shares-contract" -version = "1.5.1" +version = "1.7.0" dependencies = [ "platform-value", "platform-version", @@ -1750,7 +1751,7 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "platform-serialization" -version = "1.5.1" +version = "1.7.0" dependencies = [ "bincode", "platform-version", @@ -1758,7 +1759,7 @@ dependencies = [ [[package]] name = "platform-serialization-derive" -version = "1.5.1" +version = "1.7.0" dependencies = [ "proc-macro2", "quote", @@ -1768,13 +1769,13 @@ dependencies = [ [[package]] name = "platform-value" -version = "1.5.1" +version = "1.7.0" dependencies = [ "base64 0.22.1", "bincode", "bs58", "hex", - "indexmap 2.6.0", + "indexmap 2.7.0", "lazy_static", "platform-serialization", "platform-version", @@ -1787,7 +1788,7 @@ dependencies = [ [[package]] name = "platform-version" -version = "1.5.1" +version = "1.7.0" dependencies = [ "bincode", "grovedb-version", @@ -1798,7 +1799,7 @@ dependencies = [ [[package]] name = "platform-versioning" -version = "1.5.1" +version = "1.7.0" dependencies = [ "proc-macro2", "quote", @@ -2321,9 +2322,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" @@ -2351,7 +2352,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -2824,7 +2825,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "toml_datetime", "winnow 0.5.40", ] @@ -2835,7 +2836,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -2986,6 +2987,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wallet-utils-contract" +version = "1.7.0" +dependencies = [ + "platform-value", + "platform-version", + "serde_json", + "thiserror", +] + [[package]] name = "want" version = "0.3.1" @@ -3303,7 +3314,7 @@ dependencies = [ [[package]] name = "withdrawals-contract" -version = "1.5.1" +version = "1.7.0" dependencies = [ "num_enum 0.5.11", "platform-value", diff --git a/packages/indexer/Dockerfile b/packages/indexer/Dockerfile index 9c2e761ee..f7c39a6e0 100644 --- a/packages/indexer/Dockerfile +++ b/packages/indexer/Dockerfile @@ -2,7 +2,7 @@ FROM rust:1.82-alpine3.19 as build RUN apk add --no-cache git cmake clang openssl openssl-dev openssl-libs-static build-base WORKDIR / -RUN git clone --depth 1 --branch v1.5.1 https://github.com/dashevo/platform +RUN git clone --depth 1 --branch v1.7.0 https://github.com/dashevo/platform WORKDIR /app COPY Cargo.lock /app COPY Cargo.toml /app diff --git a/packages/indexer/migrations/V43__add_identity_alias_st_hash.sql b/packages/indexer/migrations/V43__add_identity_alias_st_hash.sql new file mode 100644 index 000000000..608ca4e6a --- /dev/null +++ b/packages/indexer/migrations/V43__add_identity_alias_st_hash.sql @@ -0,0 +1,2 @@ +ALTER TABLE identity_aliases +ADD COLUMN "state_transition_hash" CHAR(64) NOT NULL; diff --git a/packages/indexer/src/entities/data_contract.rs b/packages/indexer/src/entities/data_contract.rs index 69ffe6f66..b125ceacd 100644 --- a/packages/indexer/src/entities/data_contract.rs +++ b/packages/indexer/src/entities/data_contract.rs @@ -92,7 +92,8 @@ impl From for DataContract { SystemDataContract::MasternodeRewards => "MasternodeRewards", SystemDataContract::FeatureFlags => "FeatureFlags", SystemDataContract::DPNS => "DPNS", - SystemDataContract::Dashpay => "Dashpay" + SystemDataContract::Dashpay => "Dashpay", + SystemDataContract::WalletUtils => "WalletUtils" }; let identifier = data_contract.id(); let source = data_contract.source(platform_version).unwrap(); diff --git a/packages/indexer/src/entities/identity.rs b/packages/indexer/src/entities/identity.rs index b69a6cb91..c09e9dc63 100644 --- a/packages/indexer/src/entities/identity.rs +++ b/packages/indexer/src/entities/identity.rs @@ -2,8 +2,8 @@ use std::env; use base64::Engine; use base64::engine::general_purpose; use dashcore_rpc::{Auth, Client, RpcApi}; +use dashcore_rpc::dashcore::Txid; use data_contracts::SystemDataContract; -use dpp::dashcore::{Transaction, Txid}; use dpp::identifier::Identifier; use dpp::identity::state_transition::AssetLockProved; use dpp::platform_value::string_encoding::Encoding::{Base58, Base64}; @@ -30,7 +30,7 @@ impl From for Identity { let asset_lock = state_transition.asset_lock_proof().clone(); let asset_lock_output_index = asset_lock.output_index(); - let transaction: Transaction = match asset_lock { + let transaction = match asset_lock { AssetLockProof::Instant(instant_lock) => instant_lock.transaction, AssetLockProof::Chain(chain_lock) => { let tx_hash = chain_lock.out_point.txid.to_string(); diff --git a/packages/indexer/src/processor/psql/dao/mod.rs b/packages/indexer/src/processor/psql/dao/mod.rs index 6366ad66f..792b05a4c 100644 --- a/packages/indexer/src/processor/psql/dao/mod.rs +++ b/packages/indexer/src/processor/psql/dao/mod.rs @@ -179,19 +179,20 @@ impl PostgresDAO { Ok(()) } - pub async fn create_identity_alias(&self, identity: Identity, alias: String) -> Result<(), PoolError> { + pub async fn create_identity_alias(&self, identity: Identity, alias: String, st_hash: String) -> Result<(), PoolError> { let client = self.connection_pool.get().await.unwrap(); - let query = "INSERT INTO identity_aliases(identity_identifier,alias) VALUES ($1, $2);"; + let query = "INSERT INTO identity_aliases(identity_identifier,alias,state_transition_hash) VALUES ($1, $2, $3);"; let stmt = client.prepare_cached(query).await.unwrap(); client.query(&stmt, &[ &identity.identifier.to_string(Base58), &alias, + &st_hash, ]).await.unwrap(); - println!("Created Identity Alias {} -> {}", identity.identifier.to_string(Base58), alias); + println!("Created Identity Alias {} -> {} ({})", identity.identifier.to_string(Base58), alias, &st_hash); Ok(()) } diff --git a/packages/indexer/src/processor/psql/mod.rs b/packages/indexer/src/processor/psql/mod.rs index c828b5844..940609e72 100644 --- a/packages/indexer/src/processor/psql/mod.rs +++ b/packages/indexer/src/processor/psql/mod.rs @@ -145,7 +145,7 @@ impl PSQLProcessor { let identity = self.dao.get_identity_by_identifier(identity_identifier.clone()).await.unwrap().expect(&format!("Could not find identity with identifier {}", identity_identifier)); let alias = format!("{}.{}", label, normalized_parent_domain_name); - self.dao.create_identity_alias(identity, alias).await.unwrap(); + self.dao.create_identity_alias(identity, alias, st_hash.clone()).await.unwrap(); } if document_type == "dataContracts" && document_transition.data_contract_id() == self.platform_explorer_identifier { @@ -431,6 +431,7 @@ impl PSQLProcessor { self.dao.create_document(dash_tld_document, None).await.unwrap(); } SystemDataContract::Dashpay => {} + SystemDataContract::WalletUtils => {} } }