From 522e435b8b7d8122dbb02fefad141987b1a3e9e8 Mon Sep 17 00:00:00 2001 From: chihunmanse Date: Thu, 26 Feb 2026 15:18:13 +0900 Subject: [PATCH 01/16] user_dashboard: redesign export private key step 2 ui --- .../app/export_private_key/page.module.scss | 81 +++++++- .../src/app/export_private_key/page.tsx | 178 ++++++++++++++---- ui/oko_common_ui/src/icons/arbitrum_icon.tsx | 22 +++ ui/oko_common_ui/src/icons/base_icon.tsx | 22 +++ ui/oko_common_ui/src/icons/ethereum_icon.tsx | 2 +- ui/oko_common_ui/src/icons/initia_icon.tsx | 22 +++ ui/oko_common_ui/src/icons/rialo_icon.tsx | 22 +++ .../src/icons/solana_circle_icon.tsx | 22 +++ ui/oko_common_ui/src/icons/zigchain_icon.tsx | 22 +++ 9 files changed, 353 insertions(+), 40 deletions(-) create mode 100644 ui/oko_common_ui/src/icons/arbitrum_icon.tsx create mode 100644 ui/oko_common_ui/src/icons/base_icon.tsx create mode 100644 ui/oko_common_ui/src/icons/initia_icon.tsx create mode 100644 ui/oko_common_ui/src/icons/rialo_icon.tsx create mode 100644 ui/oko_common_ui/src/icons/solana_circle_icon.tsx create mode 100644 ui/oko_common_ui/src/icons/zigchain_icon.tsx diff --git a/apps/user_dashboard/src/app/export_private_key/page.module.scss b/apps/user_dashboard/src/app/export_private_key/page.module.scss index 7277424a7..937558ef8 100644 --- a/apps/user_dashboard/src/app/export_private_key/page.module.scss +++ b/apps/user_dashboard/src/app/export_private_key/page.module.scss @@ -109,13 +109,26 @@ /* Step 2 styles */ -.privateKeySection { +.keySection { display: flex; flex-direction: column; } -.privateKeyLabel { - margin-bottom: 12px; +.sectionHeader { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; +} + +.sectionKeyIcon { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + flex-shrink: 0; + color: var(--fg-quaternary); } .privateKeyField { @@ -172,3 +185,65 @@ height: 24px; flex-shrink: 0; } + +.chainsList { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 8px; + border-radius: 12px; +} + +.chainsTitle { + display: flex; + align-items: center; + gap: 12px; + flex-shrink: 0; +} + +.chainsSeparator { + width: 1px; + height: 12px; + background-color: var(--border-primary); + flex-shrink: 0; +} + +.chainsItems { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.chainItem { + display: flex; + align-items: center; + gap: 4px; +} + +.divider { + border: none; + border-top: 1px solid var(--border-secondary); + margin: 32px 0; +} + +.infoBox { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px; + background-color: var(--bg-secondary); + border-radius: 12px; +} + +.infoBoxTitle { + display: flex; + align-items: center; + gap: 8px; +} + +.infoBoxIcon { + width: 20px; + height: 20px; + flex-shrink: 0; +} diff --git a/apps/user_dashboard/src/app/export_private_key/page.tsx b/apps/user_dashboard/src/app/export_private_key/page.tsx index 033ff40d1..2894f9914 100644 --- a/apps/user_dashboard/src/app/export_private_key/page.tsx +++ b/apps/user_dashboard/src/app/export_private_key/page.tsx @@ -1,11 +1,20 @@ "use client"; import { Button } from "@oko-wallet/oko-common-ui/button"; +import { ArbitrumIcon } from "@oko-wallet/oko-common-ui/icons/arbitrum_icon"; +import { BaseIcon } from "@oko-wallet/oko-common-ui/icons/base_icon"; +import { CosmosIcon } from "@oko-wallet/oko-common-ui/icons/cosmos_icon"; import { DiscordIcon } from "@oko-wallet/oko-common-ui/icons/discord_icon"; +import { EthereumIcon } from "@oko-wallet/oko-common-ui/icons/ethereum_icon"; import { GoogleIcon } from "@oko-wallet/oko-common-ui/icons/google_icon"; +import { InfoCircleIcon } from "@oko-wallet/oko-common-ui/icons/info_circle"; +import { InitiaIcon } from "@oko-wallet/oko-common-ui/icons/initia_icon"; import { MailboxIcon } from "@oko-wallet/oko-common-ui/icons/mailbox"; +import { RialoIcon } from "@oko-wallet/oko-common-ui/icons/rialo_icon"; +import { SolanaCircleIcon } from "@oko-wallet/oko-common-ui/icons/solana_circle_icon"; import { TelegramIcon } from "@oko-wallet/oko-common-ui/icons/telegram_icon"; import { XIcon } from "@oko-wallet/oko-common-ui/icons/x_icon"; +import { ZigchainIcon } from "@oko-wallet/oko-common-ui/icons/zigchain_icon"; import { Typography } from "@oko-wallet/oko-common-ui/typography"; import type { AuthType } from "@oko-wallet/oko-types/auth"; import { type ReactNode, useCallback, useState } from "react"; @@ -120,6 +129,65 @@ const CopyIcon = () => { ); }; +const SectionKeyIcon = () => { + return ( + + key + + + ); +}; + +const EVM_COSMOS_CHAINS = [ + { name: "Ethereum", icon: }, + { name: "Base", icon: }, + { name: "Arbitrum", icon: }, + { name: "Cosmos Hub", icon: }, + { name: "Initia", icon: }, + { name: "Zigchain", icon: }, +]; + +const SVM_CHAINS = [ + { name: "Solana", icon: }, + { name: "Rialo", icon: }, +]; + +const ChainsList = ({ + chains, +}: { + chains: { name: string; icon: ReactNode }[]; +}) => { + return ( +
+
+ + Examples + + +
+
+ {chains.map((chain) => ( + + {chain.icon} + + {chain.name} + + + ))} +
+
+ ); +}; + const EyeOffIcon = () => { return ( - View and copy your private keys + View and copy your private key
-
- - EVM/Cosmos Private Key - + {/* EVM & Cosmos Section */} +
+
+ + + + + EVM & Cosmos + +
+
onToggleReveal("secp256k1")} @@ -283,28 +353,38 @@ const Step2Content = ({
)}
-
-
+
- + -
+
+ + +
+ +
+ + {/* Solana & SVM Section */} +
+
+ + + + + Solana & SVM + +
-
- - SVM Private Key -
onToggleReveal("ed25519")} @@ -322,7 +402,9 @@ const Step2Content = ({ weight="medium" color="secondary" className={ - revealedKeys.ed25519 ? undefined : styles.privateKeyTextBlurred + revealedKeys.ed25519 + ? undefined + : styles.privateKeyTextBlurred } > {privateKeys.ed25519} @@ -341,16 +423,40 @@ const Step2Content = ({
)}
+ +
+ + + +
+ +
-
+
- + {/* Info Box */} +
+
+ + + Why are there two keys? + +
+ + Different ecosystems use different cryptographic curves, so their + private keys are generated differently. + +
); }; diff --git a/ui/oko_common_ui/src/icons/arbitrum_icon.tsx b/ui/oko_common_ui/src/icons/arbitrum_icon.tsx new file mode 100644 index 000000000..f1080180e --- /dev/null +++ b/ui/oko_common_ui/src/icons/arbitrum_icon.tsx @@ -0,0 +1,22 @@ +import { type FC } from "react"; + +import { s3BucketURL } from "./paths"; + +export const ArbitrumIcon: FC = ({ + width = 16, + height = 16, +}) => { + return ( + arbitrum_icon + ); +}; + +export interface ArbitrumIconProps { + width?: number; + height?: number; +} diff --git a/ui/oko_common_ui/src/icons/base_icon.tsx b/ui/oko_common_ui/src/icons/base_icon.tsx new file mode 100644 index 000000000..599988a23 --- /dev/null +++ b/ui/oko_common_ui/src/icons/base_icon.tsx @@ -0,0 +1,22 @@ +import { type FC } from "react"; + +import { s3BucketURL } from "./paths"; + +export const BaseIcon: FC = ({ + width = 16, + height = 16, +}) => { + return ( + base_icon + ); +}; + +export interface BaseIconProps { + width?: number; + height?: number; +} diff --git a/ui/oko_common_ui/src/icons/ethereum_icon.tsx b/ui/oko_common_ui/src/icons/ethereum_icon.tsx index 01b357297..21081ca08 100644 --- a/ui/oko_common_ui/src/icons/ethereum_icon.tsx +++ b/ui/oko_common_ui/src/icons/ethereum_icon.tsx @@ -8,7 +8,7 @@ export const EthereumIcon: FC = ({ }) => { return ( ethereum_icon = ({ + width = 16, + height = 16, +}) => { + return ( + initia_icon + ); +}; + +export interface InitiaIconProps { + width?: number; + height?: number; +} diff --git a/ui/oko_common_ui/src/icons/rialo_icon.tsx b/ui/oko_common_ui/src/icons/rialo_icon.tsx new file mode 100644 index 000000000..b913f8974 --- /dev/null +++ b/ui/oko_common_ui/src/icons/rialo_icon.tsx @@ -0,0 +1,22 @@ +import { type FC } from "react"; + +import { s3BucketURL } from "./paths"; + +export const RialoIcon: FC = ({ + width = 16, + height = 16, +}) => { + return ( + rialo_icon + ); +}; + +export interface RialoIconProps { + width?: number; + height?: number; +} diff --git a/ui/oko_common_ui/src/icons/solana_circle_icon.tsx b/ui/oko_common_ui/src/icons/solana_circle_icon.tsx new file mode 100644 index 000000000..8d60af4d3 --- /dev/null +++ b/ui/oko_common_ui/src/icons/solana_circle_icon.tsx @@ -0,0 +1,22 @@ +import { type FC } from "react"; + +import { s3BucketURL } from "./paths"; + +export const SolanaCircleIcon: FC = ({ + width = 16, + height = 16, +}) => { + return ( + solana_circle_icon + ); +}; + +export interface SolanaCircleIconProps { + width?: number; + height?: number; +} diff --git a/ui/oko_common_ui/src/icons/zigchain_icon.tsx b/ui/oko_common_ui/src/icons/zigchain_icon.tsx new file mode 100644 index 000000000..e63a5f78b --- /dev/null +++ b/ui/oko_common_ui/src/icons/zigchain_icon.tsx @@ -0,0 +1,22 @@ +import { type FC } from "react"; + +import { s3BucketURL } from "./paths"; + +export const ZigchainIcon: FC = ({ + width = 16, + height = 16, +}) => { + return ( + zigchain_icon + ); +}; + +export interface ZigchainIconProps { + width?: number; + height?: number; +} From 26f4a948c86b3cd9b8a5de3ecb8b416ecaf07209 Mon Sep 17 00:00:00 2001 From: chihunmanse Date: Thu, 26 Feb 2026 15:49:59 +0900 Subject: [PATCH 02/16] attached: keep export keys in attached iframe memory --- .../src/app/export_private_key/page.tsx | 3 +-- .../src/window_msgs/export_key_store.ts | 18 ++++++++++++++++++ .../src/window_msgs/export_private_key.ts | 15 +++++++-------- .../src/types/msg/export_priv_key.ts | 2 +- 4 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 embed/oko_attached/src/window_msgs/export_key_store.ts diff --git a/apps/user_dashboard/src/app/export_private_key/page.tsx b/apps/user_dashboard/src/app/export_private_key/page.tsx index 2894f9914..674b074fe 100644 --- a/apps/user_dashboard/src/app/export_private_key/page.tsx +++ b/apps/user_dashboard/src/app/export_private_key/page.tsx @@ -495,7 +495,7 @@ const Page = () => { secp256k1: false, ed25519: false, }); - const [privateKeys, setPrivateKeys] = useState<{ + const [privateKeys] = useState<{ secp256k1: string; ed25519: string; } | null>(null); @@ -586,7 +586,6 @@ const Page = () => { resAny.msg_type === "__export_private_key_ack__" && resAny.payload.success ) { - setPrivateKeys(resAny.payload.data); setStep(2); } else { const errorType = !resAny.payload.success diff --git a/embed/oko_attached/src/window_msgs/export_key_store.ts b/embed/oko_attached/src/window_msgs/export_key_store.ts new file mode 100644 index 000000000..847cfebad --- /dev/null +++ b/embed/oko_attached/src/window_msgs/export_key_store.ts @@ -0,0 +1,18 @@ +export interface ExportedKeys { + secp256k1: string; + ed25519: string; +} + +let storedKeys: ExportedKeys | null = null; + +export function setExportedKeys(keys: ExportedKeys): void { + storedKeys = keys; +} + +export function getExportedKeys(): ExportedKeys | null { + return storedKeys; +} + +export function clearExportedKeys(): void { + storedKeys = null; +} diff --git a/embed/oko_attached/src/window_msgs/export_private_key.ts b/embed/oko_attached/src/window_msgs/export_private_key.ts index 4582d5a3c..740b23820 100644 --- a/embed/oko_attached/src/window_msgs/export_private_key.ts +++ b/embed/oko_attached/src/window_msgs/export_private_key.ts @@ -11,6 +11,7 @@ import type { } from "@oko-wallet/oko-types/user"; import bs58 from "bs58"; +import { setExportedKeys } from "./export_key_store"; import { type ReAuthCredentials, setReAuthResolver, @@ -194,15 +195,13 @@ export async function handleExportPrivateKey( keypairBytes.set(pubkeyBytes, seedBytes.length); const ed25519Keypair = bs58.encode(keypairBytes); - // 9. Return result - console.log(`${LOG_PREFIX} export complete`); - sendAck({ - success: true, - data: { - secp256k1: secp256k1PrivateKey, - ed25519: ed25519Keypair, - }, + // 9. Store keys in module-level memory and signal success (no key data sent) + setExportedKeys({ + secp256k1: secp256k1PrivateKey, + ed25519: ed25519Keypair, }); + console.log(`${LOG_PREFIX} export complete, keys stored`); + sendAck({ success: true }); } catch (err) { console.error(`${LOG_PREFIX} unexpected error`, err); sendAck({ diff --git a/sdk/oko_sdk_core/src/types/msg/export_priv_key.ts b/sdk/oko_sdk_core/src/types/msg/export_priv_key.ts index d0551ed19..f45482196 100644 --- a/sdk/oko_sdk_core/src/types/msg/export_priv_key.ts +++ b/sdk/oko_sdk_core/src/types/msg/export_priv_key.ts @@ -16,7 +16,7 @@ export type ExportPrivateKeyError = | { type: "REAUTH_ERROR"; error: string }; export type ExportPrivateKeyAckPayload = - | { success: true; data: { secp256k1: string; ed25519: string } } + | { success: true } | { success: false; error: ExportPrivateKeyError }; export interface OkoWalletMsgExportPrivateKeyAck { From e71133c8c99577f918cb388b36420c4e70afb196 Mon Sep 17 00:00:00 2001 From: chihunmanse Date: Thu, 26 Feb 2026 15:57:44 +0900 Subject: [PATCH 03/16] attached: add export key display route --- .../export/export_display.module.scss | 117 ++++++++++++ .../src/components/export/export_display.tsx | 179 ++++++++++++++++++ embed/oko_attached/src/routeTree.gen.ts | 21 ++ .../src/routes/export/display/index.tsx | 7 + 4 files changed, 324 insertions(+) create mode 100644 embed/oko_attached/src/components/export/export_display.module.scss create mode 100644 embed/oko_attached/src/components/export/export_display.tsx create mode 100644 embed/oko_attached/src/routes/export/display/index.tsx diff --git a/embed/oko_attached/src/components/export/export_display.module.scss b/embed/oko_attached/src/components/export/export_display.module.scss new file mode 100644 index 000000000..8a71072ee --- /dev/null +++ b/embed/oko_attached/src/components/export/export_display.module.scss @@ -0,0 +1,117 @@ +:root { + --bg-secondary: #fafafa; + --bg-secondary-hover: #f5f5f5; + --text-primary: #181d27; + --text-secondary: #414651; + --fg-primary: #181d27; +} + +.container { + width: 100%; + display: flex; + flex-direction: column; +} + +.privateKeyField { + position: relative; + min-height: 68px; + width: 100%; + cursor: pointer; + + &:hover .privateKeyBg { + background-color: var(--bg-secondary-hover, #f5f5f5); + } +} + +.privateKeyBg { + display: flex; + align-items: center; + justify-content: center; + padding: 12px 16px; + background-color: var(--bg-secondary); + border-radius: 12px; + overflow: hidden; + word-break: break-all; +} + +.privateKeyText { + flex: 1; + font-family: var(--font-family-body, Inter, sans-serif); + font-weight: 500; + font-size: var(--font-size-text-md, 15px); + line-height: var(--line-height-text-md, 22px); + color: var(--text-secondary); +} + +.privateKeyTextBlurred { + filter: blur(4px); + opacity: 0.5; +} + +.privateKeyHint { + position: absolute; + inset: 0; + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; +} + +.eyeOffIcon { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + flex-shrink: 0; + color: var(--fg-primary); +} + +.hintText { + flex: 1; + font-family: var(--font-family-body, Inter, sans-serif); + font-weight: 500; + font-size: var(--font-size-text-md, 15px); + line-height: var(--line-height-text-md, 22px); + color: var(--text-primary); +} + +.copyButton { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + width: 100%; + padding: 10px 16px; + border: 2px solid rgba(255, 255, 255, 0.12); + border-radius: 8px; + background-color: #181d27; + color: white; + cursor: pointer; + font-family: var(--font-family-body, Inter, sans-serif); + font-weight: 600; + font-size: var(--font-size-text-md, 15px); + line-height: var(--line-height-text-md, 22px); + box-shadow: 0 1px 2px 0 rgba(10, 13, 18, 0.05); +} + +.copyButton:hover { + opacity: 0.9; +} + +.copyButtonIcon { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + flex-shrink: 0; +} + +.error { + padding: 16px; + text-align: center; + font-family: var(--font-family-body, Inter, sans-serif); + font-size: var(--font-size-text-sm, 14px); + color: var(--text-secondary); +} diff --git a/embed/oko_attached/src/components/export/export_display.tsx b/embed/oko_attached/src/components/export/export_display.tsx new file mode 100644 index 000000000..319ba980b --- /dev/null +++ b/embed/oko_attached/src/components/export/export_display.tsx @@ -0,0 +1,179 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +import { + type ExportedKeys, + getExportedKeys, +} from "@oko-wallet-attached/window_msgs/export_key_store"; + +import styles from "./export_display.module.scss"; + +type KeyType = "secp256k1" | "ed25519"; + +const PARENT_MSG_TARGET = "oko_user_dashboard"; + +function postToParent(msgType: string, data?: Record) { + window.parent.postMessage( + { target: PARENT_MSG_TARGET, msg_type: msgType, ...data }, + "*", + ); +} + +const EyeOffIcon = () => { + return ( + + hidden + + + ); +}; + +const CopyIcon = () => { + return ( + + copy + + + ); +}; + +export const ExportDisplay = () => { + const params = new URLSearchParams(window.location.search); + const keyType = params.get("key_type") as KeyType | null; + + const [revealed, setRevealed] = useState(false); + const [keys, setKeys] = useState(null); + const [error, setError] = useState(null); + const containerRef = useRef(null); + + // Read keys from same-origin module store + useEffect(() => { + const stored = getExportedKeys(); + if (!stored) { + setError("No exported keys found."); + return; + } + if (!keyType || !(keyType in stored)) { + setError(`Invalid key_type: ${keyType}`); + return; + } + setKeys(stored); + }, [keyType]); + + // ResizeObserver → notify parent of height changes + useEffect(() => { + const el = containerRef.current; + if (!el) { + return; + } + + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + postToParent("__export_display_resize__", { + height: entry.contentRect.height, + key_type: keyType, + }); + } + }); + observer.observe(el); + + return () => { + observer.disconnect(); + }; + }, [keyType]); + + const handleToggleReveal = useCallback(() => { + setRevealed((prev) => !prev); + }, []); + + const handleCopy = useCallback(async () => { + if (!keys || !keyType) { + return; + } + const keyValue = keys[keyType]; + try { + await navigator.clipboard.writeText(keyValue); + postToParent("__export_display_copied__", { key_type: keyType }); + } catch { + postToParent("__export_display_copy_failed__", { key_type: keyType }); + } + }, [keys, keyType]); + + if (error) { + return
{error}
; + } + + if (!keys || !keyType) { + return null; + } + + const keyValue = keys[keyType]; + + return ( +
+
{ + if (e.key === "Enter" || e.key === " ") { + handleToggleReveal(); + } + }} + > +
+ + {keyValue} + +
+ {!revealed && ( +
+ + + + + Click or tap to reveal your private key. +
+ Ensure no one else can see your screen. +
+
+ )} +
+ +
+ + +
+ ); +}; diff --git a/embed/oko_attached/src/routeTree.gen.ts b/embed/oko_attached/src/routeTree.gen.ts index 1994b25bf..2908582b9 100644 --- a/embed/oko_attached/src/routeTree.gen.ts +++ b/embed/oko_attached/src/routeTree.gen.ts @@ -16,6 +16,7 @@ import { Route as XCallbackIndexRouteImport } from './routes/x/callback/index' import { Route as TelegramCallbackIndexRouteImport } from './routes/telegram/callback/index' import { Route as GoogleCallbackIndexRouteImport } from './routes/google/callback/index' import { Route as ExportReauthIndexRouteImport } from './routes/export/reauth/index' +import { Route as ExportDisplayIndexRouteImport } from './routes/export/display/index' import { Route as EmailCallbackIndexRouteImport } from './routes/email/callback/index' import { Route as DiscordCallbackIndexRouteImport } from './routes/discord/callback/index' @@ -54,6 +55,11 @@ const ExportReauthIndexRoute = ExportReauthIndexRouteImport.update({ path: '/export/reauth/', getParentRoute: () => rootRouteImport, } as any) +const ExportDisplayIndexRoute = ExportDisplayIndexRouteImport.update({ + id: '/export/display/', + path: '/export/display/', + getParentRoute: () => rootRouteImport, +} as any) const EmailCallbackIndexRoute = EmailCallbackIndexRouteImport.update({ id: '/email/callback/', path: '/email/callback/', @@ -71,6 +77,7 @@ export interface FileRoutesByFullPath { '/telegram/': typeof TelegramIndexRoute '/discord/callback/': typeof DiscordCallbackIndexRoute '/email/callback/': typeof EmailCallbackIndexRoute + '/export/display/': typeof ExportDisplayIndexRoute '/export/reauth/': typeof ExportReauthIndexRoute '/google/callback/': typeof GoogleCallbackIndexRoute '/telegram/callback/': typeof TelegramCallbackIndexRoute @@ -82,6 +89,7 @@ export interface FileRoutesByTo { '/telegram': typeof TelegramIndexRoute '/discord/callback': typeof DiscordCallbackIndexRoute '/email/callback': typeof EmailCallbackIndexRoute + '/export/display': typeof ExportDisplayIndexRoute '/export/reauth': typeof ExportReauthIndexRoute '/google/callback': typeof GoogleCallbackIndexRoute '/telegram/callback': typeof TelegramCallbackIndexRoute @@ -94,6 +102,7 @@ export interface FileRoutesById { '/telegram/': typeof TelegramIndexRoute '/discord/callback/': typeof DiscordCallbackIndexRoute '/email/callback/': typeof EmailCallbackIndexRoute + '/export/display/': typeof ExportDisplayIndexRoute '/export/reauth/': typeof ExportReauthIndexRoute '/google/callback/': typeof GoogleCallbackIndexRoute '/telegram/callback/': typeof TelegramCallbackIndexRoute @@ -107,6 +116,7 @@ export interface FileRouteTypes { | '/telegram/' | '/discord/callback/' | '/email/callback/' + | '/export/display/' | '/export/reauth/' | '/google/callback/' | '/telegram/callback/' @@ -118,6 +128,7 @@ export interface FileRouteTypes { | '/telegram' | '/discord/callback' | '/email/callback' + | '/export/display' | '/export/reauth' | '/google/callback' | '/telegram/callback' @@ -129,6 +140,7 @@ export interface FileRouteTypes { | '/telegram/' | '/discord/callback/' | '/email/callback/' + | '/export/display/' | '/export/reauth/' | '/google/callback/' | '/telegram/callback/' @@ -141,6 +153,7 @@ export interface RootRouteChildren { TelegramIndexRoute: typeof TelegramIndexRoute DiscordCallbackIndexRoute: typeof DiscordCallbackIndexRoute EmailCallbackIndexRoute: typeof EmailCallbackIndexRoute + ExportDisplayIndexRoute: typeof ExportDisplayIndexRoute ExportReauthIndexRoute: typeof ExportReauthIndexRoute GoogleCallbackIndexRoute: typeof GoogleCallbackIndexRoute TelegramCallbackIndexRoute: typeof TelegramCallbackIndexRoute @@ -198,6 +211,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ExportReauthIndexRouteImport parentRoute: typeof rootRouteImport } + '/export/display/': { + id: '/export/display/' + path: '/export/display' + fullPath: '/export/display/' + preLoaderRoute: typeof ExportDisplayIndexRouteImport + parentRoute: typeof rootRouteImport + } '/email/callback/': { id: '/email/callback/' path: '/email/callback' @@ -221,6 +241,7 @@ const rootRouteChildren: RootRouteChildren = { TelegramIndexRoute: TelegramIndexRoute, DiscordCallbackIndexRoute: DiscordCallbackIndexRoute, EmailCallbackIndexRoute: EmailCallbackIndexRoute, + ExportDisplayIndexRoute: ExportDisplayIndexRoute, ExportReauthIndexRoute: ExportReauthIndexRoute, GoogleCallbackIndexRoute: GoogleCallbackIndexRoute, TelegramCallbackIndexRoute: TelegramCallbackIndexRoute, diff --git a/embed/oko_attached/src/routes/export/display/index.tsx b/embed/oko_attached/src/routes/export/display/index.tsx new file mode 100644 index 000000000..e8a9a76e5 --- /dev/null +++ b/embed/oko_attached/src/routes/export/display/index.tsx @@ -0,0 +1,7 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { ExportDisplay } from "@oko-wallet-attached/components/export/export_display"; + +export const Route = createFileRoute("/export/display/")({ + component: ExportDisplay, +}); From bf94cd85705bcf06d9687ebe5baf32581e82283d Mon Sep 17 00:00:00 2001 From: chihunmanse Date: Thu, 26 Feb 2026 16:18:05 +0900 Subject: [PATCH 04/16] user_dashboard: render export keys in attached iframe --- .../app/export_private_key/page.module.scss | 55 +--- .../src/app/export_private_key/page.tsx | 234 +++++------------- .../export/export_display.module.scss | 7 + .../src/components/export/export_display.tsx | 39 ++- .../src/window_msgs/export_key_store.ts | 35 +++ 5 files changed, 130 insertions(+), 240 deletions(-) diff --git a/apps/user_dashboard/src/app/export_private_key/page.module.scss b/apps/user_dashboard/src/app/export_private_key/page.module.scss index 937558ef8..e442e7585 100644 --- a/apps/user_dashboard/src/app/export_private_key/page.module.scss +++ b/apps/user_dashboard/src/app/export_private_key/page.module.scss @@ -131,59 +131,10 @@ color: var(--fg-quaternary); } -.privateKeyField { - position: relative; - min-height: 68px; +.keyIframe { width: 100%; - cursor: pointer; - - &:hover .privateKeyBg { - background-color: var(--bg-secondary-hover, #f5f5f5); - } -} - -.privateKeyBg { - display: flex; - align-items: center; - justify-content: center; - padding: 12px 16px; - background-color: var(--bg-secondary); - border-radius: 12px; - overflow: hidden; - word-break: break-all; -} - -.privateKeyTextBlurred { - filter: blur(4px); - opacity: 0.5; -} - -.privateKeyHint { - position: absolute; - inset: 0; - display: flex; - align-items: center; - gap: 12px; - padding: 12px 16px; -} - -.eyeOffIcon { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - flex-shrink: 0; - color: var(--fg-primary); -} - -.copyButtonIcon { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - flex-shrink: 0; + border: none; + display: block; } .chainsList { diff --git a/apps/user_dashboard/src/app/export_private_key/page.tsx b/apps/user_dashboard/src/app/export_private_key/page.tsx index 674b074fe..6dfc47680 100644 --- a/apps/user_dashboard/src/app/export_private_key/page.tsx +++ b/apps/user_dashboard/src/app/export_private_key/page.tsx @@ -17,12 +17,11 @@ import { XIcon } from "@oko-wallet/oko-common-ui/icons/x_icon"; import { ZigchainIcon } from "@oko-wallet/oko-common-ui/icons/zigchain_icon"; import { Typography } from "@oko-wallet/oko-common-ui/typography"; import type { AuthType } from "@oko-wallet/oko-types/auth"; -import { type ReactNode, useCallback, useState } from "react"; +import { type ReactNode, useCallback, useEffect, useState } from "react"; import type { OkoWalletMsgExportPrivateKeyAck } from "../../../../../sdk/oko_sdk_core/dist/types"; import styles from "./page.module.scss"; import { displayToast } from "@oko-wallet-user-dashboard/components/toast"; -import { useCopyToClipboard } from "@oko-wallet-user-dashboard/hooks/use_copy_to_clipboard"; import { selectCosmosSDK, useSDKState, @@ -111,24 +110,6 @@ const KeyIcon = () => { ); }; -const CopyIcon = () => { - return ( - - copy - - - ); -}; - const SectionKeyIcon = () => { return ( { - return ( - - eye off - - - ); -}; - const Step1Content = ({ authInfo, displayIdentifier, @@ -286,16 +249,46 @@ const Step1Content = ({ }; const Step2Content = ({ - privateKeys, - revealedKeys, - onToggleReveal, - onCopy, + attachedOrigin, }: { - privateKeys: { secp256k1: string; ed25519: string }; - revealedKeys: { secp256k1: boolean; ed25519: boolean }; - onToggleReveal: (key: "secp256k1" | "ed25519") => void; - onCopy: (key: string) => void; + attachedOrigin: string; }) => { + const [secpIframeHeight, setSecpIframeHeight] = useState(0); + const [edIframeHeight, setEdIframeHeight] = useState(0); + + useEffect(() => { + const handler = (event: MessageEvent) => { + if (event.origin !== attachedOrigin) { + return; + } + const { data } = event; + if (data?.target !== "oko_user_dashboard") { + return; + } + + if (data.msg_type === "__export_display_resize__") { + if (data.key_type === "secp256k1") { + setSecpIframeHeight(data.height); + } else if (data.key_type === "ed25519") { + setEdIframeHeight(data.height); + } + } else if (data.msg_type === "__export_display_copied__") { + displayToast({ variant: "success", title: "Copied!" }); + } else if (data.msg_type === "__export_display_copy_failed__") { + displayToast({ + variant: "confirm", + title: "Copy Failed", + description: "Could not copy to clipboard.", + }); + } + }; + + window.addEventListener("message", handler); + return () => { + window.removeEventListener("message", handler); + }; + }, [attachedOrigin]); + return ( <> @@ -315,57 +308,13 @@ const Step2Content = ({
-
onToggleReveal("secp256k1")} - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - onToggleReveal("secp256k1"); - } - }} - > -
- - {privateKeys.secp256k1} - -
- {!revealedKeys.secp256k1 && ( -
- - - - - Click or tap to reveal your private key. -
- Ensure no one else can see your screen. -
-
- )} -
- -
- - +