@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/CHANGELOG.md +12 -0
- package/dist/chunk-DDA6FP6U.js +7 -0
- package/dist/credit-connect.d.ts +16 -0
- package/dist/credit-connect.js +414 -0
- package/dist/index.css +561 -0
- package/dist/index.d.ts +37 -26
- package/dist/index.js +336 -1068
- package/dist/package.json +23 -6
- package/package.json +17 -5
- package/src/ConnectKitProvider.tsx +81 -26
- package/src/ConnectModal.scss +2 -2
- package/src/ConnectModal.tsx +48 -43
- package/src/api/account.ts +21 -0
- package/src/api/balance.ts +10 -0
- package/src/api/chain.ts +17 -0
- package/src/api/contract.ts +33 -0
- package/src/api/transaction.ts +12 -0
- package/src/components/QRFrame.tsx +2 -2
- package/src/core/config.ts +83 -0
- package/src/credit-connect.ts +2 -0
- package/src/creditConnectConnector.ts +176 -159
- package/src/events/account.ts +49 -0
- package/src/hooks/useWCState.ts +24 -9
- package/src/hooks/useWagmiConnect.ts +13 -8
- package/src/index.ts +33 -2
- package/src/types.ts +9 -0
- package/src/utils/platform.ts +1 -4
- package/src/views/CreditWalletView.tsx +4 -2
- package/src/views/MetaMaskView.tsx +4 -2
- package/src/views/SwitchChainView.tsx +9 -4
- package/tsup.config.ts +6 -2
package/dist/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gluwa/connect-kit",
|
|
3
|
-
"version": "0.
|
|
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":
|
|
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": "^
|
|
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
|
-
"@
|
|
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.
|
|
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":
|
|
19
|
+
"sideEffects": [
|
|
20
|
+
"**/*.scss",
|
|
21
|
+
"**/*.css"
|
|
22
|
+
],
|
|
16
23
|
"author": "",
|
|
17
24
|
"license": "ISC",
|
|
18
25
|
"dependencies": {
|
|
19
|
-
"qrcode.react": "^
|
|
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
|
-
"@
|
|
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
|
|
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
|
-
|
|
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
|
|
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}
|
package/src/ConnectModal.scss
CHANGED
|
@@ -520,11 +520,11 @@
|
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
-
// 스크롤 wrapper —
|
|
523
|
+
// 스크롤 wrapper — 세로 스크롤만 담당
|
|
524
524
|
.ck-wallet-grid-scroll {
|
|
525
525
|
flex: 1;
|
|
526
526
|
min-height: 0;
|
|
527
|
-
width:
|
|
527
|
+
width: 100%;
|
|
528
528
|
overflow-x: hidden;
|
|
529
529
|
overflow-y: auto;
|
|
530
530
|
}
|
package/src/ConnectModal.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback,
|
|
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
|
-
|
|
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 = (
|
|
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
|
|
63
|
+
if (code != null && REJECTED_ERROR_CODES.has(code)) return 'rejected';
|
|
44
64
|
const message = error.message?.toLowerCase() ?? '';
|
|
45
|
-
if (
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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
|
+
};
|
package/src/api/chain.ts
ADDED
|
@@ -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
|
+
};
|