diff --git a/src/components/basic/Input/Input.css b/src/components/basic/Input/Input.css
index 00f3c350..ea596976 100644
--- a/src/components/basic/Input/Input.css
+++ b/src/components/basic/Input/Input.css
@@ -16,6 +16,7 @@
.input::placeholder {
color: rgb(var(--color-dark-gray));
+ opacity: 0.2;
font-size: 1.5rem;
}
diff --git a/src/components/composed/Balance/Balance.test.js b/src/components/composed/Balance/Balance.test.js
index 1caaf646..29b8f185 100644
--- a/src/components/composed/Balance/Balance.test.js
+++ b/src/components/composed/Balance/Balance.test.js
@@ -25,7 +25,7 @@ test('Render account balance with ML', () => {
expect(balanceParagraphs[0].textContent).toBe(BALANCE_SAMPLE + ' ML')
expect(balanceParagraphs[1].textContent).toBe(
- BALANCE_SAMPLE * EXCHANGE_RATE_SAMPLE + ',00 USD',
+ BALANCE_SAMPLE * EXCHANGE_RATE_SAMPLE + '.00 USD',
)
})
@@ -49,7 +49,7 @@ test('Render account balance with BTC', () => {
expect(balanceParagraphs[0].textContent).toBe(BALANCE_SAMPLE + ' BTC')
expect(balanceParagraphs[1].textContent).toBe(
- BALANCE_SAMPLE * EXCHANGE_RATE_SAMPLE + ',00 USD',
+ BALANCE_SAMPLE * EXCHANGE_RATE_SAMPLE + '.00 USD',
)
})
diff --git a/src/components/composed/CryptoFiatField/CryptoFiatField.js b/src/components/composed/CryptoFiatField/CryptoFiatField.js
index 4d68faaa..0586ed10 100644
--- a/src/components/composed/CryptoFiatField/CryptoFiatField.js
+++ b/src/components/composed/CryptoFiatField/CryptoFiatField.js
@@ -39,7 +39,7 @@ const CryptoFiatField = ({
const [value, setValue] = useState(inputValue)
const [validity, setValidity] = useState(parentValidity)
const amountErrorMessage = 'Amount set is bigger than this wallet balance.'
- const amountFormatErrorMessage = 'Amount format is invalid. Use 0,00 instead.'
+ const amountFormatErrorMessage = 'Amount format is invalid. Use 0.00 instead.'
const zeroErrorMessage = 'Amount must be greater than 0.'
const isDelegationMode =
transactionMode === AppInfo.ML_TRANSACTION_MODES.DELEGATION &&
@@ -77,7 +77,7 @@ const CryptoFiatField = ({
// Consider the correct format for 0,00 that might also be 0.00
const displayedBottomValue =
networkType === AppInfo.NETWORK_TYPES.TESTNET
- ? `≈ 0,00 ${fiatName}`
+ ? `≈ 0.00 ${fiatName}`
: formattedBottomValue
const calculateFiatValue = (value) => {
@@ -141,12 +141,12 @@ const CryptoFiatField = ({
setAmountValidity(true)
setValidity('valid')
}
- setValue(value || 0)
- updateValue(value || 0)
+ setValue(value || '')
+ updateValue(value || '')
const validity = AppInfo.amountRegex.test(value)
- if (!validity) {
+ if (parsedValue > 0 && !validity) {
setValidity('invalid')
setAmountValidity(false)
setErrorMessage(amountFormatErrorMessage)
diff --git a/src/components/composed/CryptoFiatField/CryptoFiatField.test.js b/src/components/composed/CryptoFiatField/CryptoFiatField.test.js
index c7a855c3..ff540ade 100644
--- a/src/components/composed/CryptoFiatField/CryptoFiatField.test.js
+++ b/src/components/composed/CryptoFiatField/CryptoFiatField.test.js
@@ -60,7 +60,7 @@ test('Render TextField component', () => {
})
expect(input).toHaveValue(maxValueInToken.toString())
- expect(bottomNote).toHaveTextContent('≈ 10054453,50 USD')
+ expect(bottomNote).toHaveTextContent('≈ 10054453.50 USD')
// expect(switchButton).toBeInTheDocument()
// expect(arrowIcons).toHaveLength(2)
@@ -102,7 +102,7 @@ test('Render TextField component fdf', async () => {
const maxValueInCrypto = maxValueInToken - totalFeeCrypto
fireEvent.click(actionButton)
- expect(cryptoInput).toHaveValue(maxValueInCrypto.toString().replace('.', ','))
+ expect(cryptoInput).toHaveValue(maxValueInCrypto.toString())
// fireEvent.click(switchButton)
// const fiatInput = screen.getByTestId('input')
@@ -156,7 +156,7 @@ test('Render TextField when networkType is testnet', () => {
})
expect(input).toHaveValue(maxValueInToken.toString())
- expect(bottomNote).toHaveTextContent('≈ 0,00 USD')
+ expect(bottomNote).toHaveTextContent('≈ 0.00 USD')
// expect(switchButton).toBeInTheDocument()
diff --git a/src/components/composed/CurrentStaking/CurrentStaking.js b/src/components/composed/CurrentStaking/CurrentStaking.js
index 9a4de933..8b66ce2e 100644
--- a/src/components/composed/CurrentStaking/CurrentStaking.js
+++ b/src/components/composed/CurrentStaking/CurrentStaking.js
@@ -12,6 +12,7 @@ import { TransactionContext, SettingsContext, AccountContext } from '@Contexts'
import './CurrentStaking.css'
import Timer from '../../basic/Timer/Timer'
+import { useEffectOnce } from '../../../hooks/etc/useEffectOnce'
const CurrentStaking = ({ addressList }) => {
const { networkType } = useContext(SettingsContext)
@@ -39,6 +40,10 @@ const CurrentStaking = ({ addressList }) => {
getTransactions,
} = useMlWalletInfo(addressList)
+ useEffectOnce(() => {
+ getDelegations()
+ })
+
const onNextButtonClick = () => {
setDelegationStep(2)
}
diff --git a/src/components/containers/SendTransaction/SendTransaction.js b/src/components/containers/SendTransaction/SendTransaction.js
index 39a6384a..5803b32b 100644
--- a/src/components/containers/SendTransaction/SendTransaction.js
+++ b/src/components/containers/SendTransaction/SendTransaction.js
@@ -29,6 +29,7 @@ const SendTransaction = ({
confirmTransaction,
goBackToWallet,
preEnterAddress,
+ setAdjustedFee,
}) => {
const { walletType, balanceLoading } = useContext(AccountContext)
const { feeLoading, transactionMode, currentDelegationInfo } =
@@ -72,7 +73,9 @@ const SendTransaction = ({
}
const openConfirmation = async () => {
+ if (!isFormValid) return
setPopupState(true)
+ setTxErrorMessage('')
onSendTransaction &&
onSendTransaction({
to: addressTo,
@@ -106,6 +109,19 @@ const SendTransaction = ({
setPassValidity(false)
setPass('')
setAllowClosing(true)
+ } else if (e.message.includes('minimum fee')) {
+ // need to adjust fee
+ setAskPassword(false)
+ setPassPristinity(false)
+ setPassValidity(false)
+ setPass('')
+ setFee(e.message.split('minimum fee ')[1]) // Override fee with minimum fee
+ setTotalFeeCryptoParent(e.message.split('minimum fee ')[1])
+ setAdjustedFee(e.message.split('minimum fee ')[1])
+ setTxErrorMessage('Transaction fee adjusted')
+ console.error(e)
+ setAllowClosing(true)
+ setPopupState(true)
} else {
// handle other errors
setAskPassword(false)
@@ -336,7 +352,7 @@ const SendTransaction = ({
fiatName={fiatName}
totalFeeFiat={totalFeeFiat}
totalFeeCrypto={totalFeeCrypto}
- // txErrorMessage={txErrorMessage} // TODO move update on confirmation stage
+ txErrorMessage={txErrorMessage} // TODO move update on confirmation stage
fee={fee}
onConfirm={handleConfirm}
onCancel={handleCancel}
diff --git a/src/components/containers/Wallet/Delegation.test.js b/src/components/containers/Wallet/Delegation.test.js
index d3cd68b7..e8938985 100644
--- a/src/components/containers/Wallet/Delegation.test.js
+++ b/src/components/containers/Wallet/Delegation.test.js
@@ -41,7 +41,7 @@ describe('Delegation', () => {
)
expect(screen.getByTestId('delegation-date')).toHaveTextContent(date)
expect(screen.getByTestId('delegation-amount')).toHaveTextContent(
- 'Amount: 0,001',
+ 'Amount: 0.001',
)
})
diff --git a/src/hooks/UseWalletInfo/useBtcWalletInfo.test.js b/src/hooks/UseWalletInfo/useBtcWalletInfo.test.js
index 09aad964..57203df8 100644
--- a/src/hooks/UseWalletInfo/useBtcWalletInfo.test.js
+++ b/src/hooks/UseWalletInfo/useBtcWalletInfo.test.js
@@ -76,7 +76,7 @@ test('UseBtcWalletInfo hook', async () => {
transactionsList = result.current.btcTransactionsList
})
- expect(balance).toBe('0,02881771')
+ expect(balance).toBe('0.02881771')
// TODO: +1 because of the message transaction. This is a temporary solution
expect(transactionsList.length).toBe(rawTransactions.length + 1)
})
diff --git a/src/hooks/UseWalletInfo/useMlWalletInfo.js b/src/hooks/UseWalletInfo/useMlWalletInfo.js
index 717207fb..399eaf01 100644
--- a/src/hooks/UseWalletInfo/useMlWalletInfo.js
+++ b/src/hooks/UseWalletInfo/useMlWalletInfo.js
@@ -93,11 +93,22 @@ const useMlWalletInfo = (addresses) => {
const delegation_details = await Mintlayer.getDelegationDetails(
delegations.map((delegation) => delegation.delegation_id),
)
+ const blocks_data = await Mintlayer.getBlocksData(
+ delegation_details.map(
+ (delegation) => delegation.creation_block_height,
+ ),
+ )
const mergedDelegations = delegations.map((delegation, index) => {
return {
...delegation,
- creation_time: delegation_details[index].creation_time.timestamp,
+ balance: delegation.balance.atoms,
+ creation_block_height:
+ delegation_details[index].creation_block_height,
+ creation_time: blocks_data.find(
+ ({ height }) =>
+ height === delegation_details[index].creation_block_height,
+ ).header.timestamp.timestamp,
}
})
@@ -119,9 +130,9 @@ const useMlWalletInfo = (addresses) => {
if (effectCalled.current) return
effectCalled.current = true
- getTransactions()
- getDelegations()
- getBalance()
+ // getTransactions()
+ // getDelegations()
+ // getBalance()
}, [getBalance, getTransactions, getDelegations])
return {
@@ -134,6 +145,7 @@ const useMlWalletInfo = (addresses) => {
mlDelegationsBalance,
getDelegations,
getTransactions,
+ getBalance,
}
}
diff --git a/src/hooks/etc/useEffectOnce.js b/src/hooks/etc/useEffectOnce.js
new file mode 100644
index 00000000..136db9aa
--- /dev/null
+++ b/src/hooks/etc/useEffectOnce.js
@@ -0,0 +1,10 @@
+import { useEffect, useRef } from 'react'
+export function useEffectOnce(effect) {
+ const called = useRef(false)
+ useEffect(() => {
+ if (!called.current) {
+ called.current = true
+ effect()
+ }
+ }, [effect])
+}
diff --git a/src/index.js b/src/index.js
index f58cd1af..0e082ad7 100644
--- a/src/index.js
+++ b/src/index.js
@@ -127,7 +127,6 @@ const App = () => {
'This script should only be loaded in a browser extension.'
) {
// not extension env
- console.log('not extension env')
return
}
// other error throw further
diff --git a/src/pages/Dashboard/Dashboard.js b/src/pages/Dashboard/Dashboard.js
index b749e10e..475fcb5e 100644
--- a/src/pages/Dashboard/Dashboard.js
+++ b/src/pages/Dashboard/Dashboard.js
@@ -18,6 +18,7 @@ import useOneDayAgoHist from 'src/hooks/UseOneDayAgoHist/useOneDayAgoHist'
import { useNavigate } from 'react-router-dom'
import { BTC } from '@Helpers'
import { AppInfo } from '@Constants'
+import { useEffectOnce } from 'src/hooks/etc/useEffectOnce'
const DashboardPage = () => {
const { addresses, accountName, setWalletType, accountID } =
@@ -37,7 +38,7 @@ const DashboardPage = () => {
const [connectedWalletType, setConnectedWalletType] = useState('')
const { btcBalance } = useBtcWalletInfo(currentBtcAddress)
- const { mlBalance } = useMlWalletInfo(currentMlAddresses)
+ const { mlBalance, getBalance } = useMlWalletInfo(currentMlAddresses)
const { exchangeRate: btcExchangeRate } = useExchangeRates('btc', 'usd')
const { exchangeRate: mlExchangeRate } = useExchangeRates('ml', 'usd')
const { yesterdayExchangeRate: btcYesterdayExchangeRate } =
@@ -172,6 +173,10 @@ const DashboardPage = () => {
getCurrentAccount(accountID).then((account) => setAccount(account))
}, [accountID])
+ useEffectOnce(() => {
+ getBalance()
+ }, [])
+
return (
<>
diff --git a/src/pages/SendTransaction/SendTransaction.js b/src/pages/SendTransaction/SendTransaction.js
index 07dfdf7b..10f4f259 100644
--- a/src/pages/SendTransaction/SendTransaction.js
+++ b/src/pages/SendTransaction/SendTransaction.js
@@ -21,6 +21,7 @@ import { ML } from '@Cryptos'
import { Mintlayer } from '@APIs'
import './SendTransaction.css'
+import { useEffectOnce } from '../../hooks/etc/useEffectOnce'
const SendTransactionPage = () => {
const { addresses, accountID, walletType } = useContext(AccountContext)
@@ -36,6 +37,7 @@ const SendTransactionPage = () => {
: addresses.mlTestnetAddresses
const [totalFeeFiat, setTotalFeeFiat] = useState(0)
const [totalFeeCrypto, setTotalFeeCrypto] = useState(0)
+ const [adjustedFee, setAdjustedFee] = useState(0)
const navigate = useNavigate()
const tokenName = walletType.name === 'Mintlayer' ? 'ML' : 'BTC'
const fiatName = 'USD'
@@ -48,7 +50,11 @@ const SendTransactionPage = () => {
const { exchangeRate } = useExchangeRates(tokenName, fiatName)
const { btcBalance } = useBtcWalletInfo(currentBtcAddress)
- const { mlBalance } = useMlWalletInfo(currentMlAddresses)
+ const { mlBalance, getBalance } = useMlWalletInfo(currentMlAddresses)
+
+ useEffectOnce(() => {
+ getBalance()
+ })
const maxValueToken = walletType.name === 'Mintlayer' ? mlBalance : btcBalance
@@ -90,26 +96,30 @@ const SendTransactionPage = () => {
const calculateMlTotalFee = async (transactionInfo) => {
setFeeLoading(true)
const address = transactionInfo.to
- const amountToSend = MLHelpers.getAmountInAtoms(
- transactionInfo.amount,
- ).toString()
+ const amountToSend = MLHelpers.getAmountInAtoms(transactionInfo.amount)
const unusedChangeAddress = await ML.getUnusedAddress(changeAddress)
const utxos = await Mintlayer.getWalletUtxos(mlAddressList)
const parsedUtxos = utxos
.map((utxo) => JSON.parse(utxo))
.filter((utxo) => utxo.length > 0)
- const fee = await MLTransaction.calculateFee(
- parsedUtxos,
- address,
- unusedChangeAddress,
- amountToSend,
- networkType,
- )
- const feeInCoins = MLHelpers.getAmountInCoins(fee)
- setTotalFeeFiat(Format.fiatValue(feeInCoins * exchangeRate))
- setTotalFeeCrypto(feeInCoins)
- setFeeLoading(false)
- return feeInCoins
+ try {
+ const fee = await MLTransaction.calculateFee({
+ utxosTotal: parsedUtxos,
+ address: address,
+ changeAddress: unusedChangeAddress,
+ amountToUse: amountToSend,
+ network: networkType,
+ })
+ const feeInCoins = MLHelpers.getAmountInCoins(Number(fee))
+ setTotalFeeFiat(Format.fiatValue(feeInCoins * exchangeRate))
+ setTotalFeeCrypto(feeInCoins)
+ setFeeLoading(false)
+ return feeInCoins
+ } catch (e) {
+ console.error('Error calculating fee:', e)
+ goBackToWallet()
+ setFeeLoading(false)
+ }
}
const createTransaction = async (transactionInfo) => {
@@ -151,7 +161,7 @@ const SendTransactionPage = () => {
const confirmMlTransaction = async (password) => {
const amountToSend = MLHelpers.getAmountInAtoms(
transactionInformation.amount,
- ).toString()
+ )
const { mlPrivKeys } = await Account.unlockAccount(accountID, password)
const privKey =
networkType === 'mainnet'
@@ -173,14 +183,17 @@ const SendTransactionPage = () => {
const parsedUtxos = utxos
.map((utxo) => JSON.parse(utxo))
.filter((utxo) => utxo.length > 0)
- const result = await MLTransaction.sendTransaction(
- parsedUtxos,
- keysList,
- transactionInformation.to,
- unusedChageAddress,
- amountToSend,
- networkType,
- )
+ const result = await MLTransaction.sendTransaction({
+ utxosTotal: parsedUtxos,
+ keysList: keysList,
+ address: transactionInformation.to,
+ changeAddress: unusedChageAddress,
+ amountToUse: amountToSend,
+ network: networkType,
+ ...(adjustedFee && {
+ adjustedFee: MLHelpers.getAmountInAtoms(adjustedFee),
+ }),
+ })
return result
}
@@ -195,6 +208,7 @@ const SendTransactionPage = () => {
totalFeeFiat={totalFeeFiat}
totalFeeCrypto={totalFeeCrypto}
setTotalFeeCrypto={setTotalFeeCrypto}
+ setAdjustedFee={setAdjustedFee}
transactionData={transactionData}
exchangeRate={exchangeRate}
maxValueInToken={maxValueToken}
diff --git a/src/pages/Staking/Staking.js b/src/pages/Staking/Staking.js
index acfbcc8b..868ed725 100644
--- a/src/pages/Staking/Staking.js
+++ b/src/pages/Staking/Staking.js
@@ -14,6 +14,7 @@ import { ML } from '@Cryptos'
import { Mintlayer } from '@APIs'
import './Staking.css'
+import { useEffectOnce } from '../../hooks/etc/useEffectOnce'
const StakingPage = () => {
const { state } = useLocation()
@@ -50,7 +51,7 @@ const StakingPage = () => {
const [transactionInformation, setTransactionInformation] = useState(null)
const { exchangeRate } = useExchangeRates(tokenName, fiatName)
- const { mlBalance } = useMlWalletInfo(currentMlAddresses)
+ const { mlBalance, getBalance } = useMlWalletInfo(currentMlAddresses)
const delegationBalance = Format.BTCValue(
MLHelpers.getAmountInCoins(currentDelegationInfo.balance),
)
@@ -63,6 +64,10 @@ const StakingPage = () => {
navigate('/wallet')
}
+ useEffectOnce(()=>{
+ getBalance()
+ })
+
useEffect(() => {
if (state && state.action === 'createDelegate') {
setDelegationStep(2)
@@ -96,7 +101,7 @@ const StakingPage = () => {
const address = transactionInfo.to
const amountToSend = MLHelpers.getAmountInAtoms(
transactionInfo.amount,
- ).toString()
+ )
const unusedChangeAddress = await ML.getUnusedAddress(changeAddresses)
const unusedReceivingAddress = await ML.getUnusedAddress(receivingAddresses)
const utxos = await Mintlayer.getWalletUtxos(mlAddressList)
@@ -105,15 +110,13 @@ const StakingPage = () => {
.filter((utxo) => utxo.length > 0)
const fee =
transactionMode === AppInfo.ML_TRANSACTION_MODES.STAKING
- ? await MLTransaction.calculateFee(
- parsedUtxos,
- undefined,
- unusedChangeAddress,
- amountToSend,
- networkType,
- undefined,
- address,
- )
+ ? await MLTransaction.calculateFee({
+ utxosTotal: parsedUtxos,
+ changeAddress: unusedChangeAddress,
+ amountToUse: amountToSend,
+ network: networkType,
+ delegationId: address,
+ })
: transactionMode === AppInfo.ML_TRANSACTION_MODES.WITHDRAW
? await MLTransaction.calculateSpenDelegFee(
address,
@@ -121,15 +124,15 @@ const StakingPage = () => {
networkType,
currentDelegationInfo,
)
- : await MLTransaction.calculateFee(
- parsedUtxos,
- unusedReceivingAddress,
- unusedChangeAddress,
- amountToSend,
- networkType,
- address,
- )
- const feeInCoins = MLHelpers.getAmountInCoins(fee)
+ : await MLTransaction.calculateFee({
+ utxosTotal: parsedUtxos,
+ address: unusedReceivingAddress,
+ changeAddress: unusedChangeAddress,
+ amountToUse: BigInt(0),
+ network: networkType,
+ poolId: address,
+ })
+ const feeInCoins = MLHelpers.getAmountInCoins(Number(fee))
setTotalFeeFiat(Format.fiatValue(feeInCoins * exchangeRate))
setTotalFeeCrypto(feeInCoins)
setFeeLoading(false)
@@ -144,7 +147,7 @@ const StakingPage = () => {
const confirmMlTransaction = async (password) => {
const amountToSend = MLHelpers.getAmountInAtoms(
transactionInformation.amount,
- ).toString()
+ )
const { mlPrivKeys } = await Account.unlockAccount(accountID, password)
const privKey =
networkType === 'mainnet'
@@ -170,16 +173,14 @@ const StakingPage = () => {
const result =
transactionMode === AppInfo.ML_TRANSACTION_MODES.STAKING
- ? await MLTransaction.sendTransaction(
- parsedUtxos,
- keysList,
- undefined,
- unusedChageAddress,
- amountToSend,
- networkType,
- undefined,
- transactionInformation.to,
- )
+ ? await MLTransaction.sendTransaction({
+ utxosTotal: parsedUtxos,
+ keysList: keysList,
+ changeAddress: unusedChageAddress,
+ amountToUse: amountToSend,
+ network: networkType,
+ delegationId: transactionInformation.to,
+ })
: transactionMode === AppInfo.ML_TRANSACTION_MODES.WITHDRAW
? await MLTransaction.spendFromDelegation(
keysList,
@@ -188,17 +189,16 @@ const StakingPage = () => {
networkType,
currentDelegationInfo,
)
- : await MLTransaction.sendTransaction(
- parsedUtxos,
- keysList,
- unusedReceivingAddress,
- unusedChageAddress,
- '0',
- networkType,
- transactionInformation.to,
- undefined,
- transactionMode,
- )
+ : await MLTransaction.sendTransaction({
+ utxosTotal: parsedUtxos,
+ keysList: keysList,
+ address: unusedReceivingAddress,
+ changeAddress: unusedChageAddress,
+ amountToUse: BigInt('0'),
+ network: networkType,
+ poolId: transactionInformation.to,
+ transactionMode: transactionMode,
+ })
return result
}
diff --git a/src/pages/Wallet/Wallet.js b/src/pages/Wallet/Wallet.js
index fed8e860..1096c9e5 100644
--- a/src/pages/Wallet/Wallet.js
+++ b/src/pages/Wallet/Wallet.js
@@ -12,6 +12,7 @@ import { AppInfo } from '@Constants'
import { LocalStorageService } from '@Storage'
import './Wallet.css'
+import { useEffectOnce } from '../../hooks/etc/useEffectOnce'
const WalletPage = () => {
const navigate = useNavigate()
@@ -29,8 +30,13 @@ const WalletPage = () => {
: addresses.mlTestnetAddresses
const [openShowAddress, setOpenShowAddress] = useState(false)
const { btcTransactionsList, btcBalance } = useBtcWalletInfo(btcAddress)
- const { mlTransactionsList, mlBalance, mlBalanceLocked } =
- useMlWalletInfo(currentMlAddresses)
+ const {
+ mlTransactionsList,
+ mlBalance,
+ mlBalanceLocked,
+ getTransactions,
+ getBalance,
+ } = useMlWalletInfo(currentMlAddresses)
const { exchangeRate: btcExchangeRate } = useExchangeRates('btc', 'usd')
const { exchangeRate: mlExchangeRate } = useExchangeRates('ml', 'usd')
@@ -64,6 +70,11 @@ const WalletPage = () => {
LocalStorageService.getItem(unconfirmedTransactionString) &&
walletType.name === 'Mintlayer'
+ useEffectOnce(() => {
+ getTransactions()
+ getBalance()
+ })
+
return (
diff --git a/src/services/API/Mintlayer/Mintlayer.js b/src/services/API/Mintlayer/Mintlayer.js
index 5e12834c..162b8bf0 100644
--- a/src/services/API/Mintlayer/Mintlayer.js
+++ b/src/services/API/Mintlayer/Mintlayer.js
@@ -2,17 +2,19 @@ import { EnvVars } from '@Constants'
import { LocalStorageService } from '@Storage'
import { AppInfo } from '@Constants'
-const prefix = '/api/v1'
+const prefix = '/api/v2'
const MINTLAYER_ENDPOINTS = {
GET_ADDRESS_DATA: '/address/:address',
GET_TRANSACTION_DATA: '/transaction/:txid',
- GET_ADDRESS_UTXO: '/address/:address/available-utxos',
+ GET_ADDRESS_UTXO: '/address/:address/spendable-utxos',
POST_TRANSACTION: '/transaction',
GET_FEES_ESTIMATES: '/feerate',
GET_ADDRESS_DELEGATIONS: '/address/:address/delegations',
GET_DELEGATION: '/delegation/:delegation',
GET_CHAIN_TIP: '/chain/tip',
+ GET_BLOCK_HASH: '/chain/:height',
+ GET_BLOCK_DATA: '/block/:hash',
}
const requestMintlayer = async (url, body = null, request = fetch) => {
@@ -28,6 +30,20 @@ const requestMintlayer = async (url, body = null, request = fetch) => {
)
}
+ // handle RPC error
+ if (
+ error.error.includes(
+ 'Mempool error: Transaction does not pay sufficient fees to be relayed',
+ )
+ ) {
+ const errorMessage = error.error
+ .split('Mempool error: ')[1]
+ .split(')')[0]
+ .replace('(tx_fee:', '. estimated fee')
+ .replace('min_relay_fee:', 'minimum fee')
+ throw new Error(errorMessage)
+ }
+
// handle RPC error
if (error.error.includes('Mempool error:')) {
const errorMessage = error.error
@@ -83,10 +99,10 @@ const getAddressBalance = async (address) => {
const response = await getAddressData(address)
const data = JSON.parse(response)
const balance = {
- balanceInAtoms: data.coin_balance,
+ balanceInAtoms: data.coin_balance.atoms,
}
const balanceLocked = {
- balanceInAtoms: data.locked_coin_balance || 0,
+ balanceInAtoms: data.locked_coin_balance.atoms || 0,
}
return { balance, balanceLocked }
} catch (error) {
@@ -192,6 +208,18 @@ const getDelegation = (delegation) =>
MINTLAYER_ENDPOINTS.GET_DELEGATION.replace(':delegation', delegation),
)
+const getBlockDataByHeight = (height) => {
+ return tryServers(
+ MINTLAYER_ENDPOINTS.GET_BLOCK_HASH.replace(':height', height),
+ )
+ .then(JSON.parse)
+ .then((response) => {
+ return tryServers(
+ MINTLAYER_ENDPOINTS.GET_BLOCK_DATA.replace(':hash', response),
+ )
+ })
+}
+
const getWalletDelegations = (addresses) => {
const delegationsPromises = addresses.map((address) =>
getAddressDelegations(address),
@@ -208,6 +236,12 @@ const getDelegationDetails = (delegations) => {
results.flatMap(JSON.parse),
)
}
+const getBlocksData = (heights) => {
+ const heightsPromises = heights.map((height) => getBlockDataByHeight(height))
+ return Promise.all(heightsPromises).then((results) =>
+ results.flatMap(JSON.parse),
+ )
+}
const getChainTip = async () => {
return tryServers(MINTLAYER_ENDPOINTS.GET_CHAIN_TIP)
@@ -237,5 +271,6 @@ export {
getChainTip,
broadcastTransaction,
getFeesEstimates,
+ getBlocksData,
MINTLAYER_ENDPOINTS,
}
diff --git a/src/services/Crypto/Mintlayer/Mintlayer.js b/src/services/Crypto/Mintlayer/Mintlayer.js
index fbac8687..45359211 100644
--- a/src/services/Crypto/Mintlayer/Mintlayer.js
+++ b/src/services/Crypto/Mintlayer/Mintlayer.js
@@ -14,7 +14,6 @@ import init, {
estimate_transaction_size,
encode_lock_until_time,
encode_output_lock_then_transfer,
- encode_lock_until_height,
encode_lock_for_block_count,
encode_output_create_delegation,
encode_output_delegate_staking,
@@ -173,6 +172,7 @@ export const getOutputs = async ({
if (type === 'LockThenTransfer' && !lock) {
throw new Error('LockThenTransfer requires a lock')
}
+
const amountInstace = Amount.from_atoms(amount)
const networkIndex = NETWORKS[networkType]
@@ -185,7 +185,7 @@ export const getOutputs = async ({
lockEncoded = encode_lock_until_time(BigInt(lock.UntilTime.timestamp))
}
if (lock.ForBlockCount) {
- lockEncoded = encode_lock_until_height(BigInt(lock.ForBlockCount))
+ lockEncoded = encode_lock_for_block_count(BigInt(lock.ForBlockCount))
}
return encode_output_lock_then_transfer(
amountInstace,
diff --git a/src/services/Database/IndexedDB/IndexedDB.js b/src/services/Database/IndexedDB/IndexedDB.js
index 3d861764..6af94f9e 100644
--- a/src/services/Database/IndexedDB/IndexedDB.js
+++ b/src/services/Database/IndexedDB/IndexedDB.js
@@ -68,7 +68,6 @@ const saveAccounts = async (accounts, onError, DB = IDB) => {
for (const account of accounts) {
await update(oldAccounts, account)
}
- console.log('Accounts saved successfully')
db.close()
} catch (error) {
diff --git a/src/utils/Constants/AppInfo/AppInfo.js b/src/utils/Constants/AppInfo/AppInfo.js
index 2cdbc046..1601cdef 100644
--- a/src/utils/Constants/AppInfo/AppInfo.js
+++ b/src/utils/Constants/AppInfo/AppInfo.js
@@ -6,9 +6,9 @@ const appAccounts = async () => {
return accounts
}
-const decimalSeparator = ','
-const thousandsSeparator = '.'
-const amountRegex = /^\d+(,\d+)?$/
+const decimalSeparator = '.'
+const thousandsSeparator = ' '
+const amountRegex = /^\d+(.\d+)?$/
const minEntropyLength = 192
const DEFAULT_WALLETS_TO_CREATE = ['btc']
const ML_ATOMS_PER_COIN = 100000000000
diff --git a/src/utils/Helpers/ML/ML.js b/src/utils/Helpers/ML/ML.js
index 8d19a1ef..c3e0b2b1 100644
--- a/src/utils/Helpers/ML/ML.js
+++ b/src/utils/Helpers/ML/ML.js
@@ -7,7 +7,7 @@ const getAmountInCoins = (amointInAtoms) => {
}
const getAmountInAtoms = (amountInCoins) => {
- return Math.round(amountInCoins * AppInfo.ML_ATOMS_PER_COIN)
+ return BigInt(Math.round(amountInCoins * AppInfo.ML_ATOMS_PER_COIN))
}
const getParsedTransactions = (transactions, addresses) => {
@@ -90,38 +90,38 @@ const getParsedTransactions = (transactions, addresses) => {
const totalValue = transaction.outputs.reduce((acc, output) => {
if (!addresses.includes(output.destination)) {
if (output.type === 'Transfer') {
- return acc + output.value.amount
+ return acc + output.value.amount.decimal
}
if (output.type === 'LockThenTransfer') {
- return acc + Number(output.value.amount)
+ return acc + Number(output.value.amount.decimal)
}
if (output.type === 'CreateStakePool') {
type = 'CreateStakePool'
destAddress = output.pool_id
- return acc + Number(output.data.amount)
+ return acc + Number(output.data.amount.decimal)
}
if (output.type === 'DelegateStaking') {
type = 'DelegateStaking'
destAddress = output.delegation_id
- return acc + Number(output.amount)
+ return acc + Number(output.amount.decimal)
}
if (output.type === 'CreateDelegationId') {
type = 'CreateDelegationId'
destAddress = output.pool_id
sameWalletTransaction = false
- return acc + Number(output.amount)
+ return acc + Number(output.amount.decimal)
}
}
if (addresses.includes(output.destination)) {
if (output.type === 'CreateStakePool') {
type = 'CreateStakePool'
destAddress = output.pool_id
- return acc + Number(output.data.amount)
+ return acc + Number(output.data.amount.decimal)
}
if (output.type === 'DelegateStaking') {
type = 'DelegateStaking'
destAddress = output.delegation_id
- return acc + Number(output.amount)
+ return acc + Number(output.amount.decimal)
}
if (output.type === 'CreateDelegationId') {
type = 'CreateDelegationId'
@@ -132,7 +132,7 @@ const getParsedTransactions = (transactions, addresses) => {
}
return acc
}, 0)
- value = getAmountInCoins(totalValue, AppInfo.ML_ATOMS_PER_COIN)
+ value = totalValue
}
if (withInputUTXO && direction === 'in' && transaction.outputs.length > 0) {
@@ -140,15 +140,15 @@ const getParsedTransactions = (transactions, addresses) => {
const totalValue = transaction.outputs.reduce((acc, output) => {
if (addresses.includes(output.destination)) {
if (output.type === 'Transfer') {
- return acc + output.value.amount
+ return acc + output.value.amount.decimal
}
if (output.type === 'LockThenTransfer') {
- return acc + Number(output.value.amount)
+ return acc + Number(output.value.amount.decimal)
}
}
return acc
}, 0)
- value = getAmountInCoins(totalValue, AppInfo.ML_ATOMS_PER_COIN)
+ value = totalValue
}
if (
@@ -163,31 +163,28 @@ const getParsedTransactions = (transactions, addresses) => {
const totalValue = transaction.outputs.reduce((acc, output) => {
if (addresses.includes(output.destination)) {
if (output.type === 'Transfer') {
- return acc + output.value.amount
+ return acc + output.value.amount.decimal
}
if (output.type === 'LockThenTransfer') {
if (
- transaction.inputs[0].input?.Account?.account
- ?.DelegationBalance[0]
+ transaction.inputs[0].input?.account_type === 'DelegationBalance'
) {
type = 'Delegate Withdrawal'
- destAddress =
- transaction.inputs[0].input?.Account?.account
- ?.DelegationBalance[0]
+ destAddress = transaction.inputs[0].input?.delegation_id
}
- return acc + Number(output.value.amount)
+ return acc + Number(output.value.amount.decimal)
}
}
return acc
}, 0)
- value = getAmountInCoins(totalValue, AppInfo.ML_ATOMS_PER_COIN)
+ value = totalValue
}
const confirmations = transaction.confirmations
const date = transaction.timestamp
const txid = transaction.txid
- const fee = transaction.fee
+ const fee = transaction.fee.decimal
const isConfirmed = confirmations > 0
return {
diff --git a/src/utils/Helpers/ML/MLTransaction.js b/src/utils/Helpers/ML/MLTransaction.js
index edede9ea..919bf46a 100644
--- a/src/utils/Helpers/ML/MLTransaction.js
+++ b/src/utils/Helpers/ML/MLTransaction.js
@@ -6,7 +6,10 @@ import { ML as MLHelpers } from '@Helpers'
import { AppInfo } from '@Constants'
const getUtxoBalance = (utxo) => {
- return utxo.reduce((sum, item) => sum + Number(item.utxo.value.amount), 0)
+ return utxo.reduce(
+ (sum, item) => sum + BigInt(item.utxo.value.amount.atoms),
+ BigInt(0),
+ )
}
const getUtxoAvailable = (utxo) => {
@@ -23,7 +26,7 @@ const getUtxoAvailable = (utxo) => {
const getUtxoTransaction = (utxo) => {
return utxo.map((item) => ({
- transaction: item.outpoint.id.Transaction,
+ transaction: item.outpoint.source_id,
index: item.outpoint.index,
}))
}
@@ -56,19 +59,40 @@ const getTxInput = async (outpointSourceId) => {
)
}
-const getTransactionUtxos = (utxos, amountToUse, fee = 0) => {
- let balance = 0
+/**
+ * Get utxos to spend
+ * NOTE: This function require optimization to get UTXOs with the lowest amounts first or 50% lowest and 50% highest, see: https://arxiv.org/pdf/2311.01113.pdf
+ * At this point there is a risk of not having enough UTXOs to spend because first picked UTXOs is equal to the amount to spend without fee
+ * In that case backend will return error with proper fee amount wich is parsed and passed as override fee value.
+ * Need to add some "backup" additional UTXO is AMOUNT is equal of UTXOs amount so that server error less likely to happen but I'm leaving it just to be sure
+ * @param utxos
+ * @param amountToUse
+ * @param fee
+ * @returns {*[]}
+ */
+const getTransactionUtxos = (utxos, amountToUse, fee = BigInt(0)) => {
+ let balance = BigInt(0)
const utxosToSpend = []
+ let lastIndex = 0
for (let i = 0; i < utxos.length; i++) {
+ lastIndex = i
const utxoBalance = getUtxoBalance(utxos[i])
- if (balance < Number(amountToUse) + fee) {
+ if (balance < BigInt(amountToUse) + fee) {
balance += utxoBalance
utxosToSpend.push(utxos[i])
} else {
break
}
}
+
+ if (balance === BigInt(amountToUse)) {
+ // pick up extra UTXO
+ if (utxos[lastIndex + 1]) {
+ utxosToSpend.push(utxos[lastIndex + 1])
+ }
+ }
+
return utxosToSpend
}
@@ -130,7 +154,7 @@ const getOptUtxos = async (utxos, network) => {
const opt_utxos = await Promise.all(
utxos.map((item) => {
return ML.getOutputs({
- amount: item.utxo.value.amount,
+ amount: item.utxo.value.amount.atoms,
address: item.utxo.destination,
networkType: network,
type: item.utxo.type,
@@ -188,9 +212,11 @@ const totalUtxosAmount = (utxosToSpend) => {
return utxosToSpend
.flatMap((utxo) => [...utxo])
.reduce((acc, utxo) => {
- const amount = utxo.utxo.value ? Number(utxo.utxo.value.amount) : 0
+ const amount = utxo?.utxo?.value?.amount
+ ? BigInt(utxo.utxo.value.amount.atoms)
+ : 0
return acc + amount
- }, 0)
+ }, BigInt(0))
}
const getUtxoAddress = (utxosToSpend) => {
@@ -199,7 +225,7 @@ const getUtxoAddress = (utxosToSpend) => {
.map((utxo) => utxo.utxo.destination)
}
-const calculateFee = async (
+const calculateFee = async ({
utxosTotal,
address,
changeAddress,
@@ -207,11 +233,11 @@ const calculateFee = async (
network,
poolId,
delegationId,
-) => {
- const amountToUseFinale = Number(amountToUse) <= 0 ? 1 : amountToUse
+}) => {
+ const amountToUseFinale = amountToUse <= 0 ? BigInt(1) : amountToUse
const utxos = getUtxoAvailable(utxosTotal)
const totalAmount = !poolId ? totalUtxosAmount(utxos) : 0
- if (totalAmount < Number(amountToUse) && !poolId) {
+ if (totalAmount < BigInt(amountToUse) && !poolId) {
throw new Error('Insufficient funds')
}
const requireUtxo = getTransactionUtxos(utxos, amountToUseFinale)
@@ -229,7 +255,7 @@ const calculateFee = async (
delegationId,
)
const changeAmount = (
- totalUtxosAmount(requireUtxo) - Number(amountToUseFinale)
+ totalUtxosAmount(requireUtxo) - amountToUseFinale
).toString()
const txChangeOutput = await getTxOutput(changeAmount, changeAddress, network)
const outputs = [...txOutput, ...txChangeOutput]
@@ -244,7 +270,7 @@ const calculateFee = async (
const feeEstimates = JSON.parse(feeEstimatesResponse)
const fee = Math.ceil((Number(feeEstimates) / 1000) * size)
- return fee
+ return BigInt(fee)
}
const calculateSpenDelegFee = async (address, amount, network, delegation) => {
@@ -277,7 +303,7 @@ const calculateSpenDelegFee = async (address, amount, network, delegation) => {
return fee
}
-const sendTransaction = async (
+const sendTransaction = async ({
utxosTotal,
keysList,
address,
@@ -287,25 +313,29 @@ const sendTransaction = async (
poolId,
delegationId,
transactionMode,
-) => {
+ adjustedFee,
+}) => {
const utxos = getUtxoAvailable(utxosTotal)
const totalAmount = totalUtxosAmount(utxos)
- const fee = await calculateFee(
- utxos,
- address,
- changeAddress,
- amountToUse,
- network,
- poolId,
- delegationId,
- )
- if (fee > AppInfo.MAX_ML_FEE) {
+ const fee =
+ adjustedFee ||
+ (await calculateFee({
+ utxosTotal: utxos,
+ address,
+ changeAddress,
+ amountToUse,
+ network,
+ poolId,
+ delegationId,
+ }))
+
+ if (fee > BigInt(AppInfo.MAX_ML_FEE)) {
throw new Error('Fee is too high, please try again later.')
}
let amount = amountToUse
- if (totalAmount < Number(amountToUse) + fee) {
+ if (totalAmount < amountToUse + fee) {
amount = totalAmount - fee
}
@@ -322,11 +352,8 @@ const sendTransaction = async (
poolId,
delegationId,
)
- const changeAmount = (
- totalUtxosAmount(requireUtxo) -
- Number(amount) -
- fee
- ).toString()
+
+ const changeAmount = (totalUtxosAmount(requireUtxo) - amount - fee).toString()
const txChangeOutput = await getTxOutput(changeAmount, changeAddress, network)
const outputs = [...txOutput, ...txChangeOutput]
const optUtxos = await getOptUtxos(requireUtxo.flat(), network)
@@ -364,11 +391,11 @@ const sendTransaction = async (
direction: 'out',
type: 'Unconfirmed',
destAddress: address || delegationId,
- value: MLHelpers.getAmountInCoins(amount),
+ value: MLHelpers.getAmountInCoins(Number(amount)),
confirmations: 0,
date: '',
txid: JSON.parse(result).tx_id,
- fee: fee,
+ fee: fee.toString(),
isConfirmed: false,
mode: transactionMode,
poolId: poolId,
@@ -448,7 +475,7 @@ const spendFromDelegation = async (
direction: 'out',
type: 'Unconfirmed',
destAddress: address,
- value: MLHelpers.getAmountInCoins(amount),
+ value: MLHelpers.getAmountInCoins(Number(amount)),
confirmations: 0,
date: '',
txid: JSON.parse(result).tx_id,
diff --git a/src/utils/Helpers/ML/MLTransaction.test.js b/src/utils/Helpers/ML/MLTransaction.test.js
index 8b1fe23f..5ebd133c 100644
--- a/src/utils/Helpers/ML/MLTransaction.test.js
+++ b/src/utils/Helpers/ML/MLTransaction.test.js
@@ -8,51 +8,60 @@ import {
const UTXSOS_MOCK = [
{
outpoint: {
- id: {
- Transaction:
- '0cd5de5319de96f7c967a24a51224f4eab7882e6dbe336ab6837b15e1b68dace',
- },
- index: 1,
+ source_id:
+ '0cd5de5319de96f7c967a24a51224f4eab7882e6dbe336ab6837b15e1b68dace',
+ source_type: 'Transaction',
+ input_type: 'UTXO',
+ index: 0,
},
utxo: {
destination: 'tmt1qylgafccyyy26zrtqk8gjvcwzut26taruuyzmcr6',
type: 'Transfer',
value: {
- amount: '200',
+ amount: {
+ atoms: '200',
+ decimals: '0.000000002',
+ },
type: 'Coin',
},
},
},
{
outpoint: {
- id: {
- Transaction:
- '0cd5de5319de96f7c967a24a51224f4eab7882e6dbe336ab6837b15e1b68dace',
- },
+ source_id:
+ '0cd5de5319de96f7c967a24a51224f4eab7882e6dbe336ab6837b15e1b68dace',
+ source_type: 'Transaction',
+ input_type: 'UTXO',
index: 1,
},
utxo: {
destination: 'tmt1qylgafccyyy26zrtqk8gjvcwzut26taruuyzmcr6',
type: 'Transfer',
value: {
- amount: '200',
+ amount: {
+ atoms: '200',
+ decimals: '0.000000002',
+ },
type: 'Coin',
},
},
},
{
outpoint: {
- id: {
- Transaction:
- '0cd5de5319de96f7c967a24a51224f4eab7882e6dbe336ab6837b15e1b68dace',
- },
- index: 1,
+ source_id:
+ '0cd5de5319de96f7c967a24a51224f4eab7882e6dbe336ab6837b15e1b68dace',
+ source_type: 'Transaction',
+ input_type: 'UTXO',
+ index: 2,
},
utxo: {
destination: 'tmt1qylgafccyyy26zrtqk8gjvcwzut26taruuyzmcr6',
type: 'Transfer',
value: {
- amount: '200',
+ amount: {
+ atoms: '200',
+ decimals: '0.000000002',
+ },
type: 'Coin',
},
},
diff --git a/src/utils/Helpers/Number/Format.js b/src/utils/Helpers/Number/Format.js
index 6110378c..945c215a 100644
--- a/src/utils/Helpers/Number/Format.js
+++ b/src/utils/Helpers/Number/Format.js
@@ -5,14 +5,6 @@ import { getDecimalNumber } from './Number'
const getNumber = (value) =>
typeof value === 'number' ? value : NumbersHelper.floatStringToNumber(value)
-//TODO fix the value
-// const BTCValue = (value) =>
-// getNumber(value)
-// .toFixed(8)
-// .replace(/\.0+$/, '')
-// .replace(/(\.0{0,}[1-9]+)(0+)$/, '$1')
-// .replace('.', AppInfo.decimalSeparator)
-
const BTCValue = (value) => {
let str = getNumber(value).toString()
const decimalIndex = str.indexOf('.')