Skip to content

Commit

Permalink
web-wallet: Add address validation (Transfer flow)
Browse files Browse the repository at this point in the history
Resolves #1377
  • Loading branch information
nortonandreev committed Feb 22, 2024
1 parent b790655 commit 9eb65d5
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 12 deletions.
10 changes: 5 additions & 5 deletions web-wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add visible version, commit hash and build date [#1441]
- Add Address validation (Transfer flow) [#1377]

### Changed

- Change Get Quote API Endpoint to env variable [#1311]

### Removed

- Remove the use of `checkValidity()` in Send and Stake flow amounts validity checks [#1391]

### Fixed

- Fix typo in routes/welcome/__tests__/page.spec.js [#1445]
- Fix typo in routes/welcome/tests/page.spec.js [#1445]

## [0.2.1] - 2024-02-20

Expand Down Expand Up @@ -50,7 +53,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Change Get Quote API Endpoint to env variable [#1311]
- Change Holdings component design [#1361]
- Change `fiatCurrency`, `locale`, `tokenCurrency`, `token` to required properties in Balance component [#1323]
- Change `package.json` fields to reflect repo change [#1367]
Expand Down Expand Up @@ -96,6 +98,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#1367]: https://github.com/dusk-network/rusk/issues/1367
[#1353]: https://github.com/dusk-network/rusk/issues/1353
[#1343]: https://github.com/dusk-network/rusk/issues/1343
[#1377]: https://github.com/dusk-network/rusk/issues/1377
[#1403]: https://github.com/dusk-network/rusk/issues/1403
[#1412]: https://github.com/dusk-network/rusk/issues/1412
[#1410]: https://github.com/dusk-network/rusk/issues/1410
Expand All @@ -107,11 +110,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#1423]: https://github.com/dusk-network/rusk/issues/1423
[#1391]: https://github.com/dusk-network/rusk/issues/1391
[#1417]: https://github.com/dusk-network/rusk/issues/1417
<<<<<<< HEAD
[#1445]: https://github.com/dusk-network/rusk/issues/1445
=======
[#1441]: https://github.com/dusk-network/rusk/issues/1441
>>>>>>> e0c2a641 (web-wallet: Add visible version, commit hash and build date)

<!-- VERSIONS -->
[Unreleased]: https://github.com/dusk-network/rusk/tree/master/web-wallet
Expand Down
12 changes: 10 additions & 2 deletions web-wallet/src/lib/components/Send/Send.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { deductLuxFeeFrom } from "$lib/contracts";
import { duskToLux, luxToDusk } from "$lib/dusk/currency";
import { validateAddress } from "$lib/dusk/string";
import { logo } from "$lib/dusk/icons";
import {
AnchorButton,
Expand Down Expand Up @@ -79,6 +80,8 @@
$: totalLuxFee = luxFee + duskToLux(amount);
$: isFeeWithinLimit = totalLuxFee <= duskToLux(spendable);
$: isNextButtonDisabled = !(isAmountValid && isValidGas && isFeeWithinLimit);
$: addressValidationResult = validateAddress(address);
</script>

<div class="operation">
Expand Down Expand Up @@ -148,7 +151,7 @@
<WizardStep
step={1}
{key}
nextButton={{ disabled: address.length === 0 }}
nextButton={{ disabled: !addressValidationResult.isValid }}
>
<div in:fade|global class="operation__send">
<ContractStatusesList items={statuses}/>
Expand All @@ -166,7 +169,8 @@
/>
</div>
<Textbox
className="operation__send-address"
className="operation__send-address
{!addressValidationResult.isValid ? "operation__send-address--invalid" : ""}"
type="multiline"
bind:value={address}
/>
Expand Down Expand Up @@ -326,4 +330,8 @@
.review-transaction__value {
font-weight: bold;
}
:global(.operation__send-address--invalid) {
color: var(--error-color);
}
</style>
23 changes: 21 additions & 2 deletions web-wallet/src/lib/components/__tests__/Send.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ describe("Send", () => {
value: "1,000.000000000"
}]
};
const address =

const invalidAddress =
"aB5rL7yC2zK9eV3xH1gQ6fP4jD8sM0iU2oX7wG9nZ8lT3hU4jP5mK8nS6qJ3wF4aA9bB2cC5dD8eE7";

const address =
"47jNTgAhzn9KCKF3msCfvKg3k1P1QpPCLZ3HG3AoNp87sQ5WNS3QyjckYHWeuXqW7uvLmbKgejpP8Xkcip89vnMM";

afterEach(() => {
cleanup();
baseProps.execute.mockClear();
Expand Down Expand Up @@ -117,7 +121,22 @@ describe("Send", () => {
expect(nextButton).toBeDisabled();
});

it("should enable the next button if the user inputs an address", async () => {
it("should disable the next button if the address is invalid empty", async () => {
const { getByRole } = render(Send, baseProps);

await fireEvent.click(getByRole("button", { name: "Next" }));

const nextButton = getByRole("button", { name: "Next" });
const addressInput = getByRole("textbox");

await fireEvent.input(addressInput, { target: { value: invalidAddress } });

expect(addressInput).toHaveValue(invalidAddress);

expect(nextButton).toBeDisabled();
});

it("should enable the next button if the user inputs a valid address", async () => {
const { getByRole } = render(Send, baseProps);

await fireEvent.click(getByRole("button", { name: "Next" }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ exports[`Send > Address step > should render the Send component Address step 1`]
</div>
<textarea
class="dusk-textbox dusk-textbox-multiline operation__send-address"
class="dusk-textbox dusk-textbox-multiline operation__send-address
operation__send-address--invalid"
/>
<!--&lt;Textbox&gt;-->
Expand Down Expand Up @@ -560,7 +561,7 @@ exports[`Send > Review step > should render the Send component Review step 1`] =
<span
class="s-m9vn8aRVZTU1"
>
aB5rL7yC2zK9eV3xH1gQ6fP4jD8sM0iU2oX7wG9nZ8lT3hU4jP5mK8nS6qJ3wF4aA9bB2cC5dD8eE7
47jNTgAhzn9KCKF3msCfvKg3k1P1QpPCLZ3HG3AoNp87sQ5WNS3QyjckYHWeuXqW7uvLmbKgejpP8Xkcip89vnMM
</span>
</dd>
</dl>
Expand Down
2 changes: 1 addition & 1 deletion web-wallet/src/lib/dusk/components/Textbox/Textbox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
inputElement?.select();
}
const classes = makeClassName([
$: classes = makeClassName([
"dusk-textbox",
`dusk-textbox-${type}`,
className
Expand Down
45 changes: 45 additions & 0 deletions web-wallet/src/lib/dusk/string/__tests__/validateAddress.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { validateAddress } from "..";
import { describe, expect, it } from "vitest";

describe("validateAddress", () => {
const validAddresses = [
"47jNTgAhzn9KCKF3msCfvKg3k1P1QpPCLZ3HG3AoNp87sQ5WNS3QyjckYHWeuXqW7uvLmbKgejpP8Xkcip89vnMM",
"4xwKPC9UMvketmoNkDvyaJufcTZWmNn8giB8xWTf3Qk8nFkRW81nTVwSdGPcbomzHThPuoXsdFzrzwiMar6BEfdw",
"5kB6VBePF8eFhFVjLwM1xrEL6yGBm1uDsoWyRjdqDQ2nNz8nECAsRh3MZiM6uEo6WmukqyKzzCK9B5rcPTnjZQgt",
"4LaS4bWzFQtvxZ7frUaXbfm3xsbnHHYwNkGnLqqpmWPYQeSfbAPy7N4Md8gk5gHn9f4wxNSNyFJuyxcnXPSWTRMd",
"gMxrVEH5aW7XuQiXN2Pm2YRLHyCNmokmBb1VzjcmcQg7gzmxstPnozdt7SvvMKLP71BadPsa5jmoWFc2WzWDYPo"
];

const invalidAddresses = [
// Invalid Base58
"InvalidKey12345",

// Too short
"4LaS4bWzFQtvxZ7frUaXbfm3xsbnHHYwNkGnLqqpmWPY",

// Too long
"5kB6VBePF8eFhFVjLwM1xrEL6yGBm1uDsoWyRjdqDQ2nNz8nECAsRh3MZiM6uEo6WmukqyKzzCK9B5rcPTnjZQgtXXXXXXXX",

// Empty string
"",

// Contains an invalid character
"47jNTgAhzn9KCKF3msCfvKg3k1P1QpPCLZ3HG3AoNp87sQ5WNS3QyjckYHWeuXqW7uvLmbKgejpP8Xkcip89vnM!"
];

it("passes when supplied with a valid address", () => {
for (const address of validAddresses) {
const result = validateAddress(address);

expect(result.isValid).toBe(true);
}
});

it("fails when supplied with an invalid address", () => {
for (const address of invalidAddresses) {
const result = validateAddress(address);

expect(result.isValid).toBe(false);
}
});
});
1 change: 1 addition & 0 deletions web-wallet/src/lib/dusk/string/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as calculateAdaptiveCharCount } from "./calculateAdaptiveCharCo
export { default as hexStringToBytes } from "./hexStringToBytes";
export { default as makeClassName } from "./makeClassName";
export { default as middleEllipsis } from "./middleEllipsis";
export { default as validateAddress } from "./validateAddress";
20 changes: 20 additions & 0 deletions web-wallet/src/lib/dusk/string/validateAddress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Validates a Dusk address, with feedback on failure or success.
* @param {String} address The public spent key to validate.
* @returns {{isValid: boolean, reason: string}} An object with two keys:
* - `isValid` {Boolean} - true if the address is valid, false if invalid.
* - `reason` {String} - describes why the address is invalid or confirms if it is valid.
*/
export default function validateAddress (address) {
const regex = /[^\w]/g;

if (address.length < 87 || address.length > 88) {
return { isValid: false, reason: "Invalid length. Addresses must be 87 or 88 characters long." };
}

if (address.search(regex) !== -1) {
return { isValid: false, reason: "Invalid character set. Address contains forbidden characters." };
}

return { isValid: true, reason: "Valid address." };
}

0 comments on commit 9eb65d5

Please sign in to comment.