@gluwa/connect-kit 0.1.0-next.2 → 0.2.0-next.1

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.
package/dist/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@gluwa/connect-kit",
3
- "version": "0.1.0-next.2",
3
+ "version": "0.2.0-next.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
7
+ "types": "./src/index.ts",
7
8
  "default": "./src/index.ts"
8
9
  },
10
+ "./credit-connect": {
11
+ "types": "./src/credit-connect.ts",
12
+ "default": "./src/credit-connect.ts"
13
+ },
9
14
  "./package.json": "./package.json"
10
15
  },
11
16
  "publishConfig": {
@@ -17,10 +22,17 @@
17
22
  "types": "./dist/index.d.ts",
18
23
  "default": "./dist/index.js"
19
24
  },
25
+ "./credit-connect": {
26
+ "types": "./dist/credit-connect.d.ts",
27
+ "default": "./dist/credit-connect.js"
28
+ },
20
29
  "./index.css": "./dist/index.css"
21
30
  }
22
31
  },
23
- "sideEffects": true,
32
+ "sideEffects": [
33
+ "**/*.scss",
34
+ "**/*.css"
35
+ ],
24
36
  "scripts": {
25
37
  "prepack": "pnpm build && cp package.json dist/ && cp README.md dist/",
26
38
  "build": "tsup",
@@ -31,10 +43,13 @@
31
43
  "author": "",
32
44
  "license": "ISC",
33
45
  "dependencies": {
34
- "qrcode.react": "^3.2.0"
46
+ "qrcode.react": "^4.2.0",
47
+ "wagmi": "^2.15.6",
48
+ "@wagmi/core": "^2.16.7"
35
49
  },
36
50
  "devDependencies": {
37
51
  "@gluwa/credit-connect-sdk": "workspace:*",
52
+ "@tanstack/react-query": "^5.62.0",
38
53
  "@types/react": "catalog:react18",
39
54
  "@wagmi/core": "^2.16.7",
40
55
  "esbuild-sass-plugin": "^3.7.0",
@@ -48,14 +63,16 @@
48
63
  },
49
64
  "peerDependencies": {
50
65
  "@gluwa/credit-connect-sdk": "workspace:*",
51
- "@wagmi/core": ">=2.0.0",
66
+ "@tanstack/react-query": ">=5.0.0",
52
67
  "react": "^18.0.0 || ^19.0.0",
53
- "viem": ">=2.0.0",
54
- "wagmi": ">=2.0.0"
68
+ "viem": ">=2.0.0"
55
69
  },
56
70
  "peerDependenciesMeta": {
57
71
  "@gluwa/credit-connect-sdk": {
58
72
  "optional": true
73
+ },
74
+ "@tanstack/react-query": {
75
+ "optional": true
59
76
  }
60
77
  },
61
78
  "repository": {
package/package.json CHANGED
@@ -1,24 +1,34 @@
1
1
  {
2
2
  "name": "@gluwa/connect-kit",
3
- "version": "0.1.0-next.2",
3
+ "version": "0.2.0-next.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
8
8
  "default": "./dist/index.js"
9
9
  },
10
+ "./credit-connect": {
11
+ "types": "./dist/credit-connect.d.ts",
12
+ "default": "./dist/credit-connect.js"
13
+ },
10
14
  "./index.css": "./dist/index.css"
11
15
  },
12
16
  "publishConfig": {
13
17
  "access": "public"
14
18
  },
15
- "sideEffects": true,
19
+ "sideEffects": [
20
+ "**/*.scss",
21
+ "**/*.css"
22
+ ],
16
23
  "author": "",
17
24
  "license": "ISC",
18
25
  "dependencies": {
19
- "qrcode.react": "^3.2.0"
26
+ "qrcode.react": "^4.2.0",
27
+ "wagmi": "^2.15.6",
28
+ "@wagmi/core": "^2.16.7"
20
29
  },
21
30
  "devDependencies": {
31
+ "@tanstack/react-query": "^5.62.0",
22
32
  "@types/react": "^18.3.18",
23
33
  "@wagmi/core": "^2.16.7",
24
34
  "esbuild-sass-plugin": "^3.7.0",
@@ -32,15 +42,17 @@
32
42
  "@gluwa/credit-connect-sdk": "0.2.0-next.1"
33
43
  },
34
44
  "peerDependencies": {
35
- "@wagmi/core": ">=2.0.0",
45
+ "@tanstack/react-query": ">=5.0.0",
36
46
  "react": "^18.0.0 || ^19.0.0",
37
47
  "viem": ">=2.0.0",
38
- "wagmi": ">=2.0.0",
39
48
  "@gluwa/credit-connect-sdk": "0.2.0-next.1"
40
49
  },
41
50
  "peerDependenciesMeta": {
42
51
  "@gluwa/credit-connect-sdk": {
43
52
  "optional": true
53
+ },
54
+ "@tanstack/react-query": {
55
+ "optional": true
44
56
  }
45
57
  },
46
58
  "repository": {
@@ -9,29 +9,27 @@ import {
9
9
  type FC,
10
10
  type ReactNode,
11
11
  } from 'react';
12
- import { useAccount, useDisconnect } from 'wagmi';
12
+ import { WagmiProvider, useAccount, useDisconnect, useSwitchChain } from 'wagmi';
13
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
13
14
  import { ConnectModal } from './ConnectModal';
14
- import type { ConnectErrorContext, ConnectResult, ConnectorId, Connectors } from './types';
15
+ import { getConfig, getKitConfig, initConfig } from './core/config';
16
+ import type {
17
+ ConnectErrorContext,
18
+ ConnectKitConfig,
19
+ ConnectResult,
20
+ ConnectorId,
21
+ Connectors,
22
+ } from './types';
23
+
24
+ const internalQueryClient = new QueryClient();
15
25
 
16
26
  export interface ConnectKitProviderProps {
17
27
  children: ReactNode;
28
+ kitConfig?: ConnectKitConfig;
18
29
  connectors: Connectors;
19
- wcProjectId?: string;
20
- /**
21
- * 연결돼 있는 동안 강제할 체인 ID. 다른 체인에 연결돼 있으면
22
- * 자동으로 스위치 모달이 떠서 사용자에게 전환 또는 disconnect를 요구.
23
- */
24
30
  requiredChainId?: number;
25
- /**
26
- * 연결이 정상적으로 끝나고(체인 검증까지 통과) "사용 가능한 상태"가 됐을 때 한 번 호출.
27
- * 재연결(reconnect)에서는 호출되지 않음 — 그쪽은 dApp의 watchAccount 책임.
28
- */
29
31
  onConnect?: (result: ConnectResult) => void;
30
32
  onLog?: (message: string, error?: Error) => void;
31
- /**
32
- * 연결 실패(거절/미설치/기타) 시 모달 안에 표시할 fallback UI를 dApp이 직접 그리고 싶을 때.
33
- * 미제공 시 기본 동작은 selector로 복귀.
34
- */
35
33
  renderConnectError?: (ctx: ConnectErrorContext) => ReactNode;
36
34
  }
37
35
 
@@ -57,10 +55,31 @@ const resolveConnectorId = (wagmiConnectorId: string | undefined): ConnectorId |
57
55
  return null;
58
56
  };
59
57
 
60
- export const ConnectKitProvider: FC<ConnectKitProviderProps> = ({
58
+ export const ConnectKitProvider: FC<ConnectKitProviderProps> = (props) => {
59
+ const wagmiConfig = useMemo(
60
+ () => (props.kitConfig ? initConfig(props.kitConfig) : getConfig()),
61
+ [props.kitConfig],
62
+ );
63
+
64
+ const resolvedKitConfig = props.kitConfig ?? getKitConfig();
65
+
66
+ return (
67
+ <WagmiProvider config={wagmiConfig} reconnectOnMount={false}>
68
+ <QueryClientProvider client={internalQueryClient}>
69
+ <ConnectKitProviderInner {...props} kitConfig={resolvedKitConfig} />
70
+ </QueryClientProvider>
71
+ </WagmiProvider>
72
+ );
73
+ };
74
+
75
+ type ConnectKitProviderInnerProps = Omit<ConnectKitProviderProps, 'kitConfig'> & {
76
+ kitConfig: ConnectKitConfig;
77
+ };
78
+
79
+ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
61
80
  children,
62
81
  connectors,
63
- wcProjectId,
82
+ kitConfig,
64
83
  requiredChainId,
65
84
  onConnect,
66
85
  onLog,
@@ -71,21 +90,62 @@ export const ConnectKitProvider: FC<ConnectKitProviderProps> = ({
71
90
 
72
91
  const account = useAccount();
73
92
  const { disconnectAsync } = useDisconnect();
93
+ const { switchChainAsync } = useSwitchChain();
74
94
 
75
- // dApp의 onConnect는 ref로 잡아 effect 재실행 노이즈 차단
76
95
  const onConnectRef = useRef(onConnect);
77
96
  useEffect(() => {
78
97
  onConnectRef.current = onConnect;
79
98
  }, [onConnect]);
80
99
 
81
- // chain mismatch invariant: 연결돼 있지만 요구 체인이 아니면 modal force-open
82
100
  const chainMismatch =
83
101
  requiredChainId != null &&
84
102
  account.status === 'connected' &&
85
103
  account.chainId != null &&
86
104
  account.chainId !== requiredChainId;
87
105
 
88
- const effectiveOpen = isOpen || chainMismatch;
106
+ const [autoSwitchFailedKey, setAutoSwitchFailedKey] = useState<string | null>(null);
107
+ const autoSwitchInFlightRef = useRef(false);
108
+ const isMountedRef = useRef(true);
109
+ useEffect(() => {
110
+ isMountedRef.current = true;
111
+ return () => {
112
+ isMountedRef.current = false;
113
+ };
114
+ }, []);
115
+
116
+ const mismatchKey =
117
+ chainMismatch && account.chainId != null && requiredChainId != null
118
+ ? `${account.chainId}->${requiredChainId}`
119
+ : null;
120
+
121
+ useEffect(() => {
122
+ if (!chainMismatch || requiredChainId == null) return;
123
+ if (autoSwitchInFlightRef.current) return;
124
+ if (mismatchKey != null && mismatchKey === autoSwitchFailedKey) return;
125
+
126
+ autoSwitchInFlightRef.current = true;
127
+ switchChainAsync({ chainId: requiredChainId })
128
+ .catch((err: unknown) => {
129
+ if (!isMountedRef.current) return;
130
+ const normalized = err instanceof Error ? err : new Error(String(err));
131
+ onLog?.(`[connect-kit] silent switch chain failed: ${normalized.message}`, normalized);
132
+ if (mismatchKey != null) {
133
+ setAutoSwitchFailedKey(mismatchKey);
134
+ }
135
+ })
136
+ .finally(() => {
137
+ autoSwitchInFlightRef.current = false;
138
+ });
139
+ }, [chainMismatch, mismatchKey, requiredChainId, autoSwitchFailedKey, switchChainAsync, onLog]);
140
+
141
+ useEffect(() => {
142
+ if (!chainMismatch && autoSwitchFailedKey != null) {
143
+ setAutoSwitchFailedKey(null);
144
+ }
145
+ }, [chainMismatch, autoSwitchFailedKey]);
146
+
147
+ const autoSwitchFailedForCurrent = mismatchKey != null && mismatchKey === autoSwitchFailedKey;
148
+ const effectiveOpen = isOpen || (chainMismatch && autoSwitchFailedForCurrent);
89
149
 
90
150
  const open = useCallback((): void => {
91
151
  setIsOpen(true);
@@ -99,22 +159,17 @@ export const ConnectKitProvider: FC<ConnectKitProviderProps> = ({
99
159
  await disconnectAsync();
100
160
  }, [disconnectAsync]);
101
161
 
102
- // ConnectModal → Provider: 연결 결과를 일단 보류했다가
103
- // chain 검증까지 통과한 후에만 dApp으로 forward
104
162
  const handleModalConnect = useCallback((result: ConnectResult): void => {
105
163
  setPendingResult(result);
106
164
  }, []);
107
165
 
108
- // pendingResult가 있고, 연결 + chain 매칭이 충족되면 dApp으로 발화
109
166
  useEffect(() => {
110
167
  if (!pendingResult) return;
111
168
  if (account.status !== 'connected') {
112
- // 연결이 끊겼거나 진행 중 — pending 폐기
113
169
  setPendingResult(null);
114
170
  return;
115
171
  }
116
172
  if (requiredChainId != null && account.chainId !== requiredChainId) {
117
- // chain 아직 안 맞음 — switch 후 다시 평가
118
173
  return;
119
174
  }
120
175
  onConnectRef.current?.(pendingResult);
@@ -149,7 +204,7 @@ export const ConnectKitProvider: FC<ConnectKitProviderProps> = ({
149
204
  <ConnectModal
150
205
  open={effectiveOpen}
151
206
  connectors={connectors}
152
- wcProjectId={wcProjectId}
207
+ wcProjectId={kitConfig.wcProjectId}
153
208
  requiredChainId={requiredChainId}
154
209
  renderConnectError={renderConnectError}
155
210
  onConnect={handleModalConnect}
@@ -520,11 +520,11 @@
520
520
  }
521
521
  }
522
522
 
523
- // 스크롤 wrapper — 520px 고정, 세로 스크롤만 담당
523
+ // 스크롤 wrapper — 세로 스크롤만 담당
524
524
  .ck-wallet-grid-scroll {
525
525
  flex: 1;
526
526
  min-height: 0;
527
- width: 520px;
527
+ width: 100%;
528
528
  overflow-x: hidden;
529
529
  overflow-y: auto;
530
530
  }
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useCallback, useRef, useMemo, type FC, type ReactNode } from 'react';
1
+ import { useState, useEffect, useCallback, useMemo, type FC, type ReactNode } from 'react';
2
2
  import './ConnectModal.scss';
3
3
  import { useAccount, useConfig, useDisconnect } from 'wagmi';
4
4
  import {
@@ -21,28 +21,60 @@ import { WalletConnectView } from './views/WalletConnectView';
21
21
  import { SwitchChainView } from './views/SwitchChainView';
22
22
 
23
23
  const RECENT_KEY = 'connect-kit:recent';
24
+ const VALID_CONNECTOR_IDS: ReadonlySet<ConnectorId> = new Set<ConnectorId>([
25
+ 'CREDIT_CONNECT',
26
+ 'CREDIT_WALLET',
27
+ 'METAMASK',
28
+ 'WALLET_CONNECT',
29
+ ]);
30
+
24
31
  const readRecentConnector = (): ConnectorId | null => {
25
32
  try {
26
- return localStorage.getItem(RECENT_KEY) as ConnectorId | null;
33
+ const raw = localStorage.getItem(RECENT_KEY);
34
+ if (raw == null) return null;
35
+ return VALID_CONNECTOR_IDS.has(raw as ConnectorId) ? (raw as ConnectorId) : null;
27
36
  } catch {
28
37
  return null;
29
38
  }
30
39
  };
31
40
 
32
- const writeRecentConnector = (id: ConnectorId): void => {
41
+ const writeRecentConnector = (
42
+ id: ConnectorId,
43
+ onLog?: (message: string, error?: Error) => void,
44
+ ): void => {
33
45
  try {
34
46
  localStorage.setItem(RECENT_KEY, id);
35
- } catch {}
47
+ } catch (err) {
48
+ const normalized = err instanceof Error ? err : new Error(String(err));
49
+ onLog?.(`[connect-kit] writeRecentConnector failed: ${normalized.message}`, normalized);
50
+ }
36
51
  };
37
52
 
53
+ const REJECTED_ERROR_CODES = new Set<unknown>([
54
+ 4001, // EIP-1193 user rejected
55
+ 'ACTION_REJECTED', // ethers v6
56
+ ]);
57
+
38
58
  const classifyError = (error: Error): ConnectErrorReason => {
39
59
  const code =
40
60
  typeof error === 'object' && error !== null && 'code' in error
41
61
  ? (error as unknown as { code: unknown }).code
42
62
  : undefined;
43
- if (code === 4001) return 'rejected';
63
+ if (code != null && REJECTED_ERROR_CODES.has(code)) return 'rejected';
44
64
  const message = error.message?.toLowerCase() ?? '';
45
- if (message.includes('user rejected') || message.includes('user denied')) return 'rejected';
65
+ if (
66
+ message.includes('user rejected') ||
67
+ message.includes('user denied') ||
68
+ message.includes('user cancel') ||
69
+ message.includes('rejected by user') ||
70
+ message.includes('action_rejected') ||
71
+ message.includes('사용자가 거부') ||
72
+ message.includes('사용자가 취소') ||
73
+ message.includes('ユーザーが拒否') ||
74
+ message.includes('ユーザーがキャンセル')
75
+ ) {
76
+ return 'rejected';
77
+ }
46
78
  if (message.includes('unsupported') || message.includes('not installed')) return 'unsupported';
47
79
  return 'unknown';
48
80
  };
@@ -270,8 +302,6 @@ const resolveConnectorsFromConfig = (connectorsConfig?: Connectors): ConnectorId
270
302
  };
271
303
 
272
304
  export const ConnectModal: FC<ConnectModalProps> = (props) => {
273
- // open=false면 내부 컴포넌트를 마운트하지 않음
274
- // → useConfig 등 wagmi hooks가 실행되지 않아 WagmiProvider 없이도 렌더링 가능
275
305
  if (!props.open) return null;
276
306
 
277
307
  const resolvedConnectors = resolveConnectorsFromConfig(props.connectors);
@@ -303,8 +333,7 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
303
333
  renderConnectError,
304
334
  }) => {
305
335
  const [selectedConnector, setSelectedConnector] = useState<ConnectorId | null>(null);
306
- const [qrUriMap, setQrUriMap] = useState<Partial<Record<ConnectorId, string>>>({});
307
- const pendingQrConnectorRef = useRef<ConnectorId | null>(null);
336
+ const [qrUri, setQrUri] = useState<string | null>(null);
308
337
  const [recentConnector, setRecentConnector] = useState<ConnectorId | null>(null);
309
338
  const [hasMetaMaskExtension, setHasMetaMaskExtension] = useState<boolean>(false);
310
339
  const [errorState, setErrorState] = useState<{ connectorId: ConnectorId; error: Error } | null>(
@@ -313,7 +342,6 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
313
342
 
314
343
  const wc = useWCState();
315
344
 
316
- // wagmi 상태 — chain mismatch invariant 판정용
317
345
  const account = useAccount();
318
346
  const { disconnectAsync } = useDisconnect();
319
347
  const wagmiConfig = useConfig();
@@ -329,27 +357,19 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
329
357
  return wagmiConfig.chains.find((chain) => chain.id === requiredChainId)?.name;
330
358
  }, [wagmiConfig.chains, requiredChainId]);
331
359
 
332
- // QR URI 수신 — 커넥터별 캐시에 저장 (null은 무시하여 캐시 유지)
333
360
  const handleQrUri = useCallback((uri: string | null) => {
334
- if (uri) {
335
- const id = pendingQrConnectorRef.current;
336
- if (id) setQrUriMap((prev) => ({ ...prev, [id]: uri }));
337
- }
361
+ setQrUri(uri);
338
362
  }, []);
339
363
 
340
- // 연결 완료
341
364
  const handleConnect = useCallback(
342
365
  (result: Parameters<typeof onConnect>[0]) => {
343
366
  setErrorState(null);
344
367
  onConnect(result);
345
- // chain mismatch가 있으면 모달은 SwitchChainView로 자동 전환됨 (닫지 않음)
346
- // Provider 측에서 requiredChainId가 매칭될 때까지 force-open 유지
347
368
  onClose();
348
369
  },
349
370
  [onConnect, onClose],
350
371
  );
351
372
 
352
- // 연결 실패 — 실패한 커넥터의 QR 캐시만 삭제
353
373
  const handleError = useCallback(
354
374
  (error: unknown, connectorId: ConnectorId): void => {
355
375
  const normalizedError = error instanceof Error ? error : new Error(String(error));
@@ -357,11 +377,7 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
357
377
  `[connect-kit] connection failed (${connectorId}): ${normalizedError.message}`,
358
378
  normalizedError,
359
379
  );
360
- setQrUriMap((prev) => {
361
- const next = { ...prev };
362
- delete next[connectorId];
363
- return next;
364
- });
380
+ setQrUri(null);
365
381
  wc.resetView();
366
382
  if (renderConnectError) {
367
383
  setErrorState({ connectorId, error: normalizedError });
@@ -372,14 +388,12 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
372
388
  [onLog, wc.resetView, renderConnectError],
373
389
  );
374
390
 
375
- // 연결 시도
376
391
  const { triggerConnect, cancelConnect } = useWagmiConnect({
377
392
  onConnect: handleConnect,
378
393
  onError: handleError,
379
394
  onQrUri: handleQrUri,
380
395
  });
381
396
 
382
- // wagmi connector icon 매핑
383
397
  const connectorLogoMap = useMemo(() => {
384
398
  const map: Partial<Record<ConnectorId, string>> = {};
385
399
  const connectorIds: ConnectorId[] = [
@@ -395,33 +409,29 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
395
409
  return map;
396
410
  }, [wagmiConfig.connectors]);
397
411
 
398
- // 최근 연결 기록 로드
399
412
  useEffect(() => {
400
413
  setRecentConnector(readRecentConnector());
401
414
  setHasMetaMaskExtension(detectMetaMaskExtension());
402
415
  }, []);
403
416
 
404
- // 모달 닫힐 때 상태 + QR 캐시 전체 초기화
405
417
  useEffect(() => {
406
418
  if (!open) {
407
419
  setSelectedConnector(null);
408
- setQrUriMap({});
409
- pendingQrConnectorRef.current = null;
420
+ setQrUri(null);
410
421
  setErrorState(null);
411
422
  wc.reset();
412
423
  cancelConnect();
413
424
  }
414
425
  }, [open, cancelConnect, wc.reset]);
415
426
 
416
- // 커넥터 선택
417
427
  const handleSelectConnector = useCallback(
418
428
  (id: ConnectorId): void => {
419
429
  if (id === selectedConnector && !errorState) return;
420
- pendingQrConnectorRef.current = id;
421
430
  setSelectedConnector(id);
431
+ setQrUri(null);
422
432
  setErrorState(null);
423
433
  wc.resetView();
424
- writeRecentConnector(id);
434
+ writeRecentConnector(id, onLog);
425
435
  setRecentConnector(id);
426
436
 
427
437
  if (id === 'WALLET_CONNECT' && wcProjectId) {
@@ -439,10 +449,10 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
439
449
  wcProjectId,
440
450
  wc.resetView,
441
451
  wc.loadWalletList,
452
+ onLog,
442
453
  ],
443
454
  );
444
455
 
445
- // 뒤로가기 — QR 캐시는 유지, wagmi만 reset
446
456
  const handleBack = useCallback((): void => {
447
457
  if (errorState) {
448
458
  setErrorState(null);
@@ -450,12 +460,11 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
450
460
  return;
451
461
  }
452
462
  if (selectedConnector === 'WALLET_CONNECT' && wc.handleBack()) return;
453
- pendingQrConnectorRef.current = null;
454
463
  setSelectedConnector(null);
464
+ setQrUri(null);
455
465
  cancelConnect();
456
466
  }, [selectedConnector, errorState, wc.handleBack, cancelConnect]);
457
467
 
458
- // chain mismatch 상태에서의 disconnect (Switch chain pane의 cancel)
459
468
  const handleSwitchChainDisconnect = useCallback((): void => {
460
469
  disconnectAsync().catch((err: unknown) => {
461
470
  const normalized = err instanceof Error ? err : new Error(String(err));
@@ -463,13 +472,11 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
463
472
  });
464
473
  }, [disconnectAsync, onLog]);
465
474
 
466
- // overlay 클릭 — chain mismatch 상태에선 무시 (실수 클릭 방어)
467
475
  const handleOverlayClick = useCallback((): void => {
468
476
  if (chainMismatch) return;
469
477
  onClose();
470
478
  }, [chainMismatch, onClose]);
471
479
 
472
- // close 버튼 — chain mismatch 상태에선 disconnect
473
480
  const handleCloseClick = useCallback((): void => {
474
481
  if (chainMismatch) {
475
482
  handleSwitchChainDisconnect();
@@ -487,7 +494,7 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
487
494
  retry: () => {
488
495
  const id = errorState.connectorId;
489
496
  setErrorState(null);
490
- pendingQrConnectorRef.current = id;
497
+ setQrUri(null);
491
498
  cancelConnect();
492
499
  triggerConnect(id);
493
500
  },
@@ -499,8 +506,6 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
499
506
  return renderConnectError(ctx);
500
507
  }, [errorState, renderConnectError, triggerConnect, cancelConnect]);
501
508
 
502
- // chain mismatch면 단일 pane (selector/detail 숨김)
503
- // requiredChainId != null 재확인은 TS narrowing용 — chainMismatch true면 이미 보장됨
504
509
  if (chainMismatch && requiredChainId != null) {
505
510
  return (
506
511
  <div className="ck-root">
@@ -554,7 +559,7 @@ const ConnectModalInner: FC<ConnectModalInnerProps> = ({
554
559
  />
555
560
  <DetailPane
556
561
  selectedConnector={selectedConnector}
557
- qrUri={selectedConnector ? (qrUriMap[selectedConnector] ?? null) : null}
562
+ qrUri={qrUri}
558
563
  hasMetaMaskExtension={hasMetaMaskExtension}
559
564
  connectorLogoMap={connectorLogoMap}
560
565
  wc={wc}
@@ -0,0 +1,21 @@
1
+ import {
2
+ getAccount as wagmiGetAccount,
3
+ signMessage as wagmiSignMessage,
4
+ signTypedData as wagmiSignTypedData,
5
+ type GetAccountReturnType,
6
+ type SignMessageParameters,
7
+ type SignTypedDataParameters,
8
+ } from '@wagmi/core';
9
+ import { getConfig } from '../core/config';
10
+
11
+ export const getAccount = (): GetAccountReturnType => {
12
+ return wagmiGetAccount(getConfig());
13
+ };
14
+
15
+ export const signMessage = (params: SignMessageParameters): Promise<`0x${string}`> => {
16
+ return wagmiSignMessage(getConfig(), params);
17
+ };
18
+
19
+ export const signTypedData = (params: SignTypedDataParameters): Promise<`0x${string}`> => {
20
+ return wagmiSignTypedData(getConfig(), params);
21
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ getBalance as wagmiGetBalance,
3
+ type GetBalanceParameters,
4
+ type GetBalanceReturnType,
5
+ } from '@wagmi/core';
6
+ import { getConfig } from '../core/config';
7
+
8
+ export const getBalance = (params: GetBalanceParameters): Promise<GetBalanceReturnType> => {
9
+ return wagmiGetBalance(getConfig(), params);
10
+ };
@@ -0,0 +1,17 @@
1
+ import {
2
+ switchChain as wagmiSwitchChain,
3
+ getPublicClient as wagmiGetPublicClient,
4
+ type SwitchChainParameters,
5
+ type GetPublicClientParameters,
6
+ type GetPublicClientReturnType,
7
+ } from '@wagmi/core';
8
+ import type { Chain } from 'viem';
9
+ import { getConfig } from '../core/config';
10
+
11
+ export const switchChain = (params: SwitchChainParameters): Promise<Chain> => {
12
+ return wagmiSwitchChain(getConfig(), params);
13
+ };
14
+
15
+ export const getPublicClient = (params?: GetPublicClientParameters): GetPublicClientReturnType => {
16
+ return wagmiGetPublicClient(getConfig(), params);
17
+ };
@@ -0,0 +1,33 @@
1
+ import {
2
+ readContract as wagmiReadContract,
3
+ writeContract as wagmiWriteContract,
4
+ type ReadContractParameters,
5
+ type ReadContractReturnType,
6
+ type WriteContractParameters,
7
+ type WriteContractReturnType,
8
+ type Config,
9
+ } from '@wagmi/core';
10
+ import type { Abi, ContractFunctionArgs, ContractFunctionName } from 'viem';
11
+ import { getConfig } from '../core/config';
12
+
13
+ export function readContract<
14
+ const abi extends Abi | readonly unknown[],
15
+ functionName extends ContractFunctionName<abi, 'pure' | 'view'>,
16
+ args extends ContractFunctionArgs<abi, 'pure' | 'view', functionName>,
17
+ >(
18
+ parameters: ReadContractParameters<abi, functionName, args, Config>,
19
+ ): Promise<ReadContractReturnType<abi, functionName, args>> {
20
+ return wagmiReadContract(getConfig(), parameters as any) as Promise<
21
+ ReadContractReturnType<abi, functionName, args>
22
+ >;
23
+ }
24
+
25
+ export function writeContract<
26
+ const abi extends Abi | readonly unknown[],
27
+ functionName extends ContractFunctionName<abi, 'nonpayable' | 'payable'>,
28
+ args extends ContractFunctionArgs<abi, 'nonpayable' | 'payable', functionName>,
29
+ >(
30
+ parameters: WriteContractParameters<abi, functionName, args, Config>,
31
+ ): Promise<WriteContractReturnType> {
32
+ return wagmiWriteContract(getConfig(), parameters as any);
33
+ }
@@ -0,0 +1,12 @@
1
+ import {
2
+ waitForTransactionReceipt as wagmiWaitForTransactionReceipt,
3
+ type WaitForTransactionReceiptParameters,
4
+ type WaitForTransactionReceiptReturnType,
5
+ } from '@wagmi/core';
6
+ import { getConfig } from '../core/config';
7
+
8
+ export const waitForTransactionReceipt = (
9
+ params: WaitForTransactionReceiptParameters,
10
+ ): Promise<WaitForTransactionReceiptReturnType> => {
11
+ return wagmiWaitForTransactionReceipt(getConfig(), params);
12
+ };