Skip to content

feat: Safari support#125

Open
kvhnuke wants to merge 228 commits intodevelopfrom
feat/safari-support
Open

feat: Safari support#125
kvhnuke wants to merge 228 commits intodevelopfrom
feat/safari-support

Conversation

@kvhnuke
Copy link
Contributor

@kvhnuke kvhnuke commented Aug 17, 2022

Summary by CodeRabbit

  • New Features

    • Safari support: buildable Safari extension and macOS app with Safari-integrated UI.
    • New Safari build option and macOS deployment pipeline.
  • Bug Fixes

    • Hide unsupported features on Safari (DApps link, Buy/Sell, Swap) for a smoother experience.
    • Hardware wallet options now appear only in supported browsers.
    • Onboarding reliably closes the active tab; Safari action window kept alive when needed.
  • Chores

    • CI/CD improvements: unified workflows, caching, artifact uploads, and automated file scanning with PR summaries.

jack-dev-crypto and others added 28 commits January 18, 2026 03:46
fix(signers): rename test suite from Ethereum to Bitcoin signing
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (19)
packages/extension/src/providers/ethereum/ui/eth-verify-transaction.vue (2)

285-387: ⚠️ Potential issue | 🟠 Major

Prevent the loading screen from getting stuck on early failures.

If network.value.api(), decodeTx, or other awaited calls throw before getGasCosts(), the current finally never runs. That leaves isLoading true and errorMsg empty.

✅ Suggested structure
-onBeforeMount(async () => {
-  const { Request, options } = await windowPromise;
+onBeforeMount(async () => {
+  try {
+    const { Request, options } = await windowPromise;
     network.value = (await getNetworkByName(
       Request.value.params![2],
     )) as EvmNetwork;
@@
-  await decodeTx(
+    const decoded = await decodeTx(
       Request.value.params![0] as EthereumTransaction,
       network.value as EvmNetwork,
-  ).then(decoded => {
+    );
+    {
       const realToAddress = decoded.tokenTo || decoded.toAddress || '';
@@
-    decodedTx.value = decoded;
+      decodedTx.value = decoded;
       fiatValue.value = new BigNumber(
         fromBase(decoded.tokenValue, decoded.tokenDecimals),
       )
         .times(decoded.currentPriceUSD)
         .toFixed(2);
-  });
+    }
@@
-  await tx
-    .getGasCosts()
-    .then(async gasvals => {
+    const gasvals = await tx.getGasCosts();
+    {
       let nativeVal = '0';
@@
-      selectedFee.value = GasPriceTypes.REGULAR;
-    })
-    .catch(e => {
-      errorMsg.value = e.message;
-    })
-    .finally(() => {
-      isLoading.value = false;
-    });
+      selectedFee.value = GasPriceTypes.REGULAR;
+    }
+  } catch (e: any) {
+    errorMsg.value = e?.message ?? 'Failed to load transaction';
+  } finally {
+    isLoading.value = false;
+  }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/eth-verify-transaction.vue`
around lines 285 - 387, The loading flag is only cleared in the .finally() of
tx.getGasCosts(), so early throws from network.value.api(), decodeTx(), or other
awaits leave isLoading true and errorMsg empty; wrap the entire onBeforeMount
async body in a try/catch/finally (or add a top-level try around the awaited
calls inside onBeforeMount) so any thrown error is caught and sets
errorMsg.value = e.message and the finally always sets isLoading.value = false;
target the onBeforeMount handler and ensure catches cover network.value.api(),
decodeTx(...), and Transaction.getGasCosts() failures, leaving isLoading and
errorMsg updated accordingly.

290-293: ⚠️ Potential issue | 🟠 Major

Fix the default fee selection condition.

Line 290 uses network.value.name === NetworkNames.Ethereum || NetworkNames.Binance, which always evaluates truthy and forces REGULAR for all networks (and for failure paths where later overrides don’t run).

✅ Proposed fix
-  selectedFee.value =
-    network.value.name === NetworkNames.Ethereum || NetworkNames.Binance
-      ? GasPriceTypes.REGULAR
-      : GasPriceTypes.ECONOMY;
+  selectedFee.value =
+    network.value.name === NetworkNames.Ethereum ||
+    network.value.name === NetworkNames.Binance
+      ? GasPriceTypes.REGULAR
+      : GasPriceTypes.ECONOMY;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/eth-verify-transaction.vue`
around lines 290 - 293, The condition that sets selectedFee.value wrongly uses
"network.value.name === NetworkNames.Ethereum || NetworkNames.Binance" which
always evaluates truthy; change it to explicitly compare network.value.name to
both constants (e.g., network.value.name === NetworkNames.Ethereum ||
network.value.name === NetworkNames.Binance) or use an inclusion check (e.g.,
[NetworkNames.Ethereum, NetworkNames.Binance].includes(network.value.name)) so
that selectedFee.value is REGULAR only for those two networks and ECONOMY
otherwise; update the assignment around selectedFee.value and keep
GasPriceTypes.REGULAR / GasPriceTypes.ECONOMY as the branches.
packages/extension/src/providers/solana/ui/send-transaction/index.vue (1)

455-475: ⚠️ Potential issue | 🟠 Major

Missing .catch() on fetchAssets — user gets stuck on loading spinner if the network call fails.

If props.network.getAllTokens() rejects, isLoadingAssets stays true permanently and the user sees the loading spinner forever with no way to proceed or retry.

🐛 Proposed fix
   return props.network.getAllTokens(addressFrom.value).then(allAssets => {
     accountAssets.value = allAssets as SOLToken[];
     const hasParamData =
       isSendToken.value && tokenParamData && tokenParamData.contract;
     if (hasParamData) {
       const selectedToken = accountAssets.value.find(
         asset => asset.contract === tokenParamData.contract,
       );
       if (selectedToken) {
         selectedAsset.value = selectedToken as SOLToken;
       }
     } else {
       selectedAsset.value = allAssets[0] as SOLToken;
     }
     isLoadingAssets.value = false;
+  }).catch((err) => {
+    console.error('Failed to fetch assets:', err);
+    isLoadingAssets.value = false;
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/solana/ui/send-transaction/index.vue` around
lines 455 - 475, The fetchAssets function currently returns
props.network.getAllTokens(...) without handling rejection, so
isLoadingAssets.value can remain true; wrap the network call with a .catch
handler (on the Promise returned by props.network.getAllTokens) to set
isLoadingAssets.value = false, reset accountAssets.value and selectedAsset.value
(e.g., to [] and loadingAsset), and optionally log the error; ensure the .catch
is chained to the same Promise returned by fetchAssets (or use try/catch if
converted to async) so any rejection clears the loading state and allows retry.
packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue (1)

2-17: ⚠️ Potential issue | 🟠 Major

Use a semantic button to keep keyboard access intact.

<a> without href is not focusable, so keyboard users can't open the token picker. Replace it with <button type="button"> and add focus-visible styling.

Suggested fix (semantic button + focus-visible)
-  <a class="send-token-select" `@click`="emit('update:toggleTokenSelect')">
+  <button
+    type="button"
+    class="send-token-select"
+    `@click`="emit('update:toggleTokenSelect')"
+  >
     <div class="send-token-select__image">
       <img :src="token.icon" alt="" />
     </div>
     <div class="send-token-select__info">
       <h5>{{ token.name }}</h5>
       <p>
         {{ balance ? $filters.formatFloatingPointValue(balance).value : '~' }}
         <span>{{ token.symbol }}</span>
       </p>
     </div>

     <div class="send-token-select__arrow">
       <switch-arrow />
     </div>
-  </a>
+  </button>
 .send-token-select {
   height: 60px;
   background: `@white`;
   margin: 0 24px 8px 24px;
   box-sizing: border-box;
   border: 1.5px solid rgba(98, 126, 234, 0.15);
   border-radius: 14px;
   width: calc(~'100% - 48px');
   padding: 12px 16px;
   display: flex;
   justify-content: flex-start;
   align-items: center;
   flex-direction: row;
   position: relative;
   cursor: pointer;
   text-decoration: none;
   transition: all 200ms ease-in-out;
   box-shadow: 0 2px 8px rgba(98, 126, 234, 0.06);
+  font: inherit;
+  color: inherit;
+
+  &:focus-visible {
+    outline: 2px solid rgba(98, 126, 234, 0.6);
+    outline-offset: 2px;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue`
around lines 2 - 17, The anchor element with class "send-token-select" is
non-semantic and not keyboard-focusable; replace the outer <a> with a <button
type="button"> (preserving the `@click` handler that triggers
emit('update:toggleTokenSelect')) so the token picker is keyboard-accessible,
keep inner structure (send-token-select__image, __info, __arrow and the
<switch-arrow />) and add focus-visible styling (e.g., a focus-visible class or
:focus-visible styles) to the "send-token-select" element to provide visible
keyboard focus.
packages/extension/src/ui/provider-pages/enkrypt/unlock-keyring.vue (1)

97-113: ⚠️ Potential issue | 🟠 Major

Missing .catch() leaves the UI permanently locked on rejection

sendToBackground(...) is not awaited and has no .catch() handler. If the promise rejects (background script crash, Safari extension messaging failure, etc.), isProcessing.value is never reset to false, permanently disabling both the Unlock button (via isDisabled) and the Forgot password button (via :disabled="isProcessing"). The user has no recovery path without reloading the popup.

Additionally, errorMsg.value is set on the error branch but is never exposed in the template — only the boolean isError is passed to lock-screen-password-input, so the specific error message from the background is silently discarded.

🐛 Proposed fix — add `.catch()` and reset processing state
 const approve = async () => {
   isProcessing.value = true;
   const { sendToBackground, Resolve } = await windowPromise;
   sendToBackground({
     method: InternalMethods.unlock,
     params: [password.value, false],
-  }).then(res => {
+  }).then(res => {
     if (res.error) {
       errorMsg.value = res.error.message;
       isError.value = true;
       isProcessing.value = false;
     } else {
       Resolve.value({
         result: JSON.stringify(res.result),
       });
     }
-  });
+  }).catch(() => {
+    isError.value = true;
+    isProcessing.value = false;
+  });
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/ui/provider-pages/enkrypt/unlock-keyring.vue` around
lines 97 - 113, The approve handler leaves isProcessing true on promise
rejection and discards the background error message; change it to await the
sendToBackground call (or add a .catch()) and ensure isProcessing.value is reset
in a finally block, set isError.value and populate errorMsg.value from the
caught error, and then surface errorMsg by passing it into the
lock-screen-password-input component (currently only isError is passed) so the
UI shows the specific error; locate symbols approve, sendToBackground, Resolve,
isProcessing, errorMsg, isError, windowPromise and update the promise handling
and template binding accordingly.
packages/extension/src/providers/ethereum/ui/eth-decrypt.vue (1)

152-169: ⚠️ Potential issue | 🟠 Major

Handle background call failures and reset isProcessing.

If sendToBackground rejects (or windowPromise fails), the UI can get stuck in “Decrypting…” with no resolution path.

🛠️ Suggested fix with try/catch/finally
 const approve = async () => {
   if (isProcessing.value) return;
   isProcessing.value = true;
 
-  const { Request, sendToBackground, Resolve } = await windowPromise;
-  const encryptedMessage = Request.value.params![0] as string;
-  sendToBackground({
-    method: InternalMethods.ethereumDecrypt,
-    params: [encryptedMessage, account.value],
-  }).then(res => {
-    if (res.error) {
-      Resolve.value(res);
-    } else {
-      Resolve.value({
-        result: JSON.stringify(res.result),
-      });
-    }
-  });
+  try {
+    const { Request, sendToBackground, Resolve } = await windowPromise;
+    const encryptedMessage = Request.value.params![0] as string;
+    const res = await sendToBackground({
+      method: InternalMethods.ethereumDecrypt,
+      params: [encryptedMessage, account.value],
+    });
+    if (res.error) {
+      Resolve.value(res);
+    } else {
+      Resolve.value({ result: JSON.stringify(res.result) });
+    }
+  } catch (e) {
+    console.error(e);
+  } finally {
+    isProcessing.value = false;
+  }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/eth-decrypt.vue` around lines
152 - 169, The approve handler can leave isProcessing.value true and never call
Resolve.value if windowPromise or sendToBackground rejects; wrap the await
windowPromise and the sendToBackground call in a try/catch/finally inside the
approve function (referencing approve, windowPromise, Request, sendToBackground,
Resolve, isProcessing) so that any thrown error results in Resolve.value being
called with an error object in the catch, and ensure isProcessing.value is reset
to false in finally; also handle the successful path as before but keep the
Resolve.value calls inside the try block.
packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/index.vue (1)

329-345: ⚠️ Potential issue | 🟠 Major

Remove -webkit-text-fill-color: transparent and related properties from the h3 rule.

The h3 header text will be invisible. The gradient text effect requires a background property that isn't present in this rule. The color: @primaryLabel`` is overridden by -webkit-text-fill-color: transparent, rendering the text transparent without a background to show through. Similar patterns in other files (network-activity-total.vue, send-header.vue) all include an explicit `background: linear-gradient()` property; this file is missing it. Remove the text-fill and background-clip overrides.

🛠️ Safe fix (remove transparent text fill)
     h3 {
       font-style: normal;
       font-weight: 700;
       font-size: 22px;
       line-height: 28px;
       color: `@primaryLabel`;
       margin: 0;
-      -webkit-background-clip: text;
-      -webkit-text-fill-color: transparent;
-      background-clip: text;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/index.vue`
around lines 329 - 345, In the h3 rule inside verify-transaction/index.vue
remove the gradient-related overrides so the header isn't rendered transparent:
delete -webkit-text-fill-color: transparent, -webkit-background-clip: text and
background-clip: text and keep color: `@primaryLabel` (or add an explicit
background: linear-gradient(...) if you intend a gradient); target the h3
selector in that file (the h3 CSS block) when making the change.
packages/swap/src/providers/okx/index.ts (1)

719-738: ⚠️ Potential issue | 🟡 Minor

Remove the misleading inline comment on line 732; clarify that chainIndex is optional.

The main comment at line 719 is actually correct—OKX v6 API docs confirm chainIndex is not required. However, the inline comment on line 732 (// required for swap API) is misleading since the code sends it despite it being optional. Either remove that inline comment or clarify why it's being sent (e.g., // Sent for context even though optional).

The slippagePercent parameter and format are correct: it's a percentage value (0–100), and the code correctly uses DEFAULT_SLIPPAGE = "0.5" to represent 0.5%.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swap/src/providers/okx/index.ts` around lines 719 - 738, The inline
comment on the chainIndex field in the swapParams object is misleading (it
currently reads "// Solana Chain ID - required for swap API"); update it to
reflect that chainIndex is optional in OKX v6 (e.g., change to "// Solana Chain
ID - optional (included for context)" or remove the "required" wording) so the
swapParams declaration (chainIndex, amount, fromTokenAddress, toTokenAddress,
userWalletAddress, slippagePercent, swapMode) accurately reflects API
expectations; keep the existing logic that converts NATIVE_TOKEN_ADDRESS to
SOL_NATIVE_ADDRESS and the slippagePercent formatting using DEFAULT_SLIPPAGE
as-is.
packages/name-resolution/package.json (1)

44-46: ⚠️ Potential issue | 🟡 Minor

Unfilled placeholder in repository.url.

"url": "git+https://github.com/<FILL_IT>" is a stale placeholder that has never been replaced with the actual repository URL. Should be corrected to the canonical URL (e.g., matching the pattern used in other packages).

🔧 Proposed fix
-    "url": "git+https://github.com/<FILL_IT>"
+    "url": "git+https://github.com/enkryptcom/enKrypt/tree/main/packages/name-resolution"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/name-resolution/package.json` around lines 44 - 46, Replace the
placeholder repository URL in package.json: update the "repository" object's
"url" value (currently "git+https://github.com/<FILL_IT>") to the canonical
GitHub repo URL used by other packages (e.g., the real
git+https://github.com/your-org/your-repo.git) so the repository.url field is
accurate and consistent across packages.
packages/name-resolution/tests/resolver.test.ts (1)

22-24: ⚠️ Potential issue | 🟡 Minor

Commented-out assertion silently masks a regression.

The expected address for spaceid.arb (0xb5932a6B7d50A966AEC6C74C97385412Fb497540) is commented out and replaced with null. This means the test is now passing with degraded behavior (resolution returning null) with no tracking or signal. Either re-enable the assertion once the resolution issue is fixed, or document this as a known gap with a linked issue.

Would you like me to open an issue to track this spaceid.arb resolution regression?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/name-resolution/tests/resolver.test.ts` around lines 22 - 24, The
test currently masks a regression by asserting expect(address).to.be.eq(null)
after calling resolver.resolveAddress("spaceid.arb", "ARB1"); restore the
original expectation to
expect(address).to.be.eq("0xb5932a6B7d50A966AEC6C74C97385412Fb497540") in
packages/name-resolution/tests/resolver.test.ts (re-enable the commented
assertion) so the test fails if resolution regresses; if the resolution is known
broken, instead mark the specific test as skipped (e.g., test.skip or add a
clear TODO) and create/link an issue tracking the regression in the test comment
so the gap is discoverable.
packages/signers/bitcoin/package.json (1)

21-37: ⚠️ Potential issue | 🟠 Major

Align engines.node with the new toolchain baseline.

typescript-eslint v8.56.0 explicitly requires Node 18.18.0+ (via engines.node), while this package declares support for Node >=14.15.0. Additionally, @types/node v25 targets Node 25.x. Update engines.node to reflect the actual minimum supported version (Node 18.18.0 or later), or downgrade the toolchain to match Node 14/16 support.

Example alignment (Node 18.18.0+)
 "engines": {
-  "node": ">=14.15.0"
+  "node": ">=18.18.0"
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/signers/bitcoin/package.json` around lines 21 - 37, The package's
declared engines.node (engines.node in package.json) is outdated relative to
devDependencies (typescript-eslint v8.56.0 and `@types/node` v25) — update the
engines.node value in package.json to at least ">=18.18.0" (or another minimum
Node version you choose that matches the toolchain) so it aligns with the
requirements of `@typescript-eslint` and `@types/node`; modify the engines.node
field and commit so package metadata accurately reflects the new Node baseline
used by the devDependencies.
packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue (1)

310-317: ⚠️ Potential issue | 🟠 Major

Guard against empty asset lists to avoid undefined selectedAsset.

allAssets[0] can be undefined; later usages (selectedAsset.value.decimals!) will throw. Add a fallback (or show an empty state) when the list is empty.

🐛 Proposed fix
  return props.network.getAllTokens(addressFrom.value).then(allAssets => {
    accountAssets.value = allAssets as BTCToken[];
-    selectedAsset.value = allAssets[0] as BTCToken;
+    selectedAsset.value = (allAssets[0] as BTCToken) ?? loadingAsset;
    isLoadingAssets.value = false;
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue`
around lines 310 - 317, fetchAssets currently assumes
props.network.getAllTokens(addressFrom.value) returns a non-empty array and
assigns allAssets[0] to selectedAsset, which can be undefined; update
fetchAssets to guard against an empty array by checking allAssets.length before
assigning selectedAsset (fall back to loadingAsset or a clear/empty state),
ensure accountAssets is set to allAssets, set isLoadingAssets.value = false in
all code paths, and ensure subsequent code paths that read
selectedAsset.value.decimals use the safe fallback or handle a null/empty
selectedAsset; reference fetchAssets, selectedAsset, accountAssets,
props.network.getAllTokens, and addressFrom to locate and fix the logic.
packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-token-select.vue (1)

2-28: ⚠️ Potential issue | 🟠 Major

Use a semantic <button> element for keyboard accessibility.

<a> without href is not focusable via keyboard and lacks semantic meaning for interactive actions. Replace with <button type="button"> to ensure keyboard users can access this component and to maintain proper HTML semantics.

🔧 Proposed fix
-  <a class="send-token-select" `@click`="emit('update:toggleTokenSelect')">
+  <button
+    type="button"
+    class="send-token-select"
+    `@click`="emit('update:toggleTokenSelect')"
+  >
     ...
-  </a>
+  </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-token-select.vue`
around lines 2 - 28, The root element of the SendTokenSelect component is an <a>
without href which is not keyboard-focusable; replace it with a semantic <button
type="button" class="send-token-select"
`@click`="emit('update:toggleTokenSelect')"> so keyboard users can activate it.
Keep the same class and click handler (emit('update:toggleTokenSelect')), ensure
any CSS targeting .send-token-select still applies to a button (adjust selectors
if necessary), and preserve the child structure including the <switch-arrow />
import and defineEmits usage.
packages/extension/src/providers/ethereum/ui/send-transaction/index.vue (4)

68-74: ⚠️ Potential issue | 🟡 Minor

:is-loading prop is structurally dead — always false when rendered.

AssetsSelectList lives inside v-if="!!selected && !isLoadingAssets" (line 11), so isLoadingAssets is guaranteed to be false whenever this component is in the DOM. When selectAccountFrom triggers a re-fetch, the whole .send-transaction block unmounts before isLoadingAssets flips back to true. The :is-loading binding here can never be true.

🔧 Proposed fix
  <assets-select-list
    v-model="isOpenSelectToken"
    :is-send="true"
    :assets="accountAssets"
-   :is-loading="isLoadingAssets"
    `@update`:select-asset="selectToken"
  />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/send-transaction/index.vue`
around lines 68 - 74, The :is-loading binding on AssetsSelectList is dead
because the parent v-if ("!!selected && !isLoadingAssets") guarantees
isLoadingAssets is false whenever AssetsSelectList is mounted; either remove the
unnecessary :is-loading="isLoadingAssets" prop from the AssetsSelectList usage
or move AssetsSelectList outside the v-if so it can observe loading transitions
during selectAccountFrom re-fetches; update the template where AssetsSelectList
is used (the <assets-select-list ... /> with v-model="isOpenSelectToken",
:assets="accountAssets" and `@update`:select-asset="selectToken") and ensure the
component either receives a meaningful loading prop or the parent controls its
visibility consistently.

561-581: ⚠️ Potential issue | 🟠 Major

fetchAssets has no rejection handler — network failure leaves the user on an infinite loading screen.

If getAllTokens rejects (RPC error, timeout, etc.), isLoadingAssets never resets to false. The loading spinner stays forever with no error message or recovery. The same gap affects the call-sites in onMounted (line 313) and selectAccountFrom (line 731).

🛡️ Proposed fix
   return props.network.getAllTokens(addressFrom.value).then(allAssets => {
     accountAssets.value = allAssets as (Erc20Token & { contract: string })[];
     const hasParamData =
       isSendToken.value && tokenParamData && tokenParamData.contract;
     if (hasParamData) {
       const selectedToken = accountAssets.value.find(
         asset => asset.contract === tokenParamData.contract,
       );
       if (selectedToken) {
         selectedAsset.value = selectedToken as Erc20Token;
       }
     } else {
       selectedAsset.value = allAssets[0] as Erc20Token;
     }
     isLoadingAssets.value = false;
-  });
+  }).catch(e => {
+    console.error('Error fetching assets', e);
+    isLoadingAssets.value = false;
+    // optionally surface an error state to the user
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/send-transaction/index.vue`
around lines 561 - 581, fetchAssets currently returns
props.network.getAllTokens(...) without any rejection handling so a rejected
promise leaves isLoadingAssets.value true; wrap the await/then call in a
try/catch (or append a .catch) inside fetchAssets to ensure
isLoadingAssets.value is set to false in both success and failure paths, log or
surface the error (e.g., set an error state or call the component's
notification/logging method) and rethrow or return a safe value so callers
(onMounted and selectAccountFrom) can react; update fetchAssets (and its usages
in onMounted/selectAccountFrom) to handle the propagated error instead of
relying on the spinner to resolve itself.

575-579: ⚠️ Potential issue | 🔴 Critical

allAssets[0] is undefined when getAllTokens returns an empty array, crashing all downstream selectedAsset accesses.

allAssets[0] as Erc20Token just casts away the undefined — after isLoadingAssets flips to false the form renders and immediately throws TypeErrors in hasEnoughBalance, errorMsg, TxInfo, sendButtonTitle, etc.

🐛 Proposed fix
     } else {
-      selectedAsset.value = allAssets[0] as Erc20Token;
+      if (allAssets.length > 0) {
+        selectedAsset.value = allAssets[0] as Erc20Token;
+      }
+      // else: leave loadingAsset in place or surface a "no assets" error state
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/send-transaction/index.vue`
around lines 575 - 579, The code sets selectedAsset.value = allAssets[0] as
Erc20Token even when getAllTokens returns an empty array, which causes
downstream TypeErrors; change the assignment to check array length first (if
allAssets.length > 0 set selectedAsset.value = allAssets[0] else set
selectedAsset.value = undefined/null) and ensure the component state
(isLoadingAssets, errorMsg/hasEnoughBalance/TxInfo/sendButtonTitle) treats
selectedAsset as possibly undefined (update types if needed) so the UI disables
actions or shows a friendly message when no tokens are available.

267-271: ⚠️ Potential issue | 🟠 Major

|| NetworkNames.Binance is always truthy — selectedFee defaults to REGULAR for every network.

NetworkNames.Binance is a non-empty string constant, so the OR is always true and the ternary always returns GasPriceTypes.REGULAR, regardless of the actual network. Any network intended to start on ECONOMY silently gets REGULAR fees.

🐛 Proposed fix
 const selectedFee = ref<GasPriceTypes>(
-  props.network.name === NetworkNames.Ethereum || NetworkNames.Binance
+  props.network.name === NetworkNames.Ethereum || props.network.name === NetworkNames.Binance
     ? GasPriceTypes.REGULAR
     : GasPriceTypes.ECONOMY,
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/send-transaction/index.vue`
around lines 267 - 271, The ternary condition initializing selectedFee uses
"props.network.name === NetworkNames.Ethereum || NetworkNames.Binance" which
always truthy; update the condition to explicitly compare props.network.name to
both constants (e.g. props.network.name === NetworkNames.Ethereum ||
props.network.name === NetworkNames.Binance) so networks truly match and
selectedFee picks REGULAR only for those two, otherwise ECONOMY; keep references
to selectedFee, GasPriceTypes, props.network.name, NetworkNames.Ethereum and
NetworkNames.Binance when locating the code.
packages/extension/src/providers/ethereum/ui/eth-get-encryption-key.vue (1)

153-169: ⚠️ Potential issue | 🟠 Major

Handle promise rejections and always clear isProcessing with try/catch/finally.

The await windowPromise at line 157 could reject, leaving the UI stuck in "Providing..." and never resolving the request. Additionally, isProcessing flag is never reset on any error path. Wrap the code in try/catch/finally to ensure the flag is cleared.

Note: The error code to use in the catch block should be one from the existing ErrorCodes enum (userRejected, unauthorized, unsupportedMethod, disconnected, or chainDisconnected) or getCustomError() for a custom message—there is no internal error code constant. This same issue exists in eth-decrypt.vue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/eth-get-encryption-key.vue`
around lines 153 - 169, The approve handler (approve) must guard against
rejected windowPromise and failures from sendToBackground and always reset
isProcessing; wrap the logic in try/catch/finally, await windowPromise and await
sendToBackground (or handle its rejection) instead of relying solely on .then,
and in catch call Resolve.value with an error object using an existing
ErrorCodes enum value (or getCustomError()) to report the failure; ensure
isProcessing.value is set to false in finally. Apply the same change pattern to
the similar handler in eth-decrypt.vue.
packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue (1)

99-141: ⚠️ Potential issue | 🟡 Minor

Use computed for chart options to ensure reactivity when token prop updates.

Currently, option is a static ref created at setup time. When the token prop updates (e.g., during market data refresh or asset list re-render), the chart won't reflect changes because the option object itself doesn't update. The parent component reassigns the entire assets array, which passes new token objects to this component, but the frozen ref won't respond. Other components in the same codebase (e.g., network-activity-total.vue) already use computed(() => ({ ... })) for this exact pattern, which properly tracks prop dependencies.

Additionally, defensively handle potential invalid JSON in sparkline by wrapping JSON.parse in a try-catch, though the current flow via JSON.stringify should produce valid data.

♻️ Suggested refactor
-const option = ref({
+const option = computed(() => ({
   width: 56,
   height: 24,
-  color: [props.token.priceChangePercentage >= 0 ? '#22c55e' : '#ef4444'],
+  color: [props.token.priceChangePercentage >= 0 ? '#22c55e' : '#ef4444'],
   grid: { show: false, left: 0, top: 0, right: 0, bottom: 0 },
   xAxis: [
     {
       show: false,
       type: 'category',
       showGrid: false,
       boundaryGap: false,
       splitLine: { show: false },
     },
   ],
   yAxis: [
     {
       show: false,
       type: 'value',
       splitLine: { show: false },
     },
   ],
   series: [
     {
       type: 'line',
       smooth: true,
       lineStyle: { width: 1.5 },
       areaStyle: {
         opacity: 0.1,
-        color: props.token.priceChangePercentage >= 0 ? '#22c55e' : '#ef4444',
+        color: props.token.priceChangePercentage >= 0 ? '#22c55e' : '#ef4444',
       },
       showSymbol: false,
       emphasis: { focus: 'none' },
       data:
-        props.token.sparkline !== '' ? JSON.parse(props.token.sparkline) : [],
+        parseSparklineData(props.token.sparkline),
     },
   ],
-});
+}));
+
+const parseSparklineData = (sparkline: string): number[] => {
+  if (!sparkline) return [];
+  try {
+    return JSON.parse(sparkline);
+  } catch {
+    return [];
+  }
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue`
around lines 99 - 141, The chart `option` is created as a static ref and won't
update when `props.token` changes; change `option` to a computed that returns
the options object (e.g., computed(() => ({ ... })) ) and reference
`props.token.priceChangePercentage` and parsed sparkline inside it so reactivity
tracks prop updates; also defensively parse `props.token.sparkline` with a
try-catch and return [] on parse errors so `series.data` never throws. Ensure
you replace the existing `option = ref({...})` with the computed variant and
keep the same keys (width, height, color, grid, xAxis, yAxis, series) so the
chart consumer names (option, series.data) remain unchanged.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e71891 and 5eda13c.

⛔ Files ignored due to path filters (3)
  • .yarn/versions/81e941ed.yml is excluded by !**/.yarn/**
  • .yarn/versions/8cbbf8cb.yml is excluded by !**/.yarn/**
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (90)
  • .yarnrc.yml
  • package.json
  • packages/extension-bridge/package.json
  • packages/extension/env.d.ts
  • packages/extension/package.json
  • packages/extension/src/libs/dapp-list/index.ts
  • packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-address-input.vue
  • packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-alert.vue
  • packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-token-select.vue
  • packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue
  • packages/extension/src/providers/common/ui/send-transaction/send-address-item.vue
  • packages/extension/src/providers/common/ui/send-transaction/send-contacts-list.vue
  • packages/extension/src/providers/common/ui/send-transaction/send-fee-select.vue
  • packages/extension/src/providers/common/ui/send-transaction/send-from-contacts-list.vue
  • packages/extension/src/providers/common/ui/send-transaction/send-header.vue
  • packages/extension/src/providers/common/ui/send-transaction/send-input-amount.vue
  • packages/extension/src/providers/common/ui/send-transaction/send-nft-select.vue
  • packages/extension/src/providers/common/ui/verify-transaction/verify-transaction-account.vue
  • packages/extension/src/providers/common/ui/verify-transaction/verify-transaction-amount.vue
  • packages/extension/src/providers/common/ui/verify-transaction/verify-transaction-fee.vue
  • packages/extension/src/providers/common/ui/verify-transaction/verify-transaction-network.vue
  • packages/extension/src/providers/ethereum/inject.ts
  • packages/extension/src/providers/ethereum/libs/activity-handlers/index.ts
  • packages/extension/src/providers/ethereum/libs/activity-handlers/providers/godwoken/configs.ts
  • packages/extension/src/providers/ethereum/libs/activity-handlers/providers/godwoken/index.ts
  • packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts
  • packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts
  • packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts
  • packages/extension/src/providers/ethereum/networks/5ire.ts
  • packages/extension/src/providers/ethereum/networks/godwoken.ts
  • packages/extension/src/providers/ethereum/networks/index.ts
  • packages/extension/src/providers/ethereum/tests/ethereum.events.test.ts
  • packages/extension/src/providers/ethereum/tests/ethereum.requests.test.ts
  • packages/extension/src/providers/ethereum/ui/eth-connect-dapp.vue
  • packages/extension/src/providers/ethereum/ui/eth-decrypt.vue
  • packages/extension/src/providers/ethereum/ui/eth-get-encryption-key.vue
  • packages/extension/src/providers/ethereum/ui/eth-sign-message.vue
  • packages/extension/src/providers/ethereum/ui/eth-sign-typedata.vue
  • packages/extension/src/providers/ethereum/ui/eth-verify-transaction.vue
  • packages/extension/src/providers/ethereum/ui/send-transaction/components/send-address-input.vue
  • packages/extension/src/providers/ethereum/ui/send-transaction/components/send-alert.vue
  • packages/extension/src/providers/ethereum/ui/send-transaction/components/send-token-select.vue
  • packages/extension/src/providers/ethereum/ui/send-transaction/index.vue
  • packages/extension/src/providers/ethereum/ui/send-transaction/verify-transaction/index.vue
  • packages/extension/src/providers/ethereum/ui/styles/common-popup.less
  • packages/extension/src/providers/polkadot/networks/assethub/assethub-dot.ts
  • packages/extension/src/providers/polkadot/networks/assethub/assethub-ksm.ts
  • packages/extension/src/providers/polkadot/networks/index.ts
  • packages/extension/src/providers/polkadot/networks/unique/quartz.ts
  • packages/extension/src/providers/solana/ui/send-transaction/components/send-address-input.vue
  • packages/extension/src/providers/solana/ui/send-transaction/components/send-alert.vue
  • packages/extension/src/providers/solana/ui/send-transaction/components/send-fee-select.vue
  • packages/extension/src/providers/solana/ui/send-transaction/components/send-token-select.vue
  • packages/extension/src/providers/solana/ui/send-transaction/index.vue
  • packages/extension/src/ui/action/composables/account-info.ts
  • packages/extension/src/ui/action/views/asset-detail-view/index.vue
  • packages/extension/src/ui/action/views/network-activity/components/network-activity-action.vue
  • packages/extension/src/ui/action/views/network-activity/components/network-activity-loading.vue
  • packages/extension/src/ui/action/views/network-activity/components/network-activity-total.vue
  • packages/extension/src/ui/action/views/network-activity/components/network-activity-transaction.vue
  • packages/extension/src/ui/action/views/network-activity/components/transaction-timer.vue
  • packages/extension/src/ui/action/views/network-activity/index.vue
  • packages/extension/src/ui/action/views/network-assets/components/network-assets-item.vue
  • packages/extension/src/ui/action/views/network-assets/index.vue
  • packages/extension/src/ui/action/views/send-process/components/send-process-account.vue
  • packages/extension/src/ui/action/views/send-process/components/send-process-amount.vue
  • packages/extension/src/ui/action/views/send-process/components/send-process-nft.vue
  • packages/extension/src/ui/action/views/send-process/index.vue
  • packages/extension/src/ui/provider-pages/enkrypt/unlock-keyring.vue
  • packages/hw-wallets/package.json
  • packages/hw-wallets/src/trezor/trezorConnect.ts
  • packages/keyring/package.json
  • packages/name-resolution/package.json
  • packages/name-resolution/tests/ens.test.ts
  • packages/name-resolution/tests/resolver.test.ts
  • packages/request/package.json
  • packages/signers/bitcoin/package.json
  • packages/signers/bitcoin/tests/sign.test.ts
  • packages/signers/ethereum/package.json
  • packages/signers/kadena/package.json
  • packages/signers/massa/package.json
  • packages/signers/polkadot/package.json
  • packages/storage/package.json
  • packages/swap/package.json
  • packages/swap/src/configs.ts
  • packages/swap/src/index.ts
  • packages/swap/src/providers/okx/index.ts
  • packages/types/package.json
  • packages/types/src/networks.ts
  • packages/utils/package.json
💤 Files with no reviewable changes (13)
  • packages/extension/src/providers/ethereum/libs/assets-handlers/token-lists.ts
  • packages/extension/src/providers/ethereum/libs/assets-handlers/types/tokenbalance-mew.ts
  • packages/extension/src/providers/ethereum/networks/index.ts
  • packages/extension/src/providers/ethereum/networks/5ire.ts
  • packages/extension/src/providers/ethereum/libs/activity-handlers/providers/godwoken/configs.ts
  • packages/types/src/networks.ts
  • packages/extension/src/providers/ethereum/libs/activity-handlers/index.ts
  • packages/extension/src/providers/ethereum/libs/assets-handlers/assetinfo-mew.ts
  • packages/extension/src/libs/dapp-list/index.ts
  • packages/extension/src/providers/polkadot/networks/index.ts
  • packages/extension/src/providers/ethereum/libs/activity-handlers/providers/godwoken/index.ts
  • packages/extension/src/providers/polkadot/networks/unique/quartz.ts
  • packages/extension/src/providers/ethereum/networks/godwoken.ts

Comment on lines 153 to 155
const approve = async () => {
if (isProcessing.value) return;
const { Request, Resolve } = await windowPromise;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

TOCTOU race: set isProcessing = true before the await.

The guard at line 154 checks isProcessing.value synchronously, but the flag is only set to true at line 157, after await windowPromise. Because JavaScript suspends at each await, two rapid clicks can both pass the if (isProcessing.value) return check before either call reaches the flag assignment — sending two signatures for a single user intent.

🔒 Proposed fix: move the flag before the first `await`
 const approve = async () => {
   if (isProcessing.value) return;
+  isProcessing.value = true;
   const { Request, Resolve } = await windowPromise;
   const msg = Request.value.params![0] as `0x{string}`;
-  isProcessing.value = true;
   MessageSigner({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const approve = async () => {
if (isProcessing.value) return;
const { Request, Resolve } = await windowPromise;
const approve = async () => {
if (isProcessing.value) return;
isProcessing.value = true;
const { Request, Resolve } = await windowPromise;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/providers/ethereum/ui/eth-sign-message.vue` around
lines 153 - 155, The approve handler has a TOCTOU race: move the isProcessing
flag before the await to prevent concurrent runs — in the approve function
(reference: approve, isProcessing, windowPromise) set isProcessing.value = true
immediately after the synchronous guard and before awaiting windowPromise, and
wrap the async work in try/catch/finally to ensure isProcessing.value is reset
to false in the finally block (and rethrow or handle errors in catch) so the
flag is always cleared even on failure.

Comment on lines +55 to +60
<modal-forgot
v-if="isForgot"
:is-forgot="isForgot"
@toggle:forgot="toggleForgot"
:disabled="isProcessing"
/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the modal-forgot component file
fd -e vue "modal-forgot" | head -20

Repository: enkryptcom/enKrypt

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for modal-forgot references in the codebase
rg "modal-forgot" --type vue -A 3 -B 3

Repository: enkryptcom/enKrypt

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Search for modal-forgot in all files
rg "modal-forgot" -A 3 -B 3

Repository: enkryptcom/enKrypt

Length of output: 7447


🏁 Script executed:

#!/bin/bash
# Read the complete modal-forgot component
cat -n packages/extension/src/ui/action/views/modal-forgot/index.vue

Repository: enkryptcom/enKrypt

Length of output: 1848


🏁 Script executed:

#!/bin/bash
# Find and read lock-screen-forgot component
cat -n packages/extension/src/ui/action/views/lock-screen/components/lock-screen-forgot.vue

Repository: enkryptcom/enKrypt

Length of output: 5136


Remove :disabled="isProcessing" from <modal-forgot> or declare the prop in the component

The :disabled="isProcessing" attribute is not declared as a prop in the modal-forgot component. Since modal-forgot uses v-bind="$attrs" and doesn't set inheritAttrs: false, the attribute falls through to lock-screen-forgot, which also doesn't declare it. This results in the disabled attribute being applied as an HTML attribute to the root <div> element, which is non-semantic and has no effect.

The lock-screen-forgot component manages its button's disabled state internally via a computed property (isDisabled), so the external disabled prop is unused. Either remove the attribute if it's not needed, or properly declare disabled as a prop in modal-forgot and wire it through the component hierarchy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/extension/src/ui/provider-pages/enkrypt/unlock-keyring.vue` around
lines 55 - 60, The template passes :disabled="isProcessing" into <modal-forgot>
but that prop is not declared and falls through to lock-screen-forgot as an HTML
attribute; either remove the attribute from the usage or declare and forward a
proper prop. Fix by deleting :disabled="isProcessing" from the <modal-forgot>
invocation in unlock-keyring.vue if external disabling is unused, or add a
disabled prop to the modal-forgot component (e.g., props: { disabled: Boolean })
and pass it down/consume it (or explicitly forward it to lock-screen-forgot) so
the disabled state is handled via the components’ computed logic instead of
becoming a stray HTML attribute. Ensure modal-forgot’s implementation uses the
declared prop (or sets inheritAttrs: false and v-bind="$attrs" intentionally)
and that lock-screen-forgot receives/uses the prop if needed.

const DEFAULT_TOKEN_ACCOUNT_RENT_EXEMPTION = 2039280;
const SOL_NATIVE_ADDRESS = "11111111111111111111111111111111";
const OKX_API_URL = "https://partners.mewapi.io/okxswapv5";
const OKX_API_URL = "https://partners.mewapi.io/okxswapv6";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n packages/swap/src/providers/okx/index.ts | head -100

Repository: enkryptcom/enKrypt

Length of output: 3690


🏁 Script executed:

sed -n '480,495p; 524,535p; 727,742p' packages/swap/src/providers/okx/index.ts

Repository: enkryptcom/enKrypt

Length of output: 1620


🌐 Web query:

OKX DEX Aggregator v6 API base URL endpoints all-tokens quote swap

💡 Result:

OKX DEX Aggregator V6 REST base URL (aggregator): https://web3.okx.com/api/v6/dex/aggregator [1]

Endpoints (full URLs):

  • All tokens: GET https://web3.okx.com/api/v6/dex/aggregator/all-tokens [1]
  • Quote: GET https://web3.okx.com/api/v6/dex/aggregator/quote [2]
  • Swap: GET https://web3.okx.com/api/v6/dex/aggregator/swap [3]

Sources:
[1] https://web3.okx.com/ar/build/dev-docs/wallet-api/dex-get-tokens
[2] https://web3.okx.com/zh-hans/build/dev-docs/dex-api/dex-get-quote
[3] https://web3.okx.com/build/dev-docs/dex-api/dex-swap


Use official OKX v6 API base URL, not third-party proxy.

Line 50 uses https://partners.mewapi.io/okxswapv6 as the base URL, which is a third-party proxy (MEW) and not the official OKX DEX Aggregator v6 API. The official OKX v6 base URL is https://web3.okx.com/api/v6/dex/aggregator, with endpoints:

  • GET https://web3.okx.com/api/v6/dex/aggregator/all-tokens
  • GET https://web3.okx.com/api/v6/dex/aggregator/quote
  • GET https://web3.okx.com/api/v6/dex/aggregator/swap

Confirm whether the MEW proxy is intentional and approved, or switch to the official OKX endpoint for v6.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/swap/src/providers/okx/index.ts` at line 50, Replace the third-party
proxy base URL constant OKX_API_URL with the official OKX DEX Aggregator v6 base
URL "https://web3.okx.com/api/v6/dex/aggregator"; update any code that
constructs paths (e.g., calls that append "/all-tokens", "/quote", "/swap") to
use OKX_API_URL so endpoints become OKX_API_URL + "/all-tokens" etc., and remove
or document the MEW proxy only if its usage was intentional/approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants