+
Locked: {balanceLocked} {symbol()}
) : (
diff --git a/src/components/composed/LockedBalanceList/LockedBalanceList.css b/src/components/composed/LockedBalanceList/LockedBalanceList.css
new file mode 100644
index 00000000..4c4f6d26
--- /dev/null
+++ b/src/components/composed/LockedBalanceList/LockedBalanceList.css
@@ -0,0 +1,26 @@
+.locked-table {
+ width: 100%;
+}
+
+.locked-table-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ height: 465px;
+ overflow: auto;
+}
+
+.locked-title {
+ text-align: left;
+ padding: 10px;
+ background: rgb(var(--color-green));
+ width: 50%;
+ color: rgb(var(--color-white));
+}
+
+.locked-loading-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 465px;
+}
diff --git a/src/components/composed/LockedBalanceList/LockedBalanceList.js b/src/components/composed/LockedBalanceList/LockedBalanceList.js
new file mode 100644
index 00000000..c00b11ec
--- /dev/null
+++ b/src/components/composed/LockedBalanceList/LockedBalanceList.js
@@ -0,0 +1,122 @@
+import React, { useContext, useState, useEffect, useCallback } from 'react'
+import { NetworkContext } from '@Contexts'
+
+import { Loading } from '@ComposedComponents'
+import { Mintlayer } from '@APIs'
+import { addDays } from 'date-fns'
+import LockedBalanceListItem from './LockedBalanceListItem'
+import './LockedBalanceList.css'
+
+const LockedBalanceList = () => {
+ const { lockedUtxos, transactions, fetchingUtxos } =
+ useContext(NetworkContext)
+ const [loading, setLoading] = useState(false)
+ const [updatedUtxosList, setUpdatedUtxosList] = useState([])
+
+ const getBlockData = async (blockId) => {
+ try {
+ const blockData = await Mintlayer.getBlockDataByHash(blockId)
+ const parsedData = JSON.parse(blockData)
+ if (parsedData && parsedData.height) {
+ return parsedData.height
+ }
+ } catch (error) {
+ console.error('Failed to fetch block data:', error)
+ }
+ }
+
+ const getUpdatedUtxosWithDate = useCallback(
+ async (utxos) => {
+ setLoading(true)
+ const updatedUtxos = await Promise.all(
+ utxos.map(async (utxo) => {
+ if (utxo.utxo.lock.type === 'ForBlockCount') {
+ const initialTransaction = transactions.find(
+ (tx) => tx.txid === utxo.outpoint.source_id,
+ )
+ if (initialTransaction && !utxo.utxo.lock.content.unlockBlock) {
+ // Calculating the unlock date
+ const calculatedUnlockTimestamp =
+ addDays(
+ new Date(initialTransaction.date * 1000),
+ 10,
+ ).getTime() / 1000
+
+ const blockData = await getBlockData(initialTransaction.blockId)
+
+ utxo.utxo.lock.content = {
+ lockedFor: utxo.utxo.lock.content,
+ timestamp: calculatedUnlockTimestamp,
+ unlockBlock: blockData + utxo.utxo.lock.content,
+ }
+ }
+ }
+ return utxo
+ }),
+ )
+ setLoading(false)
+ return updatedUtxos
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [lockedUtxos, transactions],
+ )
+
+ useEffect(() => {
+ const fetchUpdatedUtxos = async () => {
+ const updatedUtxos = await getUpdatedUtxosWithDate(lockedUtxos)
+ const sortedUtxos = updatedUtxos.sort(
+ (a, b) => a.utxo.lock.content.timestamp - b.utxo.lock.content.timestamp,
+ )
+ setUpdatedUtxosList(sortedUtxos)
+ }
+
+ fetchUpdatedUtxos()
+ }, [lockedUtxos, transactions, getUpdatedUtxosWithDate])
+
+ return (
+ <>
+
+ {(fetchingUtxos || loading) ? (
+
+
+
+ ) : (
+
+
+
+
+ Date
+ |
+
+ Amount
+ |
+
+
+
+ {lockedUtxos &&
+ updatedUtxosList.map((utxo, index) => (
+
+ ))}
+
+
+ )}
+
+ >
+ )
+}
+
+export default LockedBalanceList
diff --git a/src/components/composed/LockedBalanceList/LockedBalanceListItem.css b/src/components/composed/LockedBalanceList/LockedBalanceListItem.css
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/composed/LockedBalanceList/LockedBalanceListItem.js b/src/components/composed/LockedBalanceList/LockedBalanceListItem.js
new file mode 100644
index 00000000..0d7dbe3c
--- /dev/null
+++ b/src/components/composed/LockedBalanceList/LockedBalanceListItem.js
@@ -0,0 +1,34 @@
+import { useMemo } from 'react'
+import { format } from 'date-fns'
+
+import './LockedBalanceListItem.css'
+
+const LockedBalanceListItem = ({ index, utxo }) => {
+
+ const displayDate = useMemo(() => {
+ if (utxo.utxo.lock.type === 'ForBlockCount') {
+ return `~ ${format(
+ new Date(utxo.utxo.lock.content.timestamp * 1000),
+ 'dd/MM/yyyy HH:mm',
+ )} (Block height: ${utxo.utxo.lock.content.unlockBlock})`
+ } else if (utxo.utxo.lock.type !== 'ForBlockCount') {
+ return format(
+ new Date(utxo.utxo.lock.content.timestamp * 1000),
+ 'dd/MM/yyyy HH:mm',
+ )
+ }
+ }, [utxo])
+
+ return (
+
+ {displayDate} |
+ {utxo.utxo.value.amount.decimal} ML |
+
+ )
+}
+
+export default LockedBalanceListItem
diff --git a/src/components/composed/index.js b/src/components/composed/index.js
index 2e2144bc..d2224828 100644
--- a/src/components/composed/index.js
+++ b/src/components/composed/index.js
@@ -20,6 +20,7 @@ import CurrentStaking from './CurrentStaking/CurrentStaking'
import HelpTooltip from './HelpTooltip/HelpTooltip'
import RestoreSeedField from './RestoreSeedField/RestoreSeedField'
import UpdateButton from './UpdateButton/UpdateButton'
+import LockedBalanceList from './LockedBalanceList/LockedBalanceList'
export {
Balance,
@@ -44,4 +45,5 @@ export {
HelpTooltip,
RestoreSeedField,
UpdateButton,
+ LockedBalanceList,
}
diff --git a/src/contexts/NetworkProvider/NetworkProvider.js b/src/contexts/NetworkProvider/NetworkProvider.js
index 8e60cafa..8a441cf9 100644
--- a/src/contexts/NetworkProvider/NetworkProvider.js
+++ b/src/contexts/NetworkProvider/NetworkProvider.js
@@ -1,9 +1,5 @@
import React, { createContext, useContext, useEffect, useState } from 'react'
-import {
- getAddressData,
- getChainTip,
- getTransactionData,
-} from '../../services/API/Mintlayer/Mintlayer'
+
import { AccountContext, SettingsContext } from '@Contexts'
import { AppInfo } from '@Constants'
import { ML_ATOMS_PER_COIN } from '../../utils/Constants/AppInfo/AppInfo'
@@ -33,6 +29,7 @@ const NetworkProvider = ({ value: propValue, children }) => {
const [lockedBalance, setLockedBalance] = useState(0)
const [unusedAddresses, setUnusedAddresses] = useState({})
const [utxos, setUtxos] = useState([])
+ const [lockedUtxos, setLockedUtxos] = useState([])
const [transactions, setTransactions] = useState([])
const [feerate, setFeerate] = useState(0)
@@ -84,12 +81,12 @@ const NetworkProvider = ({ value: propValue, children }) => {
const addresses_data_receive = await Promise.all(
currentMlAddresses.mlReceivingAddresses.map((address) =>
- getAddressData(address),
+ Mintlayer.getAddressData(address),
),
)
const addresses_data_change = await Promise.all(
currentMlAddresses.mlChangeAddresses.map((address) =>
- getAddressData(address),
+ Mintlayer.getAddressData(address),
),
)
const addresses_data = [...addresses_data_receive, ...addresses_data_change]
@@ -143,7 +140,9 @@ const NetworkProvider = ({ value: propValue, children }) => {
setCurrentAccountId(accountID)
// fetch transactions data
- const transactions = transaction_ids.map((txid) => getTransactionData(txid))
+ const transactions = transaction_ids.map((txid) =>
+ Mintlayer.getTransactionData(txid),
+ )
const transactions_data = await Promise.all(transactions)
const parsedTransactions = ML.getParsedTransactions(
@@ -160,11 +159,19 @@ const NetworkProvider = ({ value: propValue, children }) => {
const unconfirmedTransactions =
LocalStorageService.getItem(unconfirmedTransactionString) || []
- const utxos = await Mintlayer.getWalletUtxos(addressList)
- const parsedUtxos = utxos
+ const fetchedUtxos = await Mintlayer.getWalletUtxos(addressList)
+ const fetchedSpendableUtxos =
+ await Mintlayer.getWalletSpendableUtxos(addressList)
+
+ const parsedUtxos = fetchedUtxos
+ .map((utxo) => JSON.parse(utxo))
+ .filter((utxo) => utxo.length > 0)
+
+ const parsedSpendableUtxos = fetchedSpendableUtxos
.map((utxo) => JSON.parse(utxo))
.filter((utxo) => utxo.length > 0)
- const available = parsedUtxos
+
+ const available = parsedSpendableUtxos
.flatMap((utxo) => [...utxo])
.filter((item) => item.utxo.value)
.filter((item) => {
@@ -189,7 +196,12 @@ const NetworkProvider = ({ value: propValue, children }) => {
setCurrentNetworkType(networkType)
const availableUtxos = available.map((item) => item)
+ const lockedUtxos = parsedUtxos
+ .flat()
+ .filter((obj) => obj.utxo.type === 'LockThenTransfer')
+
setUtxos(availableUtxos)
+ setLockedUtxos(lockedUtxos)
setFetchingUtxos(false)
@@ -305,7 +317,7 @@ const NetworkProvider = ({ value: propValue, children }) => {
useEffect(() => {
const getData = async () => {
- const result = await getChainTip()
+ const result = await Mintlayer.getChainTip()
const { block_height } = JSON.parse(result)
setOnlineHeight(block_height)
}
@@ -321,6 +333,7 @@ const NetworkProvider = ({ value: propValue, children }) => {
tokenBalances,
// addresses,
utxos,
+ lockedUtxos,
transactions,
currentHeight,
onlineHeight,
diff --git a/src/index.js b/src/index.js
index 4e14d804..f2d0ab40 100644
--- a/src/index.js
+++ b/src/index.js
@@ -26,6 +26,7 @@ import {
CreateDelegationPage,
DelegationStakePage,
DelegationWithdrawPage,
+ LockedBalancePage,
} from '@Pages'
import {
@@ -232,6 +233,10 @@ const App = () => {
path="/wallet/:coinType/staking/create-delegation"
element={
}
/>
+
}
+ />
{
+
+ return (
+ <>
+
+ >
+ )
+}
+
+export default LockedBalancePage
diff --git a/src/pages/index.js b/src/pages/index.js
index c23026a4..c17b8dd7 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -13,6 +13,7 @@ import ConnectionPage from './ConnectionPage/ConnectionPage'
import CreateDelegationPage from './CreateDelegation/CreateDelegation'
import DelegationStakePage from './DelegationStake/DelegationStake'
import DelegationWithdrawPage from './DelegationWithdraw/DelegationWithdraw'
+import LockedBalancePage from './LockedBalance/LockedBalance'
export {
CreateAccountPage,
@@ -30,4 +31,5 @@ export {
CreateDelegationPage,
DelegationStakePage,
DelegationWithdrawPage,
+ LockedBalancePage
}
diff --git a/src/services/API/Mintlayer/Mintlayer.js b/src/services/API/Mintlayer/Mintlayer.js
index 21d800c4..f69b44c9 100644
--- a/src/services/API/Mintlayer/Mintlayer.js
+++ b/src/services/API/Mintlayer/Mintlayer.js
@@ -7,7 +7,8 @@ const prefix = '/api/v2'
const MINTLAYER_ENDPOINTS = {
GET_ADDRESS_DATA: '/address/:address',
GET_TRANSACTION_DATA: '/transaction/:txid',
- GET_ADDRESS_UTXO: '/address/:address/spendable-utxos',
+ GET_ADDRESS_UTXO: '/address/:address/all-utxos',
+ GET_ADDRESS_SPENDABLE_UTXO: '/address/:address/spendable-utxos',
POST_TRANSACTION: '/transaction',
GET_FEES_ESTIMATES: '/feerate',
GET_ADDRESS_DELEGATIONS: '/address/:address/delegations',
@@ -211,6 +212,18 @@ const getWalletUtxos = (addresses) => {
return Promise.all(utxosPromises)
}
+const getAddressSpendableUtxo = (address) =>
+ tryServers(
+ MINTLAYER_ENDPOINTS.GET_ADDRESS_SPENDABLE_UTXO.replace(':address', address),
+ )
+
+const getWalletSpendableUtxos = (addresses) => {
+ const utxosPromises = addresses.map((address) =>
+ getAddressSpendableUtxo(address),
+ )
+ return Promise.all(utxosPromises)
+}
+
const getTokensData = async (tokens) => {
const tokensData = {}
tokens.forEach((token) => {
@@ -252,6 +265,10 @@ const getBlockDataByHeight = (height) => {
})
}
+const getBlockDataByHash = (hash) => {
+ return tryServers(MINTLAYER_ENDPOINTS.GET_BLOCK_DATA.replace(':hash', hash))
+}
+
const getWalletDelegations = (addresses) => {
const delegationsPromises = addresses.map((address) =>
getAddressDelegations(address),
@@ -304,6 +321,8 @@ export {
requestMintlayer,
getAddressUtxo,
getWalletUtxos,
+ getAddressSpendableUtxo,
+ getWalletSpendableUtxos,
getAddressDelegations,
getWalletDelegations,
getDelegationDetails,
@@ -311,6 +330,7 @@ export {
broadcastTransaction,
getFeesEstimates,
getBlocksData,
+ getBlockDataByHash,
getTokensData,
getPoolsData,
MINTLAYER_ENDPOINTS,
diff --git a/src/utils/Helpers/ML/ML.js b/src/utils/Helpers/ML/ML.js
index cc3f4e73..fb7067a8 100644
--- a/src/utils/Helpers/ML/ML.js
+++ b/src/utils/Helpers/ML/ML.js
@@ -221,8 +221,10 @@ const getParsedTransactions = (transactions, addresses) => {
const txid = transaction.txid
const fee = transaction.fee.decimal
const isConfirmed = confirmations > 0
+ const blockId = transaction.block_id
return {
+ blockId,
direction,
destAddress,
value: value || 0,