Conversation
📝 WalkthroughWalkthroughIntroduces memo encryption functionality by adding preprocessing in transfer mutations to encrypt memos starting with "#", creating a new MemoDisplay component to render encrypted memos with decryption support, implementing a modal dialog flow for memo key input, developing crypto utilities for encryption/decryption across authentication methods, and extending key derivation to support memo keys. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant TransferForm as Transfer Form
participant SignTransfer as sign-transfer.ts
participant MemoKeyDialog as Memo Key Dialog
participant CryptoUtils as Crypto Utils
participant HiveAPI as Hive API
User->>TransferForm: Enter memo starting with "#"
User->>TransferForm: Submit transfer
TransferForm->>SignTransfer: Dispatch with memo="#..."
SignTransfer->>SignTransfer: Detect memo starts with "#"
SignTransfer->>MemoKeyDialog: requestMemoKey("encrypt")
MemoKeyDialog->>User: Show key input modal
User->>MemoKeyDialog: Provide memo key
MemoKeyDialog->>SignTransfer: Return signed key
SignTransfer->>CryptoUtils: encryptMemo(loginType, username, recipient, memo)
CryptoUtils->>HiveAPI: Get recipient account memo_key
CryptoUtils->>CryptoUtils: Encrypt memo with keys
CryptoUtils->>SignTransfer: Return "#encrypted..."
SignTransfer->>HiveAPI: Transfer with encrypted memo
HiveAPI->>User: Transaction confirmed
sequenceDiagram
participant User
participant TransactionRow as Transaction Row
participant MemoDisplay as Memo Display
participant MemoKeyDialog as Memo Key Dialog
participant CryptoUtils as Crypto Utils
participant HiveAPI as Hive API
TransactionRow->>MemoDisplay: Display memo="#encrypted..."
MemoDisplay->>MemoDisplay: Detect encryption (starts with #, length > 50)
MemoDisplay->>User: Render decrypt button
User->>MemoDisplay: Click decrypt
MemoDisplay->>MemoKeyDialog: requestMemoKey("decrypt")
MemoKeyDialog->>User: Show key input modal
User->>MemoKeyDialog: Provide memo key
MemoKeyDialog->>MemoDisplay: Return signed key
MemoDisplay->>CryptoUtils: decryptMemo(loginType, username, encrypted)
CryptoUtils->>HiveAPI: Get account private memo key (if keychain)
CryptoUtils->>CryptoUtils: Decrypt memo with key
CryptoUtils->>MemoDisplay: Return decrypted text
MemoDisplay->>User: Display decrypted memo
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
apps/web/src/utils/memo-crypto.ts (1)
60-66: HiveSigner fallback returns0when no access token exists.Per
getDecodedMemo(inhive-signer.ts), when no access token is available, it returnsPromise.resolve(0). The current checkresult && result.resultcorrectly rejects this case, but the error message "HiveSigner memo decode failed" may be misleading since the actual cause is a missing or expired token.Consider adding a more specific check:
Suggested improvement
if (loginType === "hivesigner") { const result = await getDecodedMemo(username, encryptedMemo); + if (result === 0) { + throw new Error("HiveSigner access token missing or expired"); + } if (result && result.result) { return result.result; } throw new Error("HiveSigner memo decode failed"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/utils/memo-crypto.ts` around lines 60 - 66, The current HiveSigner branch uses getDecodedMemo(username, encryptedMemo) and throws a generic "HiveSigner memo decode failed" when result/result.result is falsy; update the logic in the loginType === "hivesigner" block to explicitly detect the sentinel Promise.resolve(0) case (i.e., if result === 0) and throw a specific error like "HiveSigner access token missing or expired", otherwise if result is present but result.result is falsy throw the existing generic decode failure; reference getDecodedMemo, loginType, username, encryptedMemo and result/result.result when implementing the checks.apps/web/src/features/ui/input/key-input.tsx (1)
35-38: Add type annotation for thestrparameter.The
capitalizeFirstLetterhelper function is missing a TypeScript type annotation. This creates an implicitanytype.Suggested fix
-function capitalizeFirstLetter(str) { +function capitalizeFirstLetter(str: string | undefined): string { if (typeof str !== 'string' || str.length === 0) return ''; return str[0].toUpperCase() + str.slice(1); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/ui/input/key-input.tsx` around lines 35 - 38, The helper function capitalizeFirstLetter has an implicit any for its parameter; update its signature to explicitly type the parameter as a string and the return type as string (e.g., capitalizeFirstLetter(str: string): string) so TypeScript can type-check callers and avoid implicit any; keep the existing runtime guards (typeof check and empty string) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/api/mutations/sign-transfer.ts`:
- Around line 85-91: The code currently encrypts memo unconditionally when it
starts with "#" inside mutateAsync (using getLoginType, encryptMemo, activeUser,
processedMemo), which can prompt for a memo key even for transfer paths that
never submit a memo; move the encryption logic into the specific branches that
actually send a memo (the branches that call the mutation code that includes
processedMemo) or add an explicit supportsMemo check before calling encryptMemo
so only paths that will include memo perform encryption and request keys.
In `@apps/web/src/app/client-providers.tsx`:
- Around line 47-49: MemoKeyDialog is currently wrapped by DeferredRender which
delays mounting and causes its ecency-memo-key listener (installed in
MemoKeyDialog's useEffect) to miss early requestMemoKey() calls; move
<MemoKeyDialog /> out of the <DeferredRender> block so it mounts eagerly (e.g.,
place MemoKeyDialog alongside AuthUpgradeDialog or above DeferredRender) so the
listener is registered immediately. Ensure you only change the component
placement (remove from DeferredRender) and do not alter MemoKeyDialog internals.
In `@apps/web/src/features/i18n/locales/en-US.json`:
- Around line 1470-1472: Update the three locale strings to explicitly reference
the private memo key to avoid confusion: change "memo-key-title" from "Enter
Memo Key" to "Enter Private Memo Key" and change both
"memo-key-subtitle-encrypt" and "memo-key-subtitle-decrypt" to mention "private
memo key" (e.g., "Your private memo key is needed to encrypt this message." and
"Your private memo key is needed to decrypt this message."). Ensure the keys
"memo-key-title", "memo-key-subtitle-encrypt", and "memo-key-subtitle-decrypt"
are updated accordingly.
In `@apps/web/src/features/shared/memo-display.tsx`:
- Around line 22-27: The memo-detection heuristic in isLikelyEncryptedMemo
(checks startsWith("#") plus content.length > 50) is inconsistent with callers
that only use memo.startsWith("#"), causing short encrypted memos to be
misrouted; export isLikelyEncryptedMemo from memo-display and replace the simple
memo.startsWith("#") checks in the calling components (the code that routes to
MemoDisplay, e.g., where transaction-row and transfer-step-2 decide rendering)
to call isLikelyEncryptedMemo instead, or if you prefer shorter thresholds
adjust the length constant inside isLikelyEncryptedMemo and update all callers
to import it; also ensure MemoDisplay’s decryption path handles failures
gracefully (catch and fall back to safe plain-text rendering) so decryption
attempts don’t break the UI.
In `@apps/web/src/features/shared/memo-key/memo-key-events.ts`:
- Around line 18-35: The current requestMemoKey resolves any existing
pendingResolve with false on re-entry, which cancels the first waiter; instead,
change requestMemoKey so it does not call pendingResolve(false) and instead
returns the existing pending promise when a request is already in flight.
Implement a shared pendingPromise alongside pendingResolve (or promote the
existing new Promise to a module-level pendingPromise) in requestMemoKey, have
requestMemoKey check if pendingPromise exists and return it, and ensure
clearTempMemoKey clears both pendingResolve and pendingPromise so subsequent
calls behave correctly; relevant symbols: requestMemoKey, pendingResolve,
pendingPromise (new), and clearTempMemoKey (encryptMemo/decryptMemo call
requestMemoKey).
In `@apps/web/src/features/shared/transfer/transfer-step-1.tsx`:
- Around line 473-480: The UI currently advertises encrypted memos when
memo.startsWith("#"), which is unsafe for exchange recipients; change the
rendering and validation so that TransferFormText only shows the encrypted-memo
hint when the recipient (prop `to` / `recipient`) is NOT in EXCHANGE_ACCOUNTS,
and ensure submission validation in the transfer flow (the memo validation used
by the form submit handler) rejects memos starting with "#" if the recipient is
in EXCHANGE_ACCOUNTS; update the condition around TransferFormText (the
memo.startsWith("#") branch) to check EXCHANGE_ACCOUNTS membership and add a
server/client-side validation in the transfer submit/validate function to block
encrypted-memo paths for exchange accounts.
In `@packages/sdk/src/modules/operations/encrypt-memo.ts`:
- Around line 1-3: The import is pulling Memo from the internal path
"@hiveio/dhive/lib/memo"; update the import statement to import Memo from the
public package export instead (i.e., import { Memo } from "@hiveio/dhive"), so
replace the current internal-path import with a public API import for Memo
alongside the existing PrivateKey and Client imports in encrypt-memo.ts to avoid
relying on internal module paths.
---
Nitpick comments:
In `@apps/web/src/features/ui/input/key-input.tsx`:
- Around line 35-38: The helper function capitalizeFirstLetter has an implicit
any for its parameter; update its signature to explicitly type the parameter as
a string and the return type as string (e.g., capitalizeFirstLetter(str:
string): string) so TypeScript can type-check callers and avoid implicit any;
keep the existing runtime guards (typeof check and empty string) intact.
In `@apps/web/src/utils/memo-crypto.ts`:
- Around line 60-66: The current HiveSigner branch uses getDecodedMemo(username,
encryptedMemo) and throws a generic "HiveSigner memo decode failed" when
result/result.result is falsy; update the logic in the loginType ===
"hivesigner" block to explicitly detect the sentinel Promise.resolve(0) case
(i.e., if result === 0) and throw a specific error like "HiveSigner access token
missing or expired", otherwise if result is present but result.result is falsy
throw the existing generic decode failure; reference getDecodedMemo, loginType,
username, encryptedMemo and result/result.result when implementing the checks.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 85258c0d-8824-4a99-a27b-d13bfe3eff60
⛔ Files ignored due to path filters (15)
packages/sdk/dist/browser/index.d.tsis excluded by!**/dist/**packages/sdk/dist/browser/index.jsis excluded by!**/dist/**packages/sdk/dist/browser/index.js.mapis excluded by!**/dist/**,!**/*.mappackages/sdk/dist/node/index.cjsis excluded by!**/dist/**packages/sdk/dist/node/index.cjs.mapis excluded by!**/dist/**,!**/*.mappackages/sdk/dist/node/index.mjsis excluded by!**/dist/**packages/sdk/dist/node/index.mjs.mapis excluded by!**/dist/**,!**/*.mappackages/sdk/src/modules/operations/__snapshots__/memo-crypto.spec.ts.snapis excluded by!**/*.snappackages/wallets/dist/browser/index.d.tsis excluded by!**/dist/**packages/wallets/dist/browser/index.jsis excluded by!**/dist/**packages/wallets/dist/browser/index.js.mapis excluded by!**/dist/**,!**/*.mappackages/wallets/dist/node/index.cjsis excluded by!**/dist/**packages/wallets/dist/node/index.cjs.mapis excluded by!**/dist/**,!**/*.mappackages/wallets/dist/node/index.mjsis excluded by!**/dist/**packages/wallets/dist/node/index.mjs.mapis excluded by!**/dist/**,!**/*.map
📒 Files selected for processing (24)
apps/web/src/api/mutations/sign-transfer.tsapps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/hbd/_components/hive-transaction-row.tsxapps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/hive/_components/hive-transaction-row.tsxapps/web/src/app/(dynamicPages)/profile/[username]/wallet/(token)/hp/_components/hive-transaction-row.tsxapps/web/src/app/client-providers.tsxapps/web/src/features/i18n/locales/en-US.jsonapps/web/src/features/polls/components/poll-widget.tsxapps/web/src/features/shared/memo-display.tsxapps/web/src/features/shared/memo-key/index.tsapps/web/src/features/shared/memo-key/memo-key-dialog.tsxapps/web/src/features/shared/memo-key/memo-key-events.tsapps/web/src/features/shared/transactions/transaction-row.tsxapps/web/src/features/shared/transfer/transfer-step-1.tsxapps/web/src/features/shared/transfer/transfer-step-2.tsxapps/web/src/features/ui/input/key-input.tsxapps/web/src/types/keychain-impl.tsapps/web/src/utils/memo-crypto.tspackages/sdk/src/modules/operations/decrypt-memo.tspackages/sdk/src/modules/operations/encrypt-memo.tspackages/sdk/src/modules/operations/index.tspackages/sdk/src/modules/operations/memo-crypto.spec.tspackages/wallets/src/modules/wallets/utils/decrypt-memo.tspackages/wallets/src/modules/wallets/utils/detect-hive-key-derivation.tspackages/wallets/src/modules/wallets/utils/encrypt-memo.ts
| mutateAsync: async ({ to, amount, memo }: SignTransferPayload) => { | ||
| // Encrypt memo if it starts with # | ||
| let processedMemo = memo; | ||
| if (memo.startsWith("#") && to) { | ||
| const loginType = getLoginType(activeUser?.username ?? ""); | ||
| processedMemo = await encryptMemo(loginType, activeUser!.username, to, memo.slice(1)); | ||
| } |
There was a problem hiding this comment.
Only encrypt in branches that actually submit a memo.
This runs before the mode/asset switch, so # memos on paths like SPK, LARYNX, power-up, delegate, and convert will still request a memo key even though those mutations never send memo. That turns unsupported memo input into an avoidable prompt/failure path. Move the encryption into the branches that pass memo, or gate it with an explicit supportsMemo check.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/api/mutations/sign-transfer.ts` around lines 85 - 91, The code
currently encrypts memo unconditionally when it starts with "#" inside
mutateAsync (using getLoginType, encryptMemo, activeUser, processedMemo), which
can prompt for a memo key even for transfer paths that never submit a memo; move
the encryption logic into the specific branches that actually send a memo (the
branches that call the mutation code that includes processedMemo) or add an
explicit supportsMemo check before calling encryptMemo so only paths that will
include memo perform encryption and request keys.
| <DeferredRender> | ||
| <AuthUpgradeDialog /> | ||
| <MemoKeyDialog /> |
There was a problem hiding this comment.
Mount MemoKeyDialog eagerly; deferring it can drop the first memo-key request.
MemoKeyDialog installs its ecency-memo-key listener in useEffect (apps/web/src/features/shared/memo-key/memo-key-dialog.tsx:14-24). Putting it behind DeferredRender adds another async delay before that listener exists, so any early requestMemoKey() call is missed and the dialog never opens.
Suggested fix
<UIManager>
<ClientInit />
+ <MemoKeyDialog />
{/* Defer non-critical components for LCP optimization */}
<DeferredRender>
<AuthUpgradeDialog />
- <MemoKeyDialog />
<EcencyConfigManager.Conditional
condition={({ visionFeatures }) => visionFeatures.userActivityTracking.enabled}
>📝 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.
| <DeferredRender> | |
| <AuthUpgradeDialog /> | |
| <MemoKeyDialog /> | |
| <UIManager> | |
| <ClientInit /> | |
| <MemoKeyDialog /> | |
| {/* Defer non-critical components for LCP optimization */} | |
| <DeferredRender> | |
| <AuthUpgradeDialog /> | |
| <EcencyConfigManager.Conditional | |
| condition={({ visionFeatures }) => visionFeatures.userActivityTracking.enabled} | |
| > |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/client-providers.tsx` around lines 47 - 49, MemoKeyDialog is
currently wrapped by DeferredRender which delays mounting and causes its
ecency-memo-key listener (installed in MemoKeyDialog's useEffect) to miss early
requestMemoKey() calls; move <MemoKeyDialog /> out of the <DeferredRender> block
so it mounts eagerly (e.g., place MemoKeyDialog alongside AuthUpgradeDialog or
above DeferredRender) so the listener is registered immediately. Ensure you only
change the component placement (remove from DeferredRender) and do not alter
MemoKeyDialog internals.
| "memo-key-title": "Enter Memo Key", | ||
| "memo-key-subtitle-encrypt": "Your memo key is needed to encrypt this message.", | ||
| "memo-key-subtitle-decrypt": "Your memo key is needed to decrypt this message.", |
There was a problem hiding this comment.
Call out that this is the private memo key.
"Enter Memo Key" / "Your memo key is needed" is ambiguous in Hive terminology and can easily lead users to paste the public memo key, which guarantees encrypt/decrypt failures. Please make the copy explicit, e.g. “Enter Private Memo Key”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/i18n/locales/en-US.json` around lines 1470 - 1472,
Update the three locale strings to explicitly reference the private memo key to
avoid confusion: change "memo-key-title" from "Enter Memo Key" to "Enter Private
Memo Key" and change both "memo-key-subtitle-encrypt" and
"memo-key-subtitle-decrypt" to mention "private memo key" (e.g., "Your private
memo key is needed to encrypt this message." and "Your private memo key is
needed to decrypt this message."). Ensure the keys "memo-key-title",
"memo-key-subtitle-encrypt", and "memo-key-subtitle-decrypt" are updated
accordingly.
| function isLikelyEncryptedMemo(memo: string): boolean { | ||
| if (!memo.startsWith("#")) return false; | ||
| const content = memo.slice(1); | ||
| // Encrypted memos are long base58 strings with no whitespace | ||
| return content.length > 50 && !/\s/.test(content); | ||
| } |
There was a problem hiding this comment.
Inconsistent encrypted memo detection with calling components.
The isLikelyEncryptedMemo heuristic requires content.length > 50, but the components that route memos to MemoDisplay (e.g., transaction-row.tsx at lines 144-150, 183-189 and transfer-step-2.tsx at lines 56-65) use only memo.startsWith("#") without the length check.
This creates a UX problem: a short encrypted memo (≤50 chars) would be routed to MemoDisplay but then rendered as plain text (garbled base58). Consider either:
- Export and use
isLikelyEncryptedMemoin calling components for consistent detection - Lower the threshold (dhive's minimum encrypted output is shorter)
- Have
MemoDisplayalways attempt decryption for#-prefixed memos, handling failure gracefully
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/shared/memo-display.tsx` around lines 22 - 27, The
memo-detection heuristic in isLikelyEncryptedMemo (checks startsWith("#") plus
content.length > 50) is inconsistent with callers that only use
memo.startsWith("#"), causing short encrypted memos to be misrouted; export
isLikelyEncryptedMemo from memo-display and replace the simple
memo.startsWith("#") checks in the calling components (the code that routes to
MemoDisplay, e.g., where transaction-row and transfer-step-2 decide rendering)
to call isLikelyEncryptedMemo instead, or if you prefer shorter thresholds
adjust the length constant inside isLikelyEncryptedMemo and update all callers
to import it; also ensure MemoDisplay’s decryption path handles failures
gracefully (catch and fall back to safe plain-text rendering) so decryption
attempts don’t break the UI.
| export function requestMemoKey( | ||
| purpose: "encrypt" | "decrypt" | ||
| ): Promise<string | false> { | ||
| if (pendingResolve) { | ||
| pendingResolve(false); | ||
| pendingResolve = null; | ||
| } | ||
|
|
||
| clearTempMemoKey(); | ||
|
|
||
| return new Promise((resolve) => { | ||
| pendingResolve = resolve; | ||
| window.dispatchEvent( | ||
| new CustomEvent("ecency-memo-key", { | ||
| detail: { purpose } | ||
| }) | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Don’t cancel the first waiter on re-entry.
Line 21 resolves the existing caller with false before the user answers the dialog. Since both encryptMemo() and decryptMemo() call this helper directly, concurrent memo actions can fail spuriously instead of sharing the same prompt.
Safer pattern
let pendingResolve: ((key: string | false) => void) | null = null;
+let pendingRequest: Promise<string | false> | null = null;
let tempMemoKey: string | null = null;
let tempKeyTimeout: ReturnType<typeof setTimeout> | null = null;
export function requestMemoKey(
purpose: "encrypt" | "decrypt"
): Promise<string | false> {
- if (pendingResolve) {
- pendingResolve(false);
- pendingResolve = null;
- }
+ if (pendingRequest) return pendingRequest;
clearTempMemoKey();
- return new Promise((resolve) => {
- pendingResolve = resolve;
+ pendingRequest = new Promise((resolve) => {
+ pendingResolve = (key) => {
+ pendingRequest = null;
+ resolve(key);
+ };
window.dispatchEvent(
new CustomEvent("ecency-memo-key", {
detail: { purpose }
})
);
});
+
+ return pendingRequest;
}📝 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.
| export function requestMemoKey( | |
| purpose: "encrypt" | "decrypt" | |
| ): Promise<string | false> { | |
| if (pendingResolve) { | |
| pendingResolve(false); | |
| pendingResolve = null; | |
| } | |
| clearTempMemoKey(); | |
| return new Promise((resolve) => { | |
| pendingResolve = resolve; | |
| window.dispatchEvent( | |
| new CustomEvent("ecency-memo-key", { | |
| detail: { purpose } | |
| }) | |
| ); | |
| }); | |
| let pendingResolve: ((key: string | false) => void) | null = null; | |
| let pendingRequest: Promise<string | false> | null = null; | |
| let tempMemoKey: string | null = null; | |
| let tempKeyTimeout: ReturnType<typeof setTimeout> | null = null; | |
| export function requestMemoKey( | |
| purpose: "encrypt" | "decrypt" | |
| ): Promise<string | false> { | |
| if (pendingRequest) return pendingRequest; | |
| clearTempMemoKey(); | |
| pendingRequest = new Promise((resolve) => { | |
| pendingResolve = (key) => { | |
| pendingRequest = null; | |
| resolve(key); | |
| }; | |
| window.dispatchEvent( | |
| new CustomEvent("ecency-memo-key", { | |
| detail: { purpose } | |
| }) | |
| ); | |
| }); | |
| return pendingRequest; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/shared/memo-key/memo-key-events.ts` around lines 18 -
35, The current requestMemoKey resolves any existing pendingResolve with false
on re-entry, which cancels the first waiter; instead, change requestMemoKey so
it does not call pendingResolve(false) and instead returns the existing pending
promise when a request is already in flight. Implement a shared pendingPromise
alongside pendingResolve (or promote the existing new Promise to a module-level
pendingPromise) in requestMemoKey, have requestMemoKey check if pendingPromise
exists and return it, and ensure clearTempMemoKey clears both pendingResolve and
pendingPromise so subsequent calls behave correctly; relevant symbols:
requestMemoKey, pendingResolve, pendingPromise (new), and clearTempMemoKey
(encryptMemo/decryptMemo call requestMemoKey).
| <TransferFormText | ||
| msg={ | ||
| memo.startsWith("#") | ||
| ? `🔒 ${i18next.t("transfer.memo-encrypted")}` | ||
| : i18next.t("transfer.memo-help") | ||
| } | ||
| type="muted" | ||
| /> |
There was a problem hiding this comment.
Reject encrypted memos for exchange transfers.
This hint makes #... look valid even when to is in EXCHANGE_ACCOUNTS. Exchange deposits usually depend on an exact plain memo/tag; encrypting it can make the deposit unrecoverable. Please block the encrypted-memo path for exchange recipients instead of advertising it here.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/shared/transfer/transfer-step-1.tsx` around lines 473 -
480, The UI currently advertises encrypted memos when memo.startsWith("#"),
which is unsafe for exchange recipients; change the rendering and validation so
that TransferFormText only shows the encrypted-memo hint when the recipient
(prop `to` / `recipient`) is NOT in EXCHANGE_ACCOUNTS, and ensure submission
validation in the transfer flow (the memo validation used by the form submit
handler) rejects memos starting with "#" if the recipient is in
EXCHANGE_ACCOUNTS; update the condition around TransferFormText (the
memo.startsWith("#") branch) to check EXCHANGE_ACCOUNTS membership and add a
server/client-side validation in the transfer submit/validate function to block
encrypted-memo paths for exchange accounts.
| import { PrivateKey } from "@hiveio/dhive"; | ||
| import type { Client } from "@hiveio/dhive"; | ||
| import { Memo } from "@hiveio/dhive/lib/memo"; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does @hiveio/dhive publicly support importing Memo from "@hiveio/dhive/lib/memo", or is that an internal/private module path?
💡 Result:
@hiveio/dhive/lib/memo should be treated as an internal/deep import, not a publicly supported API surface.
What is publicly supported is importing Memo (and encode/decode) from the package entrypoint, since it’s part of the generated public API docs (Memo is a documented exported object in memo.ts). [1]
The only deep lib/* paths that are explicitly mentioned in dhive’s own docs are @hiveio/dhive/lib/index and @hiveio/dhive/lib/index-browser for bundling/deduplication purposes—there’s no similar documentation that blesses lib/memo. [2]
Recommended: import { Memo } from "@hiveio/dhive"; (or import { encode, decode } from "@hiveio/dhive";) rather than @hiveio/dhive/lib/memo. [1]
Sources:
[1] dhive API docs (exports include Memo, encode, decode) https://openhive-network.github.io/dhive/globals.html
[2] dhive docs (only documents deep imports for lib/index / lib/index-browser) https://openhive-network.github.io/dhive/index.html
Import Memo from the public entrypoint instead of the internal path.
Line 3 imports from @hiveio/dhive/lib/memo, which is an internal/non-documented module path. The public API surface exports Memo directly from @hiveio/dhive. Change to:
import { Memo } from "@hiveio/dhive";This protects against breaking changes if dhive's internal structure shifts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/sdk/src/modules/operations/encrypt-memo.ts` around lines 1 - 3, The
import is pulling Memo from the internal path "@hiveio/dhive/lib/memo"; update
the import statement to import Memo from the public package export instead
(i.e., import { Memo } from "@hiveio/dhive"), so replace the current
internal-path import with a public API import for Memo alongside the existing
PrivateKey and Client imports in encrypt-memo.ts to avoid relying on internal
module paths.
Summary by CodeRabbit
New Features
Bug Fixes