Skip to content

Commit b1816ba

Browse files
authored
Check recipient addresses for ENS confusables in batch send (#640)
* Refactor isValidId / confusables check to include batch send * Cleaned up warning vs error layout * Fix batch send warning font and warning/error overlap at various widths * Minor mobile layout adjustment for warning message on batch send
1 parent ffdbb98 commit b1816ba

File tree

2 files changed

+102
-28
lines changed

2 files changed

+102
-28
lines changed

frontend/src/css/app.sass

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ p, div
109109
align-items: center
110110
justify-content: center
111111

112+
.batch-send-warning-container
113+
color: $warning
114+
line-height: normal
115+
margin: 0px 10px
116+
width: 29%
117+
112118
.batch-send-label
113119
width: 20px
114120
margin: 0px 10px 18px 10px

frontend/src/pages/AccountSend.vue

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
narrow-indicator
2323
v-if="batchSendIsSupported"
2424
>
25-
<q-tab name="send" :disable="isSending" :label="$t('Send.single-send')" />
26-
<q-tab name="batch-send" :disable="isSending" :label="$t('Send.batch-send')" />
25+
<q-tab name="send" :disable="isSending" :label="$t('Send.single-send')" @click="handleTabClick()" />
26+
<q-tab name="batch-send" :disable="isSending" :label="$t('Send.batch-send')" @click="handleTabClick()" />
2727
</q-tabs>
2828

2929
<q-tab-panels v-model="tab" animated>
@@ -114,9 +114,9 @@
114114
:debounce="500"
115115
:disable="isSending"
116116
placeholder="vitalik.eth"
117-
lazy-rules
117+
:lazy-rules="false"
118118
:hideBottomSpace="true"
119-
:rules="isValidId"
119+
:rules="(value: string) => isValidId(value, undefined)"
120120
ref="recipientIdBaseInputRef"
121121
/>
122122
<div class="flex row text-caption warning-container q-pb-sm" v-if="recipientIdWarning">
@@ -314,10 +314,19 @@
314314
:debounce="500"
315315
:disable="isSending"
316316
placeholder="vitalik.eth"
317-
lazy-rules
318-
:rules="isValidId"
317+
:lazy-rules="false"
318+
:rules="(value: string) => isValidId(value, index)"
319319
/>
320-
320+
<div class="text-caption warning-container" v-if="batchSends[index].warning">
321+
<br /><br />
322+
{{ batchSends[index].warning }}
323+
</div>
324+
<div
325+
class="text-caption warning-container"
326+
v-if="batchSends[index].validationError && !batchSends[index].warning"
327+
>
328+
<br /><br />
329+
</div>
321330
<!-- Token -->
322331
<div>{{ $t('Send.select-token') }}</div>
323332

@@ -348,14 +357,12 @@
348357
<q-separator />
349358
</q-card>
350359
</div>
351-
<br /><br />
352360
</div>
353361

354362
<!-- Desktop Layout -->
355-
<q-form v-else>
363+
<q-form v-else style="display: flex; flex-direction: column">
356364
<div v-for="(Send, index) in batchSends" :key="index">
357365
<!-- Identifier -->
358-
359366
<div class="batch-send">
360367
<p class="batch-send-label text-grey">{{ index + 1 }}</p>
361368
<div class="input-container-address">
@@ -365,8 +372,8 @@
365372
:disable="isSending"
366373
placeholder="vitalik.eth"
367374
:label="$t('Send.receiver-addr-ens')"
368-
lazy-rules
369-
:rules="isValidId"
375+
:lazy-rules="false"
376+
:rules="(value: string) => isValidId(value, index)"
370377
/>
371378
</div>
372379

@@ -407,6 +414,19 @@
407414
icon="fas fa-times"
408415
/>
409416
</div>
417+
<div class="batch-send" v-if="batchSends[index].validationError">
418+
<div><br /></div>
419+
</div>
420+
<div v-for="n in numberOfErrorOrWarningBreaksNeeded" :key="n">
421+
<br v-if="batchSends[index].validationError" />
422+
</div>
423+
<div class="batch-send" v-if="batchSends[index].warning">
424+
<div class="text-caption batch-send-warning-container">
425+
{{ batchSends[index].warning }}
426+
</div>
427+
<p class="input-container-token"></p>
428+
<p class="input-container-token"></p>
429+
</div>
410430
</div>
411431
</q-form>
412432
<!-- Toll + summary details -->
@@ -449,7 +469,6 @@
449469
</tbody>
450470
</q-markup-table>
451471
</div>
452-
453472
<!-- Send button -->
454473
<div class="batch-send-buttons">
455474
<base-button
@@ -465,7 +484,6 @@
465484
:flat="true"
466485
:label="$t('Send.add-send')"
467486
/>
468-
469487
<div>
470488
<router-link :class="{ 'no-text-decoration': true, 'dark-toggle': true }" :to="{ name: 'sent' }">
471489
<div class="row items-center justify-center q-pa-sm link-container rounded-borders">
@@ -522,6 +540,8 @@ interface BatchSendData {
522540
receiver: string | undefined;
523541
token: TokenInfoExtended | null | undefined;
524542
amount: string;
543+
warning: string;
544+
validationError: boolean;
525545
}
526546
527547
function useSendForm() {
@@ -573,8 +593,10 @@ function useSendForm() {
573593
// Batch Send Form Parameters
574594
const batchSends = ref<BatchSendData[]>([]);
575595
const tab = ref('send');
596+
const previousTabChecked = ref('send');
576597
const batchSendSupportedChains = [1, 10, 100, 137, 42161, 11155111];
577598
const batchSendIsSupported = ref(false);
599+
const numberOfErrorOrWarningBreaksNeeded = ref(0);
578600
579601
// Computed form parameters.
580602
const showAdvancedWarning = computed(() => advancedAcknowledged.value === false && useNormalPubKey.value === true);
@@ -729,15 +751,15 @@ function useSendForm() {
729751
730752
let validatedBatchSendForm = true;
731753
const isValidRecipientPromises: Promise<boolean | string>[] = [];
732-
for (const batchSend of batchSends.value) {
754+
batchSends.value.forEach((batchSend, index) => {
733755
if (validatedBatchSendForm) {
734756
const { token, amount, receiver } = batchSend;
735757
const isValidAmount = Boolean(amount) && isValidTokenAmount(amount, token);
736758
const isValidToken = Boolean(token);
737-
isValidRecipientPromises.push(isValidId(receiver));
759+
isValidRecipientPromises.push(isValidId(receiver, index));
738760
if (isValidAmount !== true || !receiver || !isValidToken) validatedBatchSendForm = false;
739761
}
740-
}
762+
});
741763
if (validatedBatchSendForm) {
742764
await Promise.all(isValidRecipientPromises).then((results) => {
743765
for (const result of results) {
@@ -753,8 +775,8 @@ function useSendForm() {
753775
onMounted(async () => {
754776
await setPaymentLinkData();
755777
batchSends.value.push(
756-
{ id: 1, receiver: '', token: NATIVE_TOKEN.value, amount: '' },
757-
{ id: 2, receiver: '', token: NATIVE_TOKEN.value, amount: '' }
778+
{ id: 1, receiver: '', token: NATIVE_TOKEN.value, amount: '', warning: '', validationError: false },
779+
{ id: 2, receiver: '', token: NATIVE_TOKEN.value, amount: '', warning: '', validationError: false }
758780
);
759781
const chainId = BigNumber.from(currentChain.value?.chainId || 0).toNumber();
760782
batchSendIsSupported.value = batchSendSupportedChains.includes(chainId);
@@ -795,6 +817,8 @@ function useSendForm() {
795817
receiver: '',
796818
token: NATIVE_TOKEN.value,
797819
amount: '',
820+
warning: '',
821+
validationError: false,
798822
});
799823
}
800824
@@ -803,19 +827,57 @@ function useSendForm() {
803827
batchSends.value.splice(index, 1);
804828
}
805829
830+
function calculateErrorOrWarningBreaks() {
831+
// Width setpoints for extra break(s) needed after error (or before warning) message on the batch send page
832+
// These are eeded to prevent the error message from overlapping with the warning field field,
833+
// as page width and layout dictate the number of error lines (and therefore) breaks needed.
834+
const warningWidths = [1140, 955, 790, 710, 685, 645, 630];
835+
numberOfErrorOrWarningBreaksNeeded.value = 0;
836+
for (const width of warningWidths) {
837+
if (window.innerWidth < width) numberOfErrorOrWarningBreaksNeeded.value += 1;
838+
}
839+
}
840+
841+
function setWarning(warning: string, index: number | undefined) {
842+
if (index !== undefined) {
843+
batchSends.value[index].warning = warning;
844+
} else {
845+
recipientIdWarning.value = warning;
846+
}
847+
}
848+
849+
function handleTabClick() {
850+
setWarning('', undefined);
851+
for (let i = 0; i < batchSends.value.length; i++) {
852+
setWarning('', i);
853+
}
854+
}
855+
806856
// Validators
807-
async function isValidId(val: string | undefined) {
857+
async function isValidId(val: string | undefined, index: number | undefined) {
858+
// Check if confusable chars in string, throws with warning if so
859+
checkConfusables(val, index);
860+
808861
// Return true if nothing is provided
809-
checkConfusables();
810862
if (!val) return true;
811863
812864
// Check if recipient ID is valid
813865
try {
814866
await umbraUtils.lookupRecipient(val, provider.value as Provider, {
815867
advanced: shouldUseNormalPubKey.value,
816868
});
869+
if (index !== undefined) {
870+
batchSends.value[index].validationError = false;
871+
}
817872
return true;
818873
} catch (e: unknown) {
874+
if (index !== undefined) {
875+
batchSends.value[index].validationError = true;
876+
if (batchSends.value[index].warning === '') {
877+
// checkConfusables didn't find a warning but we have an error, we may need breaks depending on page width
878+
calculateErrorOrWarningBreaks();
879+
}
880+
}
819881
const toSentenceCase = (str: string) => str[0].toUpperCase() + str.slice(1);
820882
if (e instanceof Error && e.message) return toSentenceCase(e.message);
821883
if ((e as { reason: string }).reason) return toSentenceCase((e as { reason: string }).reason);
@@ -1158,8 +1220,8 @@ function useSendForm() {
11581220
11591221
function resetBatchSendForm() {
11601222
batchSends.value = [
1161-
{ id: 1, receiver: '', token: NATIVE_TOKEN.value, amount: '' },
1162-
{ id: 2, receiver: '', token: NATIVE_TOKEN.value, amount: '' },
1223+
{ id: 1, receiver: '', token: NATIVE_TOKEN.value, amount: '', warning: '', validationError: false },
1224+
{ id: 2, receiver: '', token: NATIVE_TOKEN.value, amount: '', warning: '', validationError: false },
11631225
];
11641226
}
11651227
@@ -1206,16 +1268,19 @@ function useSendForm() {
12061268
);
12071269
}
12081270
1209-
function checkConfusables() {
1210-
const recipientIdString = recipientId.value || '';
1271+
function checkConfusables(recipientIdString: string | undefined, index: number | undefined) {
1272+
if (previousTabChecked.value !== tab.value) {
1273+
previousTabChecked.value = tab.value;
1274+
return;
1275+
}
12111276
try {
12121277
if (recipientIdString && recipientIdString.endsWith('eth')) {
12131278
assertValidEnsName(recipientIdString);
12141279
}
1215-
recipientIdWarning.value = undefined;
1280+
setWarning('', index); // clear warning
12161281
} catch (e) {
1217-
if (e instanceof Error) {
1218-
recipientIdWarning.value = e.message;
1282+
if (e instanceof Error && e.message) {
1283+
setWarning(e.message, index);
12191284
}
12201285
}
12211286
}
@@ -1233,6 +1298,7 @@ function useSendForm() {
12331298
chainId,
12341299
checkConfusables,
12351300
currentChain,
1301+
handleTabClick,
12361302
humanAmount,
12371303
humanAmountBaseInputRef,
12381304
humanToll,
@@ -1246,6 +1312,7 @@ function useSendForm() {
12461312
isValidTokenAmount,
12471313
isValidBatchSendAmount,
12481314
NATIVE_TOKEN,
1315+
numberOfErrorOrWarningBreaksNeeded,
12491316
onFormSubmit,
12501317
onBatchSendFormSubmit,
12511318
paymentLinkParams,
@@ -1259,6 +1326,7 @@ function useSendForm() {
12591326
sendingString,
12601327
sendMax,
12611328
setHumanAmountMax,
1329+
setWarning,
12621330
showAdvancedSendWarning,
12631331
showAdvancedWarning,
12641332
summaryAmount,

0 commit comments

Comments
 (0)