diff --git a/storybook/pages/AmountValidatorPage.qml b/storybook/pages/AmountValidatorPage.qml
new file mode 100644
index 0000000000..e349a114ee
--- /dev/null
+++ b/storybook/pages/AmountValidatorPage.qml
@@ -0,0 +1,86 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import StatusQ.Validators 0.1
+
+Item {
+ id: root
+
+ ColumnLayout {
+ anchors.centerIn: parent
+
+ TextField {
+ id: textField
+
+ Layout.alignment: Qt.AlignHCenter
+
+ validator: AmountValidator {
+ decimalPoint: buttonGroup.checkedButton.decimalPoint
+
+ maxIntegralDigits: maxIntegralDigitsSpinBox.value
+ maxDecimalDigits: maxDecimalDigitsSpinBox.value
+ }
+ }
+
+ Label {
+ Layout.alignment: Qt.AlignHCenter
+
+ text: `acceptableInput: ${textField.acceptableInput}`
+ }
+
+ ButtonGroup {
+ id: buttonGroup
+
+ buttons: radioButtonsRow.children
+ }
+
+ RowLayout {
+ id: radioButtonsRow
+
+ Layout.alignment: Qt.AlignHCenter
+
+ RadioButton {
+ checked: true
+ text: "period (.)"
+
+ readonly property string decimalPoint: "."
+ }
+
+ RadioButton {
+ text: "comma (,)"
+
+ readonly property string decimalPoint: ","
+ }
+ }
+
+ RowLayout {
+ Layout.alignment: Qt.AlignHCenter
+
+ Label {
+ text: "Max number of integral digits:"
+ }
+
+ SpinBox {
+ id: maxIntegralDigitsSpinBox
+ value: 10
+ }
+ }
+
+ RowLayout {
+ Layout.alignment: Qt.AlignHCenter
+
+ Label {
+ text: "Max number of decimal digits:"
+ }
+
+ SpinBox {
+ id: maxDecimalDigitsSpinBox
+
+ value: 5
+ }
+ }
+ }
+}
+
+// category: Validators
diff --git a/storybook/qmlTests/tests/tst_AmountValidator.qml b/storybook/qmlTests/tests/tst_AmountValidator.qml
new file mode 100644
index 0000000000..ed46469327
--- /dev/null
+++ b/storybook/qmlTests/tests/tst_AmountValidator.qml
@@ -0,0 +1,175 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+import QtTest 1.15
+
+import StatusQ.Validators 0.1
+
+Item {
+ id: root
+
+ Component {
+ id: componentUnderTest
+
+ TextField {
+ validator: AmountValidator {
+ id: validator
+
+ decimalPoint: "."
+ }
+ }
+ }
+
+ property TextField textField
+
+ TestCase {
+ name: "AmountValidator"
+ when: windowShown
+
+ function type(key, times = 1) {
+ for (let i = 0; i < times; i++) {
+ keyPress(key)
+ keyRelease(key)
+ }
+ }
+
+ function init() {
+ textField = createTemporaryObject(componentUnderTest, root)
+ }
+
+ function test_empty() {
+ compare(textField.acceptableInput, false)
+ }
+
+ function test_decimalPointOnly() {
+ textField.forceActiveFocus()
+ type(Qt.Key_Period)
+
+ compare(textField.acceptableInput, false)
+ compare(textField.text, ".")
+
+ type(Qt.Key_Period)
+
+ compare(textField.acceptableInput, false)
+ compare(textField.text, ".")
+
+ textField.text = ""
+
+ type(Qt.Key_Comma)
+
+ compare(textField.acceptableInput, false)
+ compare(textField.text, ".")
+
+ type(Qt.Key_Comma)
+ type(Qt.Key_Period)
+
+ compare(textField.acceptableInput, false)
+ compare(textField.text, ".")
+
+ textField.text = ""
+ textField.validator.decimalPoint = ","
+
+ type(Qt.Key_Period)
+
+ compare(textField.acceptableInput, false)
+ compare(textField.text, ",")
+
+ type(Qt.Key_Comma)
+ type(Qt.Key_Period)
+
+ compare(textField.acceptableInput, false)
+ compare(textField.text, ",")
+ }
+
+ function test_decimalPointWithDigits() {
+ textField.forceActiveFocus()
+ type(Qt.Key_1)
+ type(Qt.Key_Period)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, "1.")
+
+ type(Qt.Key_1)
+ type(Qt.Key_Period)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, "1.1")
+
+ textField.text = ""
+ type(Qt.Key_Period)
+ type(Qt.Key_1)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, ".1")
+ }
+
+ function test_maxIntegralDigits() {
+ textField.forceActiveFocus()
+ textField.validator.maxIntegralDigits = 2
+
+ type(Qt.Key_1)
+ type(Qt.Key_1)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, "11")
+
+ type(Qt.Key_2)
+ type(Qt.Key_2)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, "11")
+
+ type(Qt.Key_Period)
+ type(Qt.Key_3)
+ type(Qt.Key_3)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, "11.33")
+ }
+
+ function test_maxDecimalDigits() {
+ textField.forceActiveFocus()
+ textField.validator.maxDecimalDigits = 2
+
+ type(Qt.Key_Period)
+ type(Qt.Key_1)
+ type(Qt.Key_1)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, ".11")
+
+ type(Qt.Key_2)
+ type(Qt.Key_2)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, ".11")
+
+ textField.cursorPosition = 0
+
+ type(Qt.Key_2)
+ type(Qt.Key_2)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, "22.11")
+ }
+
+ function test_trimmingDecimalDigits() {
+ textField.text = ".11"
+ textField.forceActiveFocus()
+ textField.validator.maxDecimalDigits = 2
+ textField.cursorPosition = 1
+
+ type(Qt.Key_2)
+ type(Qt.Key_2)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, ".22")
+
+ type(Qt.Key_3)
+ type(Qt.Key_3)
+
+ compare(textField.acceptableInput, true)
+ compare(textField.text, ".22")
+ }
+ }
+}
diff --git a/ui/StatusQ/src/StatusQ/Validators/AmountValidator.qml b/ui/StatusQ/src/StatusQ/Validators/AmountValidator.qml
new file mode 100644
index 0000000000..8c105d7ffe
--- /dev/null
+++ b/ui/StatusQ/src/StatusQ/Validators/AmountValidator.qml
@@ -0,0 +1,69 @@
+import StatusQ 0.1
+
+/*!
+ \qmltype AmountValidator
+ \inherits GenericValidator
+ \inqmlmodule StatusQ.Validators
+ \brief Validator validating amounts provided by the user.
+
+ The validator do following checks:
+
+ - marks empty input and consisting only of decimal point as Intermediate
+ - limits allowed char set - digits and (only when maxDecimalDigits is not 0)
+ two decimal point characters are available (".", ",")
+ - replaces entered decimal point to the one provied via decimalPoint property
+ - blocks attemps of entering more then one decimal point char
+ - limits number of integral part specified by maxIntegralDigits
+ - trims number of decimal part specified by maxDecimalDigits
+ - numbers starting or ending with decimal point like .1 or 1. are considered
+ as valid input
+ */
+GenericValidator {
+ id: root
+
+ property string decimalPoint: "."
+ property int maxIntegralDigits: 10
+ property int maxDecimalDigits: 10
+
+ validate: {
+ if (input.length === 0)
+ return GenericValidator.Intermediate
+
+ const charSetRegex = root.maxDecimalDigits > 0 ? /^[0-9\.\,]*$/
+ : /^[0-9]*$/
+ const validCharSet = charSetRegex.test(input)
+
+ if (!validCharSet)
+ return GenericValidator.Invalid
+
+ const delocalized = input.replace(/,/g, ".")
+
+ if (delocalized === ".")
+ return {
+ state: GenericValidator.Intermediate,
+ output: root.decimalPoint
+ }
+
+ const pointsCount = (delocalized.match(/\./g) || []).length
+
+ if (pointsCount > 1)
+ return GenericValidator.Invalid
+
+ const [integral, decimal] = pointsCount ? delocalized.split(".")
+ : [delocalized, ""]
+
+ if (integral.length > root.maxIntegralDigits)
+ return GenericValidator.Invalid
+
+ if (pointsCount === 0)
+ return GenericValidator.Acceptable
+
+ const decimalTrimmed = decimal.slice(0, root.maxDecimalDigits)
+ const localized = [integral, decimalTrimmed].join(root.decimalPoint)
+
+ return {
+ state: GenericValidator.Acceptable,
+ output: localized
+ }
+ }
+}
diff --git a/ui/StatusQ/src/StatusQ/Validators/qmldir b/ui/StatusQ/src/StatusQ/Validators/qmldir
new file mode 100644
index 0000000000..2cbe7ceaaf
--- /dev/null
+++ b/ui/StatusQ/src/StatusQ/Validators/qmldir
@@ -0,0 +1,3 @@
+module StatusQ.Validators
+
+AmountValidator 0.1 AmountValidator.qml
diff --git a/ui/StatusQ/src/statusq.qrc b/ui/StatusQ/src/statusq.qrc
index 6e28c421e4..7e256de84a 100644
--- a/ui/StatusQ/src/statusq.qrc
+++ b/ui/StatusQ/src/statusq.qrc
@@ -249,5 +249,7 @@
StatusQ/Popups/statusModal/StatusImageWithTitle.qml
StatusQ/Popups/statusModal/StatusModalFooter.qml
StatusQ/Popups/statusModal/StatusModalHeader.qml
+ StatusQ/Validators/qmldir
+ StatusQ/Validators/AmountValidator.qml