@gluwa/connect-kit 0.2.0-next.7 → 0.2.0-next.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,13 +9,6 @@
9
9
  padding-bottom: 32px;
10
10
  }
11
11
 
12
- // .ck-qr-group {
13
- // display: flex;
14
- // flex-direction: column;
15
- // align-items: center;
16
- // gap: 24px;
17
- // }
18
-
19
12
  // ── Text ──────────────────────────────────────────────────────────────────────
20
13
  .ck-qr-text {
21
14
  display: flex;
@@ -42,18 +35,16 @@
42
35
  outline: none;
43
36
  }
44
37
 
45
- .ck-qr-loading {
46
- border: 1px solid var(--semantic-divider-divider-1);
47
- }
48
-
49
- .ck-qr-timeout {
38
+ .ck-qr-skeleton {
39
+ padding: 20px;
50
40
  border: 1px solid var(--semantic-divider-divider-1);
41
+ border-radius: 20px;
51
42
  }
52
43
 
53
- // ── Skeleton ──────────────────────────────────────────────────────────────────
54
- .ck-qr-skeleton {
55
- width: 248px;
56
- height: 248px;
44
+ .ck-qr-skeleton-inner {
45
+ display: block;
46
+ width: 246px;
47
+ height: 246px;
57
48
  background: linear-gradient(
58
49
  132deg,
59
50
  var(--scale-gray-100) 36%,
@@ -64,8 +55,24 @@
64
55
  animation: ck-qr-shimmer 1.5s linear infinite;
65
56
 
66
57
  @include tablet {
67
- width: 200px;
68
- height: 200px;
58
+ width: 198px;
59
+ height: 198px;
60
+ }
61
+ }
62
+
63
+ // ── Timeout ───────────────────────────────────────────────────────────────────
64
+ .ck-qr-timeout {
65
+ width: 288px;
66
+ height: 288px;
67
+ border: 1px solid var(--semantic-divider-divider-1);
68
+ background: var(--scale-gray-100);
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+
73
+ @include tablet {
74
+ width: 240px;
75
+ height: 240px;
69
76
  }
70
77
  }
71
78
 
@@ -93,21 +100,6 @@
93
100
  }
94
101
  }
95
102
 
96
- // ── Timeout ───────────────────────────────────────────────────────────────────
97
- .ck-qr-timeout {
98
- width: 248px;
99
- height: 248px;
100
- background: var(--scale-gray-100);
101
- display: flex;
102
- align-items: center;
103
- justify-content: center;
104
-
105
- @include tablet {
106
- width: 200px;
107
- height: 200px;
108
- }
109
- }
110
-
111
103
  .ck-qr-timeout-inner {
112
104
  display: flex;
113
105
  flex-direction: column;
@@ -107,6 +107,12 @@ export const creditConnectConnector = (
107
107
  }
108
108
  });
109
109
 
110
+ manager.on('sessionExpired', (payload) => {
111
+ // eslint-disable-next-line no-console
112
+ console.log('[CC connector] session expired → emit session_expired', payload);
113
+ emitWagmiMessage?.('session_expired', payload);
114
+ });
115
+
110
116
  return manager;
111
117
  };
112
118
 
@@ -0,0 +1,82 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { type ConnectorId } from '../types';
3
+
4
+ // MM / CC 폴백용 기본 타임아웃. (CC 는 원칙적으로 서버 push 로 만료가 오지만,
5
+ // 서버 값이 없거나 신호가 유실된 경우를 대비한 폴백 용도로도 쓸 수 있음.)
6
+ export const DEFAULT_CONNECT_TIMEOUT_MS = 600_000;
7
+
8
+ interface UseConnectTimeoutParams {
9
+ /** 현재 연결 시도 중인 커넥터. null = 유휴 상태. */
10
+ connectorId: ConnectorId | null;
11
+ /** 타임아웃 감시 활성화 여부 — pending/QR 표시 중일 때 true. */
12
+ active: boolean;
13
+ /**
14
+ * 외부 이벤트 기반 만료 신호 (MM 제외). 값이 증가할 때마다 "만료 발생" 으로 간주.
15
+ * - CC: dialog 가 connector 의 `session_expired`(서버 push) 를 받아 올림.
16
+ * - WC: dialog 가 WC connect promise reject(프로토콜 ~5분 만료 등) 를 받아 올림.
17
+ */
18
+ expiredSignal?: number;
19
+ /** "Try Again" 시 실행할 재연결 로직 (dialog 가 주입). */
20
+ onRetry?: () => void;
21
+ }
22
+
23
+ interface UseConnectTimeoutResult {
24
+ /** 타임아웃 발생 여부 — true 면 dialog 가 <Timeout> 을 렌더. */
25
+ timedOut: boolean;
26
+ /** "Try Again" 버튼에 연결할 함수 — 상태 리셋 + onRetry 실행. */
27
+ retry: () => void;
28
+ }
29
+
30
+ /**
31
+ * 커넥터별로 타임아웃 신호원을 분기해 하나의 `timedOut` 으로 수렴시키는 훅.
32
+ * - CC: 서버 push (`OnSessionExpired` → connector → expiredSignal).
33
+ * - MM: 네이티브 타임아웃 없음 → client 타이머(DEFAULT_CONNECT_TIMEOUT_MS).
34
+ * - WC: 프로토콜 자체 ~5분 proposal reject → connect promise reject → expiredSignal.
35
+ */
36
+ export const useConnectTimeout = ({
37
+ connectorId,
38
+ active,
39
+ expiredSignal = 0,
40
+ onRetry,
41
+ }: UseConnectTimeoutParams): UseConnectTimeoutResult => {
42
+ const [timedOut, setTimedOut] = useState(false);
43
+
44
+ const fire = useCallback(() => {
45
+ setTimedOut(true);
46
+ }, []);
47
+
48
+ // 커넥터가 바뀌면(=새 연결 시도) 타임아웃 상태 리셋.
49
+ // active 가 false 로 떨어질 때는 리셋하지 않는다 — timeout UI 표시 중 상태가 날아가지 않도록.
50
+ useEffect(() => {
51
+ setTimedOut(false);
52
+ }, [connectorId]);
53
+
54
+ // MM 만 client 타이머로 강제 (승인 대기에 네이티브 상한이 없음).
55
+ // CC(서버 push) / WC(connect reject) 는 아래 expiredSignal effect 에서 처리.
56
+ useEffect(() => {
57
+ if (!active || connectorId !== 'METAMASK') return;
58
+ const timer = setTimeout(fire, DEFAULT_CONNECT_TIMEOUT_MS);
59
+ return () => clearTimeout(timer);
60
+ }, [active, connectorId, fire]);
61
+
62
+ // 외부 만료 신호 처리 (CC 서버 push / WC connect reject) — MM 은 client 타이머라 제외.
63
+ // signal 값이 올라가면(새 만료 이벤트) 발화.
64
+ const prevSignal = useRef(expiredSignal);
65
+ useEffect(() => {
66
+ if (!active || connectorId === 'METAMASK' || !connectorId) {
67
+ prevSignal.current = expiredSignal;
68
+ return;
69
+ }
70
+ if (expiredSignal !== prevSignal.current) {
71
+ prevSignal.current = expiredSignal;
72
+ fire();
73
+ }
74
+ }, [expiredSignal, connectorId, active, fire]);
75
+
76
+ const retry = useCallback(() => {
77
+ setTimedOut(false);
78
+ onRetry?.();
79
+ }, [onRetry]);
80
+
81
+ return { timedOut, retry };
82
+ };
@@ -0,0 +1,29 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ const METAMASK_RDNS = 'io.metamask';
4
+
5
+ // EIP-6963 응답의 rdns 로 MetaMask 확장을 식별.
6
+ const isMetaMaskAnnouncement = (event: Event): boolean => {
7
+ const detail = (event as CustomEvent<{ info?: { rdns?: string }; provider?: unknown }>).detail;
8
+ return detail?.info?.rdns?.toLowerCase() === METAMASK_RDNS && detail.provider != null;
9
+ };
10
+
11
+ export const useMetaMaskExtension = (): boolean => {
12
+ const [isInstalled, setIsInstalled] = useState(false);
13
+
14
+ useEffect(() => {
15
+ const handleAnnouncement = (event: Event): void => {
16
+ if (isMetaMaskAnnouncement(event)) setIsInstalled(true);
17
+ };
18
+
19
+ // 응답 listener를 먼저 등록한 뒤 설치된 provider 목록을 요청.
20
+ window.addEventListener('eip6963:announceProvider', handleAnnouncement);
21
+ window.dispatchEvent(new Event('eip6963:requestProvider'));
22
+
23
+ return () => {
24
+ window.removeEventListener('eip6963:announceProvider', handleAnnouncement);
25
+ };
26
+ }, []);
27
+
28
+ return isInstalled;
29
+ };
@@ -1,15 +1,13 @@
1
1
  import { useState, useRef, useCallback, useEffect } from 'react';
2
2
  import type { WCWallet, WCSubView } from '../types';
3
-
4
- // WalletConnect Explorer 등록된 Credit Wallet 의 정식 wallet name (API 응답 그대로).
5
- // CONNECTOR_META.CREDIT_CONNECT.label 은 selector button 표시용 ('Continue with Credit Wallet')
6
- // 이라 다름 — 별도 상수로 분리.
7
- const WC_EXPLORER_CREDIT_WALLET_NAME = 'Credit Wallet';
3
+ // TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
4
+ import { CREDIT_WALLET_LISTING_ID, overrideWalletDisplay } from '../wallet-display-override';
8
5
 
9
6
  const sortCreditWalletFirst = (list: WCWallet[]): WCWallet[] =>
10
7
  [...list].sort((a, b) => {
11
- if (a.name === WC_EXPLORER_CREDIT_WALLET_NAME) return -1;
12
- if (b.name === WC_EXPLORER_CREDIT_WALLET_NAME) return 1;
8
+ // TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
9
+ if (a.id === CREDIT_WALLET_LISTING_ID) return -1;
10
+ if (b.id === CREDIT_WALLET_LISTING_ID) return 1;
13
11
  return 0;
14
12
  });
15
13
 
@@ -115,7 +113,8 @@ export const useWCState = (): WCState => {
115
113
  setWalletListLoading(true);
116
114
  try {
117
115
  const list = await fetchWCWalletList(projectId, controller.signal);
118
- const sorted = sortCreditWalletFirst(list);
116
+ // TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
117
+ const sorted = sortCreditWalletFirst(list).map(overrideWalletDisplay);
119
118
  // Credit Wallet 을 최상단으로 정렬한 상태로 state 저장 — view 측은 정렬 신경 X.
120
119
  if (isMountedRef.current) setWalletList(sorted);
121
120
  } catch (err) {
@@ -13,15 +13,10 @@ const hasMetaMaskKeyword = (value: string): boolean => value.toLowerCase().inclu
13
13
  const hasWalletConnectKeyword = (value: string): boolean =>
14
14
  value.toLowerCase().includes('walletconnect');
15
15
 
16
+ // 전역 provider 대신 wagmi connector 메타데이터로 MetaMask를 판별.
16
17
  const isMetaMaskConnector = (connector: WagmiConnectorLike): boolean => {
17
18
  if (connector.id === 'metaMaskSDK') return true;
18
19
 
19
- if (connector.id === 'injected') {
20
- if (typeof window === 'undefined') return false;
21
- const eth = (window as Window & { ethereum?: { isMetaMask?: boolean } }).ethereum;
22
- return eth?.isMetaMask === true;
23
- }
24
-
25
20
  if (hasMetaMaskKeyword(connector.id) || hasMetaMaskKeyword(connector.name)) return true;
26
21
 
27
22
  if (connector.rdns) {
@@ -61,6 +56,7 @@ interface Options {
61
56
  onConnect: (result: ConnectResult) => void;
62
57
  onError: (error: Error, connectorId: ConnectorId) => void;
63
58
  onQrUri: (uri: string | null) => void;
59
+ onSessionExpired?: () => void;
64
60
  }
65
61
 
66
62
  interface UseWagmiConnectResult {
@@ -73,6 +69,7 @@ export const useWagmiConnect = ({
73
69
  onConnect,
74
70
  onError,
75
71
  onQrUri,
72
+ onSessionExpired,
76
73
  }: Options): UseWagmiConnectResult => {
77
74
  const config = useConfig();
78
75
  const pendingConnectorId = useRef<ConnectorId | null>(null);
@@ -81,11 +78,13 @@ export const useWagmiConnect = ({
81
78
  const onConnectRef = useRef(onConnect);
82
79
  const onErrorRef = useRef(onError);
83
80
  const onQrUriRef = useRef(onQrUri);
81
+ const onSessionExpiredRef = useRef(onSessionExpired);
84
82
  useEffect(() => {
85
83
  onConnectRef.current = onConnect;
86
84
  onErrorRef.current = onError;
87
85
  onQrUriRef.current = onQrUri;
88
- }, [onConnect, onError, onQrUri]);
86
+ onSessionExpiredRef.current = onSessionExpired;
87
+ }, [onConnect, onError, onQrUri, onSessionExpired]);
89
88
 
90
89
  const { connectAsync, reset, isPending } = useConnect();
91
90
 
@@ -95,6 +94,9 @@ export const useWagmiConnect = ({
95
94
  if (msg?.type === 'display_uri' && typeof msg.data === 'string') {
96
95
  onQrUriRef.current(msg.data);
97
96
  }
97
+ if (msg?.type === 'session_expired') {
98
+ onSessionExpiredRef.current?.();
99
+ }
98
100
  };
99
101
 
100
102
  const unsubscribers = config.connectors.map((connector) => {
@@ -42,7 +42,7 @@ export const AllWallets: FC<AllWalletsProps> = ({ wallets, isLoading, onSelectWa
42
42
  </div>
43
43
  {!isLoading && filtered.length === 0 ? (
44
44
  <div className="ck-all-wallets-empty" role="status">
45
- <p>No wallets found</p>
45
+ <p>No Wallet found</p>
46
46
  </div>
47
47
  ) : (
48
48
  <WalletGrid wallets={filtered} isLoading={isLoading} onSelect={onSelectWallet} />
@@ -19,13 +19,8 @@
19
19
  gap: 8px;
20
20
  flex-shrink: 0;
21
21
 
22
- @include max-width(392px) {
23
- flex-direction: column;
24
- align-items: flex-end;
25
- gap: 12px;
26
- .ck-search {
27
- width: 100%;
28
- }
22
+ .ck-search {
23
+ min-width: 0;
29
24
  }
30
25
  }
31
26
 
@@ -12,14 +12,11 @@ import { SelectorPanel } from '../selectorPanel';
12
12
  import { DetailPanel } from '../detailPanel';
13
13
  import { Snackbar } from '../../components/snackbar';
14
14
  import { resolveConnectorId } from '../../connector-meta';
15
+ import { useMetaMaskExtension } from '../../hooks/useMetaMaskExtension';
15
16
  import { useWagmiConnect } from '../../hooks/useWagmiConnect';
17
+ import { useConnectTimeout } from '../../hooks/useConnectTimeout';
16
18
  import { useWCState } from '../../hooks/useWCState';
17
- import {
18
- detectMetaMaskExtension,
19
- isMobileDevice,
20
- tryOpenDeepLink,
21
- tryOpenCreditWalletDeeplink,
22
- } from '../../utils/platform';
19
+ import { isMobileDevice, tryOpenDeepLink, tryOpenCreditWalletDeeplink } from '../../utils/platform';
23
20
 
24
21
  export type DialogStep = 'selector' | 'detail';
25
22
 
@@ -47,6 +44,13 @@ const getContentForConnector = (id: ConnectorId): DetailContent => {
47
44
  return 'idle';
48
45
  };
49
46
 
47
+ // MM 은 확장 감지 시 즉시 extension 연결, 아니면 QR. (select / retry 공통)
48
+ const getInitialContentForConnector = (
49
+ id: ConnectorId,
50
+ isMetaMaskExtensionInstalled: boolean,
51
+ ): DetailContent =>
52
+ id === 'METAMASK' && !isMetaMaskExtensionInstalled ? 'metamask-qr' : getContentForConnector(id);
53
+
50
54
  export const ConnectDialog: FC<ConnectDialogProps> = ({
51
55
  open,
52
56
  theme = 'dark',
@@ -83,6 +87,16 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
83
87
  const [content, setContent] = useState<DetailContent>('idle');
84
88
  const [showCopySnackbar, setShowCopySnackbar] = useState(false);
85
89
  const [qrUri, setQrUri] = useState<string | null>(null);
90
+ // 외부 만료 신호 카운터 — CC(서버 push session_expired) / WC(connect reject) 수신 시 증가.
91
+ const [expiredSignal, setExpiredSignal] = useState(0);
92
+ // EIP-6963 기반 MetaMask 확장 설치 상태.
93
+ const isMetaMaskExtensionInstalled = useMetaMaskExtension();
94
+
95
+ // 현재 content 를 ref 로 추적 (effect deps 없이 최신값 참조용).
96
+ const contentRef = useRef<DetailContent>(content);
97
+ contentRef.current = content;
98
+ // 타임아웃 직전 화면 — retry 시 이 화면으로 복원 (예: WC 의 wallet-qr).
99
+ const preTimeoutContentRef = useRef<DetailContent>('idle');
86
100
 
87
101
  const config = useConfig();
88
102
 
@@ -95,10 +109,61 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
95
109
  setRecentConnector(result.connectorId);
96
110
  onConnect?.(result);
97
111
  },
98
- onError: (err, connectorId) => onError?.(err, connectorId),
112
+ onError: (err, connectorId) => {
113
+ // WC: connect reject(사용자 거절 / 프로토콜 ~5분 만료 / 네트워크 등)는
114
+ // 모두 timeout UI("Connection Failed")로 통일.
115
+ if (connectorId === 'WALLET_CONNECT') {
116
+ setExpiredSignal((n) => n + 1);
117
+ }
118
+ onError?.(err, connectorId);
119
+ },
99
120
  onQrUri: (uri) => setQrUri(uri),
121
+ // CC connector 가 서버 OnSessionExpired 를 릴레이 → 만료 신호 증가.
122
+ onSessionExpired: () => setExpiredSignal((n) => n + 1),
100
123
  });
101
124
 
125
+ // 연결 타임아웃 (CC=서버 push / MM=client 타이머 / WC=connect reject) → timedOut.
126
+ const timeoutActive = step === 'detail' && !!selectedConnector && content !== 'timeout';
127
+ const { timedOut, retry } = useConnectTimeout({
128
+ connectorId: selectedConnector,
129
+ active: timeoutActive,
130
+ expiredSignal,
131
+ onRetry: () => {
132
+ if (!selectedConnector) return;
133
+ cancelConnect();
134
+ setQrUri(null);
135
+ // 타임아웃 직전 화면으로 복원 (예: WC 의 wallet-qr → 선택 지갑 QR 유지).
136
+ // idle/timeout 처럼 복원 불가한 값이면 커넥터 초기 화면으로.
137
+ const restore = preTimeoutContentRef.current;
138
+ const nextContent: DetailContent =
139
+ restore !== 'idle' && restore !== 'timeout'
140
+ ? restore
141
+ : getInitialContentForConnector(selectedConnector, isMetaMaskExtensionInstalled);
142
+ setContent(nextContent);
143
+ triggerConnect(selectedConnector);
144
+ },
145
+ });
146
+
147
+ // 타임아웃 발생 → 진행 중 연결 취소 + timeout 화면 표시.
148
+ // 전환 전 화면을 저장해 retry 시 복원 (WC wallet-qr 등).
149
+ useEffect(() => {
150
+ if (!timedOut) return;
151
+ preTimeoutContentRef.current = contentRef.current;
152
+ cancelConnect();
153
+ setContent('timeout');
154
+ }, [timedOut, cancelConnect]);
155
+
156
+ // provider 응답이 늦게 도착하면 QR에서 확장 연결 화면으로 전환.
157
+ useEffect(() => {
158
+ if (
159
+ isMetaMaskExtensionInstalled &&
160
+ selectedConnector === 'METAMASK' &&
161
+ content === 'metamask-qr'
162
+ ) {
163
+ setContent('metamask-ext');
164
+ }
165
+ }, [isMetaMaskExtensionInstalled, selectedConnector, content]);
166
+
102
167
  // 마운트 시 wagmi storage 의 recentConnectorId(연결 성공 시 wagmi 가 저장) 복원.
103
168
  useEffect(() => {
104
169
  const restoreRecent = async (): Promise<void> => {
@@ -115,10 +180,7 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
115
180
  const handleSelect = (id: ConnectorId) => {
116
181
  setSelectedConnector(id);
117
182
  // recent 는 클릭이 아니라 연결 성공(onConnect) 시점에 기록 — 위 useWagmiConnect onConnect 참조.
118
- // MetaMask 는 확장이 있으면 즉시 extension 연결, 아니면 QR.
119
- const initialContent: DetailContent =
120
- id === 'METAMASK' && !detectMetaMaskExtension() ? 'metamask-qr' : getContentForConnector(id);
121
- setContent(initialContent);
183
+ setContent(getInitialContentForConnector(id, isMetaMaskExtensionInstalled));
122
184
  setStep('detail');
123
185
  // wagmi connect 시작 (display_uri 도착 시 setQrUri 자동 호출)
124
186
  triggerConnect(id);
@@ -261,6 +323,7 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
261
323
  walletCount={wc.walletList.length}
262
324
  onBack={handleBack}
263
325
  onClose={onClose}
326
+ onRetry={retry}
264
327
  onShowAllWallets={handleShowAllWallets}
265
328
  onSelectWallet={handleSelectWallet}
266
329
  onCopyLink={() => {
@@ -37,8 +37,9 @@
37
37
  width: 100%;
38
38
  max-width: none;
39
39
  grid-template-columns: 1fr;
40
+ grid-template-rows: minmax(0, 1fr);
40
41
  max-height: 82dvh;
41
- height: 100%;
42
+ height: auto;
42
43
  border-radius: 12px 12px 0 0;
43
44
 
44
45
  &[data-step='selector'] .ck-detail { display: none !important; }
@@ -40,6 +40,8 @@ interface DetailPanelProps {
40
40
  onShowAllWallets?: () => void;
41
41
  onSelectWallet?: (wallet: WCWallet) => void;
42
42
  onCopyLink?: () => void;
43
+ /** timeout 화면의 "Try Again" 버튼 핸들러. */
44
+ onRetry?: () => void;
43
45
  }
44
46
 
45
47
  const getTitle = (content: DetailContent, connector: ConnectorId | null): string => {
@@ -78,6 +80,7 @@ export const DetailPanel: FC<DetailPanelProps> = ({
78
80
  onShowAllWallets,
79
81
  onSelectWallet,
80
82
  onCopyLink,
83
+ onRetry,
81
84
  }) => {
82
85
  // CONNECTOR_META 에서 활성 connector 의 logo / downloadUrl 가져옴.
83
86
  const connectorMeta = selectedConnector ? CONNECTOR_META[selectedConnector] : null;
@@ -112,7 +115,7 @@ export const DetailPanel: FC<DetailPanelProps> = ({
112
115
  {content === 'qr' && (
113
116
  <QR uri={qrUri} logoUrl={connectorLogoUrl} downloadUrl={connectorDownloadUrl} />
114
117
  )}
115
- {content === 'timeout' && <Timeout />}
118
+ {content === 'timeout' && <Timeout onRetry={onRetry} />}
116
119
  {(content === 'metamask-ext' || content === 'metamask-qr') && (
117
120
  <MetaMask
118
121
  variant={content === 'metamask-ext' ? 'extension' : 'qr'}
@@ -3,6 +3,7 @@
3
3
  .ck-detail {
4
4
  display: flex;
5
5
  flex-direction: column;
6
+ min-width: 0;
6
7
  background: var(--semantic-paper-dialog);
7
8
  min-height: 0;
8
9
  position: relative;
@@ -61,4 +62,8 @@
61
62
  overflow-y: auto;
62
63
  padding: 20px;
63
64
  @include themedScrollbar;
65
+
66
+ &:has(.ck-all-wallets) {
67
+ padding-bottom: 0;
68
+ }
64
69
  }
@@ -13,7 +13,3 @@ export const tryOpenDeepLink = (uri: string, deepLinkBase: string): void => {
13
13
  export const tryOpenCreditWalletDeeplink = (payloadUrl: string): void => {
14
14
  window.location.replace(`creditwallet://?uri=${encodeURIComponent(payloadUrl)}`);
15
15
  };
16
-
17
- export const detectMetaMaskExtension = (): boolean =>
18
- typeof window !== 'undefined' &&
19
- Boolean((window as Window & { ethereum?: { isMetaMask?: boolean } }).ethereum?.isMetaMask);
@@ -0,0 +1,15 @@
1
+ import penguinWalletIcon from '../assets/penguinwallet.png';
2
+ import type { WCWallet } from './types';
3
+
4
+ // TODO-CRCN-11: WC에 PenguinWallet 추가 시 삭제
5
+ export const CREDIT_WALLET_LISTING_ID =
6
+ 'ce02f1ae74686234eabfa7d31876c442779b730dfe7924a3ca0e6559a7afa30f';
7
+
8
+ export const overrideWalletDisplay = (wallet: WCWallet): WCWallet =>
9
+ wallet.id === CREDIT_WALLET_LISTING_ID
10
+ ? {
11
+ ...wallet,
12
+ name: 'PenguinWallet',
13
+ imageUrl: penguinWalletIcon,
14
+ }
15
+ : wallet;