Skip to content

Commit 399cd23

Browse files
committed
feat: implement excess tolerance input handling and validation in management configuration
1 parent 2fdc188 commit 399cd23

File tree

2 files changed

+221
-8
lines changed

2 files changed

+221
-8
lines changed

internal/adapters/entrypoints/rest/assets/static/configUtils.js

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,78 @@ function isfeePercentageKey(key) {
6767
return key === 'feePercentage';
6868
}
6969

70+
function isExcessToleranceKey(key) {
71+
return key === 'excessToleranceFixed' || key === 'excessTolerancePercentage';
72+
}
73+
74+
function isExcessToleranceFixedKey(key) {
75+
return key === 'excessToleranceFixed';
76+
}
77+
78+
function isExcessTolerancePercentageKey(key) {
79+
return key === 'excessTolerancePercentage';
80+
}
81+
82+
/**
83+
* Validates an excessToleranceFixed value.
84+
* @param {string|number} value - The value to validate (in wei as bigint string)
85+
* @returns {{isValid: boolean, error: string|null}} Validation result
86+
*/
87+
function validateExcessToleranceFixed(value) {
88+
if (value === null || value === undefined || value === '') {
89+
return { isValid: true, error: null }; // Optional field, empty is OK
90+
}
91+
92+
const strValue = String(value).trim();
93+
94+
// Check if it's a valid number
95+
const num = parseFloat(strValue);
96+
if (isNaN(num)) {
97+
return { isValid: false, error: 'Excess tolerance fixed must be a valid number' };
98+
}
99+
100+
// Check if non-negative
101+
if (num < 0) {
102+
return { isValid: false, error: 'Excess tolerance fixed must be a non-negative number' };
103+
}
104+
105+
// Check decimal places (maximum 18 for wei conversion)
106+
const decimalPart = strValue.split('.')[1];
107+
if (decimalPart && decimalPart.length > 18) {
108+
return { isValid: false, error: 'Excess tolerance fixed cannot have more than 18 decimal places' };
109+
}
110+
111+
return { isValid: true, error: null };
112+
}
113+
114+
/**
115+
* Validates an excessTolerancePercentage value.
116+
* @param {string|number} value - The value to validate (0-100 percentage)
117+
* @returns {{isValid: boolean, error: string|null}} Validation result
118+
*/
119+
function validateExcessTolerancePercentage(value) {
120+
if (value === null || value === undefined || value === '') {
121+
return { isValid: true, error: null }; // Optional field, empty is OK (will default to 0)
122+
}
123+
124+
const strValue = String(value).trim();
125+
const num = parseFloat(strValue);
126+
127+
if (isNaN(num)) {
128+
return { isValid: false, error: 'Excess tolerance percentage must be a valid number' };
129+
}
130+
131+
if (num < 0) {
132+
return { isValid: false, error: 'Excess tolerance percentage must be non-negative' };
133+
}
134+
135+
if (num > 100) {
136+
return { isValid: false, error: 'Excess tolerance percentage cannot exceed 100%' };
137+
}
138+
139+
return { isValid: true, error: null };
140+
}
141+
70142
function inferType(value) {
71143
if (value === null || value === undefined) return 'undefined';
72144
if (Array.isArray(value)) return 'array';
@@ -82,7 +154,7 @@ function validateConfig(config, originalConfig) {
82154
const expectedType = inferType(expectedValue);
83155
let actualType = inferType(value);
84156
if (
85-
(isFeeKey(key) || isfeePercentageKey(key)) &&
157+
(isFeeKey(key) || isfeePercentageKey(key) || isExcessToleranceKey(key)) &&
86158
((expectedType === 'number' && actualType === 'string') ||
87159
(expectedType === 'string' && actualType === 'number'))
88160
) {
@@ -184,5 +256,10 @@ export {
184256
hasDuplicateConfirmationAmounts,
185257
isfeePercentageKey,
186258
isToggableFeeKey,
187-
formatCap
259+
formatCap,
260+
isExcessToleranceKey,
261+
isExcessToleranceFixedKey,
262+
isExcessTolerancePercentageKey,
263+
validateExcessToleranceFixed,
264+
validateExcessTolerancePercentage
188265
};

internal/adapters/entrypoints/rest/assets/static/management.js

Lines changed: 142 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import {
1010
hasDuplicateConfirmationAmounts,
1111
isfeePercentageKey,
1212
isToggableFeeKey,
13-
formatCap
13+
formatCap,
14+
isExcessToleranceFixedKey,
15+
validateExcessToleranceFixed,
16+
validateExcessTolerancePercentage
1417
} from './configUtils.js';
1518

1619
const generalChanged = { value: false };
@@ -45,7 +48,12 @@ const populateProviderData = (providerData, rskAddress, btcAddress, coldWallet)
4548
setTextContent('isOperational', providerData.status ? "Operational" : "Not Operational");
4649
};
4750

48-
const createInput = (section, key, value) => {
51+
const createInput = (section, key, value, config) => {
52+
// Skip excessTolerancePercentage - handled together with excessToleranceFixed
53+
if (key === 'excessTolerancePercentage') {
54+
return;
55+
}
56+
4957
const div = document.createElement('div');
5058
div.classList.add('mb-3');
5159

@@ -63,6 +71,10 @@ const createInput = (section, key, value) => {
6371

6472
if (typeof value === 'boolean') {
6573
createCheckboxInput(inputContainer, section, key, value);
74+
} else if (isExcessToleranceFixedKey(key)) {
75+
// Handle excess tolerance with combined fixed/percentage toggle
76+
const percentageValue = config?.excessTolerancePercentage ?? 0;
77+
createExcessToleranceInput(inputContainer, label, section, value, percentageValue);
6678
} else if (isToggableFeeKey(key)) {
6779
createToggableFeeInput(inputContainer, label, section, key, value);
6880
} else if (isFeeKey(key)) {
@@ -136,6 +148,86 @@ const createToggableFeeInput = (inputContainer, label, section, key, value) => {
136148
label.appendChild(questionIcon);
137149
};
138150

151+
const createExcessToleranceInput = (inputContainer, label, section, fixedValue, percentageValue) => {
152+
// Toggle: ON = Fixed mode, OFF = Percentage mode
153+
const toggle = document.createElement('input');
154+
toggle.type = 'checkbox';
155+
toggle.classList.add('form-check-input');
156+
toggle.style.marginRight = '10px';
157+
toggle.dataset.key = 'excessTolerance_isFixed';
158+
toggle.setAttribute('data-testid', `config-${section.id.replace('Config','')}-excessTolerance-toggle`);
159+
160+
const toggleLabel = document.createElement('span');
161+
toggleLabel.classList.add('toggle-label');
162+
toggleLabel.style.marginRight = '10px';
163+
toggleLabel.style.fontSize = '0.85em';
164+
toggleLabel.style.color = '#666';
165+
166+
const input = document.createElement('input');
167+
input.type = 'text';
168+
input.style.width = '40%';
169+
input.classList.add('form-control');
170+
input.dataset.key = 'excessTolerance_value';
171+
input.setAttribute('data-testid', `config-${section.id.replace('Config','')}-excessTolerance-input`);
172+
173+
// Store original values for reference
174+
input.dataset.originalFixedValue = fixedValue;
175+
input.dataset.originalPercentageValue = percentageValue;
176+
177+
// Determine initial mode based on which value is non-zero
178+
const fixedNum = parseFloat(fixedValue) || 0;
179+
const percentageNum = parseFloat(percentageValue) || 0;
180+
181+
if (fixedNum > 0) {
182+
// Fixed mode
183+
toggle.checked = true;
184+
toggleLabel.textContent = 'Fixed';
185+
input.value = weiToEther(fixedValue);
186+
input.placeholder = 'Enter amount in rBTC';
187+
} else if (percentageNum > 0) {
188+
// Percentage mode
189+
toggle.checked = false;
190+
toggleLabel.textContent = 'Percentage';
191+
input.value = percentageValue;
192+
input.placeholder = 'Enter percentage (0-100)';
193+
} else {
194+
// Default to percentage mode when both are 0
195+
toggle.checked = false;
196+
toggleLabel.textContent = 'Percentage';
197+
input.value = '0';
198+
input.placeholder = 'Enter percentage (0-100)';
199+
}
200+
201+
toggle.addEventListener('change', () => {
202+
if (toggle.checked) {
203+
// Fixed mode
204+
toggleLabel.textContent = 'Fixed';
205+
input.placeholder = 'Enter amount in rBTC';
206+
// Convert current value if possible, otherwise clear
207+
const currentVal = parseFloat(input.value);
208+
if (!isNaN(currentVal) && currentVal > 0) {
209+
input.value = '';
210+
} else {
211+
input.value = '0';
212+
}
213+
} else {
214+
// Percentage mode
215+
toggleLabel.textContent = 'Percentage';
216+
input.placeholder = 'Enter percentage (0-100)';
217+
input.value = '0';
218+
}
219+
setChanged(section.id);
220+
});
221+
222+
input.addEventListener('input', () => setChanged(section.id));
223+
224+
inputContainer.appendChild(toggle);
225+
inputContainer.appendChild(toggleLabel);
226+
inputContainer.appendChild(input);
227+
const questionIcon = createQuestionIcon(getTooltipText('excessTolerance'));
228+
label.appendChild(questionIcon);
229+
};
230+
139231
const createFeeInput = (inputContainer, label, section, key, value) => {
140232
const input = document.createElement('input');
141233
input.type = 'text';
@@ -196,7 +288,8 @@ const createQuestionIcon = (tooltipText) => {
196288

197289
const getDisplayLabel = (key) => {
198290
const labels = {
199-
maxLiquidity: 'Maximum Liquidity'
291+
maxLiquidity: 'Maximum Liquidity',
292+
excessToleranceFixed: 'Excess Tolerance'
200293
};
201294
return labels[key] || key;
202295
};
@@ -213,7 +306,8 @@ const getTooltipText = (key) => {
213306
bridgeTransactionMin: 'The amount of rBTC that needs to be gathered in peg out refunds before executing a native peg out.',
214307
fixedFee: 'A fixed fee charged for transactions.',
215308
feePercentage: 'A percentage fee charged based on the transaction amount.',
216-
maxLiquidity: 'The maximum liquidity (in rBTC) the provider is willing to offer. Must be a positive value with up to 18 decimal places.'
309+
maxLiquidity: 'The maximum liquidity (in rBTC) the provider is willing to offer. Must be a positive value with up to 18 decimal places.',
310+
excessTolerance: 'The excess tolerance for transactions. Toggle ON for a fixed amount (in rBTC), OFF for a percentage (0-100%).'
217311
};
218312
return tooltips[key] || 'No description available';
219313
};
@@ -333,7 +427,7 @@ const populateConfigSection = (sectionId, config) => {
333427
if (key === 'rskConfirmations' || key === 'btcConfirmations') {
334428
createConfirmationConfig(section, key, value);
335429
} else {
336-
createInput(section, key, value);
430+
createInput(section, key, value, config);
337431
}
338432
});
339433
};
@@ -483,6 +577,11 @@ function getRegularConfig(sectionId) {
483577
const key = input.dataset.key;
484578
let value;
485579

580+
// Skip excess tolerance value - handled separately
581+
if (key === 'excessTolerance_value') {
582+
return;
583+
}
584+
486585
if (input.disabled) {
487586
if (isfeePercentageKey(key)) {
488587
value = 0;
@@ -541,9 +640,46 @@ function getRegularConfig(sectionId) {
541640

542641
checkboxes.forEach(input => {
543642
const key = input.dataset.key;
544-
if (!key.endsWith('_enabled')) config[key] = input.checked;
643+
if (!key.endsWith('_enabled') && key !== 'excessTolerance_isFixed') {
644+
config[key] = input.checked;
645+
}
545646
});
546647

648+
// Handle excess tolerance separately
649+
const excessToleranceToggle = document.querySelector(`#${sectionId} input[data-key="excessTolerance_isFixed"]`);
650+
const excessToleranceInput = document.querySelector(`#${sectionId} input[data-key="excessTolerance_value"]`);
651+
652+
if (excessToleranceToggle && excessToleranceInput) {
653+
const isFixed = excessToleranceToggle.checked;
654+
const rawValue = excessToleranceInput.value.trim();
655+
656+
if (isFixed) {
657+
// Fixed mode - validate as non-negative number and convert to wei
658+
const validation = validateExcessToleranceFixed(rawValue);
659+
if (!validation.isValid) {
660+
showErrorToast(`"${sectionId}": ${validation.error}`);
661+
throw new Error(validation.error);
662+
}
663+
try {
664+
config.excessToleranceFixed = rawValue === '' || rawValue === '0' ? '0' : etherToWei(rawValue).toString();
665+
config.excessTolerancePercentage = 0;
666+
} catch (error) {
667+
showErrorToast(`"${sectionId}": Invalid input "${rawValue}" for excess tolerance fixed. Please enter a valid number.`);
668+
throw error;
669+
}
670+
} else {
671+
// Percentage mode - validate as 0-100 percentage
672+
const validation = validateExcessTolerancePercentage(rawValue);
673+
if (!validation.isValid) {
674+
showErrorToast(`"${sectionId}": ${validation.error}`);
675+
throw new Error(validation.error);
676+
}
677+
const percentageValue = parseFloat(rawValue) || 0;
678+
config.excessToleranceFixed = '0';
679+
config.excessTolerancePercentage = percentageValue;
680+
}
681+
}
682+
547683
return config;
548684
}
549685

0 commit comments

Comments
 (0)