Repository: dusk-network/rusk (language: rust)

Issue:
web-wallet: Align gas limit defaults with Rusk wallet
#### Summary
Currently the Web wallet sets a very high gas limit for any transaction of 2.9 DUSK. In the past, when staking operations were quite expensive, these numbers made sense. But since cost were [dramatically reduced](https://github.com/dusk-network/rusk/pull/2678), and we got more data on the cost of Phoenix and Moonlight transactions, we should lower the default to a more appropriate value. 

Why should we do this? Because it will reduce the minimal balance users need and if a transaction fails they will lose less funds, as all failed transactions incur the full gas limit provided. 

#### Possible solution design or implementation
Either align the gas limit defaults to what is being used in Rusk wallet: https://github.com/dusk-network/rusk/blob/master/rusk-wallet/src/gas.rs#L14L27

Or set a safe default, like 50M LUX.


Discussion / hints:

Fix patch:
diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md
index 5a2090add9..09b7fb56b2 100644
--- a/web-wallet/CHANGELOG.md
+++ b/web-wallet/CHANGELOG.md
@@ -11,12 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Add notice for stake maturity [#2981]
 - Add capability to maintain cache consistency in case of rejected blocks [#3156]
+- Add button to reset gas settings to their defaults [#3178]
 
 ### Changed
 
 - Update Transactions list design [#1922]
 - Reword "Staking" header to "Stake" [#3113]
 - Upgrade Migration Feature to Use Reown AppKit [#3129]
+- Update default gas settings values [#3178]
 
 ### Removed
 
@@ -24,6 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+- Fix dark mode button not being in sync with the stored value [#3178]
+- Fix `BigIntInput` properties not being reactive [#3178]
+
 ## [0.9.0] - 2024-12-03
 
 ### Added
@@ -434,6 +439,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 [#3129]: https://github.com/dusk-network/rusk/issues/3129
 [#3156]: https://github.com/dusk-network/rusk/issues/3156
 [#3160]: https://github.com/dusk-network/rusk/issues/3160
+[#3178]: https://github.com/dusk-network/rusk/issues/3178
 
 <!-- VERSIONS -->
 
diff --git a/web-wallet/README.md b/web-wallet/README.md
index 117137c3e9..b7f46bfbbd 100644
--- a/web-wallet/README.md
+++ b/web-wallet/README.md
@@ -39,7 +39,7 @@ VITE_FEATURE_MOONLIGHT_TRANSACTIONS=true
 VITE_FEATURE_STAKE=true
 VITE_FEATURE_TRANSFER=true
 VITE_FEATURE_TRANSACTION_HISTORY=true
-VITE_GAS_LIMIT_DEFAULT=2900000000
+VITE_GAS_LIMIT_DEFAULT=100000000
 VITE_GAS_LIMIT_LOWER=10000000
 VITE_GAS_LIMIT_UPPER=1000000000
 VITE_GAS_PRICE_DEFAULT=1
diff --git a/web-wallet/src/lib/components/BigIntInput/BigIntInput.svelte b/web-wallet/src/lib/components/BigIntInput/BigIntInput.svelte
index a5a4ac8a5e..55e5ec05b0 100644
--- a/web-wallet/src/lib/components/BigIntInput/BigIntInput.svelte
+++ b/web-wallet/src/lib/components/BigIntInput/BigIntInput.svelte
@@ -20,12 +20,13 @@
   /** @type {bigint} */
   export let value = 0n;
 
-  let isInvalidInput = false;
   let internalValue = value.toString();
 
+  /** @type {(v: bigint, min: bigint, max: bigint) => boolean} */
+  const isInvalidInput = (v, min, max) => !!(min > v || v > max);
+
   const checkValidity = () => {
-    isInvalidInput = !!(minValue > value || value > maxValue);
-    if (isInvalidInput) {
+    if (isInvalidInput(value, minValue, maxValue)) {
       dispatch("error", "Value exceeds limits");
     }
   };
@@ -40,14 +41,17 @@
     }
   }
 
-  $: inputClass = makeClassName({
-    "invalid-input": isInvalidInput,
-    [`${className}`]: true,
-  });
-
   onMount(() => {
     checkValidity();
   });
+
+  $: inputClass = makeClassName({
+    "invalid-input": isInvalidInput(value, minValue, maxValue),
+    [`${className}`]: true,
+  });
+  $: {
+    internalValue = value.toString();
+  }
 </script>
 
 <Textbox
diff --git a/web-wallet/src/lib/stores/gasStore.js b/web-wallet/src/lib/stores/gasStore.js
index 1a3f631372..0a048c683a 100644
--- a/web-wallet/src/lib/stores/gasStore.js
+++ b/web-wallet/src/lib/stores/gasStore.js
@@ -1,8 +1,8 @@
 import { readable } from "svelte/store";
 
 const gasLimits = {
-  gasLimitLower: BigInt(import.meta.env.VITE_GAS_LIMIT_LOWER ?? 10000000),
-  gasLimitUpper: BigInt(import.meta.env.VITE_GAS_LIMIT_UPPER ?? 1000000000),
+  gasLimitLower: BigInt(import.meta.env.VITE_GAS_LIMIT_LOWER ?? 10_000_000),
+  gasLimitUpper: BigInt(import.meta.env.VITE_GAS_LIMIT_UPPER ?? 1_000_000_000),
   gasPriceLower: BigInt(import.meta.env.VITE_GAS_PRICE_LOWER ?? 1),
 };
 
diff --git a/web-wallet/src/lib/stores/settingsStore.js b/web-wallet/src/lib/stores/settingsStore.js
index e2f522e7bb..4e80ff1df0 100644
--- a/web-wallet/src/lib/stores/settingsStore.js
+++ b/web-wallet/src/lib/stores/settingsStore.js
@@ -32,7 +32,7 @@ const initialState = {
   ...browserDefaults,
   currency: "USD",
   dashboardTransactionLimit: 5,
-  gasLimit: BigInt(import.meta.env.VITE_GAS_LIMIT_DEFAULT ?? 20000000),
+  gasLimit: BigInt(import.meta.env.VITE_GAS_LIMIT_DEFAULT ?? 100_000_000),
   gasPrice: BigInt(import.meta.env.VITE_GAS_PRICE_DEFAULT ?? 1),
   hideStakingNotice: false,
   userId: "",
@@ -71,9 +71,18 @@ const { set, subscribe, update } = settingsStore;
 // Reset store to initial state
 const reset = () => set(initialState);
 
+// Resets only gas settings to their defaults.
+const resetGasSettings = () =>
+  update((current) => ({
+    ...current,
+    gasLimit: initialState.gasLimit,
+    gasPrice: initialState.gasPrice,
+  }));
+
 /** @type {SettingsStore} */
 export default {
   reset,
+  resetGasSettings,
   set,
   subscribe,
   update,
diff --git a/web-wallet/src/lib/stores/stores.d.ts b/web-wallet/src/lib/stores/stores.d.ts
index 6b5bf83c8b..e325990b69 100644
--- a/web-wallet/src/lib/stores/stores.d.ts
+++ b/web-wallet/src/lib/stores/stores.d.ts
@@ -21,7 +21,10 @@ type SettingsStoreContent = {
   userId: string;
 };
 
-type SettingsStore = Writable<SettingsStoreContent> & { reset: () => void };
+type SettingsStore = Writable<SettingsStoreContent> & {
+  reset: () => void;
+  resetGasSettings: () => void;
+};
 
 type TransactionInfo =
   | {
diff --git a/web-wallet/src/routes/(app)/settings/+page.svelte b/web-wallet/src/routes/(app)/settings/+page.svelte
index 60315dbe02..c75de6a9a2 100644
--- a/web-wallet/src/routes/(app)/settings/+page.svelte
+++ b/web-wallet/src/routes/(app)/settings/+page.svelte
@@ -24,7 +24,9 @@
   import { logout } from "$lib/navigation";
   import loginInfoStorage from "$lib/services/loginInfoStorage";
 
-  const confirmResetMessage =
+  const confirmResetGasMessage =
+    "Are you sure you want to reset the gas settings to their defaults?";
+  const confirmResetWalletMessage =
     "Confirm you've saved your recovery phrase before resetting the wallet. Proceed?";
 
   const resetWallet = () =>
@@ -39,9 +41,16 @@
         resetError = err;
       });
 
+  function handleResetGasSettingsClick() {
+    // eslint-disable-next-line no-alert
+    if (confirm(confirmResetGasMessage)) {
+      settingsStore.resetGasSettings();
+    }
+  }
+
   function handleResetWalletClick() {
     // eslint-disable-next-line no-alert
-    if (confirm(confirmResetMessage)) {
+    if (confirm(confirmResetWalletMessage)) {
       resetError = null;
       resetWallet();
     }
@@ -50,16 +59,15 @@
   /** @type {(currency: { code: string, currency: string }) => SelectOption} */
   const currencyToOption = rename({ code: "value", currency: "label" });
   const currenciesToOptions = mapWith(currencyToOption);
-  const { currency, darkMode, gasLimit, gasPrice } = $settingsStore;
   const { gasLimitLower, gasLimitUpper, gasPriceLower } = $gasStore;
 
-  let isDarkMode = darkMode;
   let isGasValid = false;
 
   /** @type {Error | null} */
   let resetError = null;
 
   $: ({ syncStatus } = $walletStore);
+  $: ({ currency, darkMode, gasLimit, gasPrice } = $settingsStore);
 </script>
 
 <section class="settings">
@@ -76,18 +84,16 @@
       <div class="settings-group__multi-control-content">
         <GasControls
           on:gasSettings={(event) => {
-            isGasValid = areValidGasSettings(
-              event.detail.price,
-              event.detail.limit
-            );
+            const { limit, price } = event.detail;
 
-            if (isGasValid) {
-              settingsStore.update((store) => {
-                store.gasLimit = event.detail.limit;
-                store.gasPrice = event.detail.price;
+            isGasValid = areValidGasSettings(price, limit);
 
-                return store;
-              });
+            if (isGasValid) {
+              settingsStore.update((store) => ({
+                ...store,
+                gasLimit: limit,
+                gasPrice: price,
+              }));
             }
           }}
           limit={gasLimit}
@@ -96,6 +102,10 @@
           price={gasPrice}
           priceLower={gasPriceLower}
         />
+        <Button
+          on:click={handleResetGasSettingsClick}
+          text="Reset to defaults"
+        />
       </div>
     </article>
     <hr />
@@ -132,14 +142,14 @@
         >
           <span>Dark mode</span>
           <Switch
-            bind:value={isDarkMode}
-            on:change={() => {
+            on:change={(event) => {
               settingsStore.update((store) => {
-                store.darkMode = isDarkMode;
+                store.darkMode = event.detail;
 
                 return store;
               });
             }}
+            value={darkMode}
           />
         </label>
       </div>
diff --git a/web-wallet/vite.config.js b/web-wallet/vite.config.js
index 6f4b8babec..a008c54190 100644
--- a/web-wallet/vite.config.js
+++ b/web-wallet/vite.config.js
@@ -93,7 +93,7 @@ export default defineConfig(({ mode }) => {
         VITE_FEATURE_STAKE: "true",
         VITE_FEATURE_TRANSACTION_HISTORY: "true",
         VITE_FEATURE_TRANSFER: "true",
-        VITE_GAS_LIMIT_DEFAULT: "20000000",
+        VITE_GAS_LIMIT_DEFAULT: "100000000",
         VITE_GAS_LIMIT_LOWER: "10000000",
         VITE_GAS_LIMIT_UPPER: "1000000000",
         VITE_GAS_PRICE_DEFAULT: "1",


Test patch:
diff --git a/web-wallet/src/lib/components/__tests__/__snapshots__/GasFee.spec.js.snap b/web-wallet/src/lib/components/__tests__/__snapshots__/GasFee.spec.js.snap
index 6f8d22f8ed..cf25a23da2 100644
--- a/web-wallet/src/lib/components/__tests__/__snapshots__/GasFee.spec.js.snap
+++ b/web-wallet/src/lib/components/__tests__/__snapshots__/GasFee.spec.js.snap
@@ -23,7 +23,7 @@ exports[`GasFee > renders the GasFee component 1`] = `
       <span
         class="svelte-1c6b481"
       >
-        0.020000000
+        0.100000000
       </span>
     </div>
      
diff --git a/web-wallet/src/lib/components/__tests__/__snapshots__/GasSettings.spec.js.snap b/web-wallet/src/lib/components/__tests__/__snapshots__/GasSettings.spec.js.snap
index 13a93aaf29..fad53a6929 100644
--- a/web-wallet/src/lib/components/__tests__/__snapshots__/GasSettings.spec.js.snap
+++ b/web-wallet/src/lib/components/__tests__/__snapshots__/GasSettings.spec.js.snap
@@ -29,7 +29,7 @@ exports[`GasSettings > renders the GasSettings component closed 1`] = `
           <span
             class="svelte-1c6b481"
           >
-            0.020000000
+            0.100000000
           </span>
         </div>
          
@@ -93,7 +93,7 @@ exports[`GasSettings > renders the GasSettings component opened 1`] = `
           <span
             class="svelte-1c6b481"
           >
-            0.020000000
+            0.100000000
           </span>
         </div>
          
diff --git a/web-wallet/src/lib/stores/__tests__/settingsStore.spec.js b/web-wallet/src/lib/stores/__tests__/settingsStore.spec.js
index ce7db5504f..2bce7b43e4 100644
--- a/web-wallet/src/lib/stores/__tests__/settingsStore.spec.js
+++ b/web-wallet/src/lib/stores/__tests__/settingsStore.spec.js
@@ -126,6 +126,45 @@ describe("Settings store", () => {
 
       expect(get(settingsStore)).toBe(settingsStoreContent);
     });
+
+    it("should expose a method to reset the gas settings to their defaults", () => {
+      const newStateWithoutGas = {
+        currency: "FAKE CURRENCY",
+        darkMode: !settingsStoreContent.darkMode,
+        dashboardTransactionLimit:
+          settingsStoreContent.dashboardTransactionLimit + 1,
+        hideStakingNotice: !settingsStoreContent.hideStakingNotice,
+        language: "FAKE LANGUAGE",
+        userId: "FAKE USER ID",
+      };
+      const newState = {
+        ...newStateWithoutGas,
+        gasLimit: settingsStoreContent.gasLimit * 2n,
+        gasPrice: settingsStoreContent.gasPrice * 15n,
+      };
+      const expectedState = {
+        ...settingsStoreContent,
+        ...newStateWithoutGas,
+      };
+
+      settingsStore.set(newState);
+
+      expect(get(settingsStore)).toBe(newState);
+
+      settingsStore.resetGasSettings();
+
+      expect(get(settingsStore)).toStrictEqual(expectedState);
+      expect(
+        JSON.parse(
+          // @ts-ignore
+          localStorage.getItem(`${CONFIG.LOCAL_STORAGE_APP_KEY}-preferences`)
+        )
+      ).toStrictEqual({
+        ...expectedState,
+        gasLimit: `${expectedState.gasLimit}n`,
+        gasPrice: `${expectedState.gasPrice}n`,
+      });
+    });
   });
 
   describe("In a non browser environment", () => {
diff --git a/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/__snapshots__/page.spec.js.snap b/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/__snapshots__/page.spec.js.snap
index dac0135539..7e1b291405 100644
--- a/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/__snapshots__/page.spec.js.snap
+++ b/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/__snapshots__/page.spec.js.snap
@@ -198,7 +198,7 @@ exports[`Allocate > should render the allocation page 1`] = `
                     <span
                       class="svelte-1c6b481"
                     >
-                      0.020000000
+                      0.100000000
                     </span>
                   </div>
                    
diff --git a/web-wallet/src/routes/(app)/settings/__tests__/__snapshots__/page.spec.js.snap b/web-wallet/src/routes/(app)/settings/__tests__/__snapshots__/page.spec.js.snap
index 003678f335..f15dcd27f7 100644
--- a/web-wallet/src/routes/(app)/settings/__tests__/__snapshots__/page.spec.js.snap
+++ b/web-wallet/src/routes/(app)/settings/__tests__/__snapshots__/page.spec.js.snap
@@ -79,6 +79,17 @@ exports[`Settings > Resetting the wallet > should show an error if clearing loca
           />
           
         </label>
+         
+        <button
+          class="dusk-button dusk-button--type--button dusk-button--variant--primary dusk-button--size--default"
+          type="button"
+        >
+          <span
+            class="dusk-button__text"
+          >
+            Reset to defaults
+          </span>
+        </button>
       </div>
     </article>
      
@@ -631,6 +642,17 @@ exports[`Settings > should render the settings page displaying the status of the
           />
           
         </label>
+         
+        <button
+          class="dusk-button dusk-button--type--button dusk-button--variant--primary dusk-button--size--default"
+          type="button"
+        >
+          <span
+            class="dusk-button__text"
+          >
+            Reset to defaults
+          </span>
+        </button>
       </div>
     </article>
      
@@ -1165,6 +1187,17 @@ exports[`Settings > should render the settings page displaying the status of the
           />
           
         </label>
+         
+        <button
+          class="dusk-button dusk-button--type--button dusk-button--variant--primary dusk-button--size--default"
+          type="button"
+        >
+          <span
+            class="dusk-button__text"
+          >
+            Reset to defaults
+          </span>
+        </button>
       </div>
     </article>
      
diff --git a/web-wallet/src/routes/(app)/settings/__tests__/page.spec.js b/web-wallet/src/routes/(app)/settings/__tests__/page.spec.js
index 6f77c9fc2c..cda6be7324 100644
--- a/web-wallet/src/routes/(app)/settings/__tests__/page.spec.js
+++ b/web-wallet/src/routes/(app)/settings/__tests__/page.spec.js
@@ -8,10 +8,16 @@ import {
   vi,
 } from "vitest";
 import { act, cleanup, fireEvent, render } from "@testing-library/svelte";
+import { get } from "svelte/store";
 
 import mockedWalletStore from "../../../../__mocks__/mockedWalletStore";
 import * as navigation from "$lib/navigation";
-import { networkStore, settingsStore, walletStore } from "$lib/stores";
+import {
+  gasStore,
+  networkStore,
+  settingsStore,
+  walletStore,
+} from "$lib/stores";
 import loginInfoStorage from "$lib/services/loginInfoStorage";
 
 import Settings from "../+page.svelte";
@@ -77,9 +83,9 @@ describe("Settings", () => {
     expect(container.firstChild).toMatchSnapshot();
   });
 
-  it("should disable the reset button while a sync is in progress", async () => {
+  it("should disable the reset wallet button while a sync is in progress", async () => {
     const { getByRole } = render(Settings);
-    const resetButton = getByRole("button", { name: /reset/i });
+    const resetButton = getByRole("button", { name: /reset wallet/i });
 
     expect(resetButton).not.toHaveAttribute("disabled");
     expect(resetButton).toHaveAttribute("data-tooltip-disabled", "true");
@@ -96,19 +102,24 @@ describe("Settings", () => {
   });
 
   it('should disable the "Back" button if invalid gas limit or price are introduced', async () => {
+    const { gasLimitLower, gasLimitUpper, gasPriceLower } = get(gasStore);
     const { getByLabelText, getByRole } = render(Settings, {});
     const priceInput = asInput(getByLabelText(/price/i));
     const limitInput = asInput(getByLabelText(/limit/i));
     const backButton = getByRole("link", { name: /back/i });
 
-    await fireInput(priceInput, 30000000);
+    await fireInput(priceInput, String(gasPriceLower - 1n));
     expect(backButton).toHaveAttribute("aria-disabled", "true");
-    await fireInput(priceInput, 20000000);
+    await fireInput(priceInput, gasPriceLower.toString());
     expect(backButton).toHaveAttribute("aria-disabled", "false");
 
-    await fireInput(limitInput, 3000000000);
+    await fireInput(limitInput, String(gasLimitLower - 1n));
+    expect(backButton).toHaveAttribute("aria-disabled", "true");
+    await fireInput(limitInput, gasLimitLower.toString());
+    expect(backButton).toHaveAttribute("aria-disabled", "false");
+    await fireInput(limitInput, String(gasLimitUpper + 1n));
     expect(backButton).toHaveAttribute("aria-disabled", "true");
-    await fireInput(limitInput, 20000000);
+    await fireInput(limitInput, gasLimitUpper.toString());
     expect(backButton).toHaveAttribute("aria-disabled", "false");
   });
 
@@ -146,9 +157,9 @@ describe("Settings", () => {
       loginInfoStorageSpy.mockRestore();
     });
 
-    it("should clear local data, settings, and login info before logging out the user if the reset button is clicked and the user confirms the operation", async () => {
+    it("should clear local data, settings, and login info before logging out the user if the reset wallet button is clicked and the user confirms the operation", async () => {
       const { getByRole } = render(Settings);
-      const resetButton = getByRole("button", { name: /reset/i });
+      const resetButton = getByRole("button", { name: /reset wallet/i });
 
       await fireEvent.click(resetButton);
 
@@ -167,7 +178,7 @@ describe("Settings", () => {
       confirmSpy.mockReturnValueOnce(false);
 
       const { getByRole } = render(Settings);
-      const resetButton = getByRole("button", { name: /reset/i });
+      const resetButton = getByRole("button", { name: /reset wallet/i });
 
       await fireEvent.click(resetButton);
 
@@ -185,7 +196,7 @@ describe("Settings", () => {
       clearDataSpy.mockRejectedValueOnce(new Error("Clear data error"));
 
       const { container, getByRole } = render(Settings);
-      const resetButton = getByRole("button", { name: /reset/i });
+      const resetButton = getByRole("button", { name: /reset wallet/i });
 
       await fireEvent.click(resetButton);