@gluwa/connect-kit 0.1.0-next.0 → 0.1.0-next.2

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@gluwa/connect-kit",
3
- "version": "0.1.0-next.0",
3
+ "version": "0.1.0-next.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -48,7 +48,9 @@
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@gluwa/credit-connect-sdk": "workspace:*",
51
+ "@wagmi/core": ">=2.0.0",
51
52
  "react": "^18.0.0 || ^19.0.0",
53
+ "viem": ">=2.0.0",
52
54
  "wagmi": ">=2.0.0"
53
55
  },
54
56
  "peerDependenciesMeta": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gluwa/connect-kit",
3
- "version": "0.1.0-next.0",
3
+ "version": "0.1.0-next.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -32,7 +32,9 @@
32
32
  "@gluwa/credit-connect-sdk": "0.2.0-next.1"
33
33
  },
34
34
  "peerDependencies": {
35
+ "@wagmi/core": ">=2.0.0",
35
36
  "react": "^18.0.0 || ^19.0.0",
37
+ "viem": ">=2.0.0",
36
38
  "wagmi": ">=2.0.0",
37
39
  "@gluwa/credit-connect-sdk": "0.2.0-next.1"
38
40
  },
@@ -0,0 +1,169 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ type FC,
10
+ type ReactNode,
11
+ } from 'react';
12
+ import { useAccount, useDisconnect } from 'wagmi';
13
+ import { ConnectModal } from './ConnectModal';
14
+ import type { ConnectErrorContext, ConnectResult, ConnectorId, Connectors } from './types';
15
+
16
+ export interface ConnectKitProviderProps {
17
+ children: ReactNode;
18
+ connectors: Connectors;
19
+ wcProjectId?: string;
20
+ /**
21
+ * 연결돼 있는 동안 강제할 체인 ID. 다른 체인에 연결돼 있으면
22
+ * 자동으로 스위치 모달이 떠서 사용자에게 전환 또는 disconnect를 요구.
23
+ */
24
+ requiredChainId?: number;
25
+ /**
26
+ * 연결이 정상적으로 끝나고(체인 검증까지 통과) "사용 가능한 상태"가 됐을 때 한 번 호출.
27
+ * 재연결(reconnect)에서는 호출되지 않음 — 그쪽은 dApp의 watchAccount 책임.
28
+ */
29
+ onConnect?: (result: ConnectResult) => void;
30
+ onLog?: (message: string, error?: Error) => void;
31
+ /**
32
+ * 연결 실패(거절/미설치/기타) 시 모달 안에 표시할 fallback UI를 dApp이 직접 그리고 싶을 때.
33
+ * 미제공 시 기본 동작은 selector로 복귀.
34
+ */
35
+ renderConnectError?: (ctx: ConnectErrorContext) => ReactNode;
36
+ }
37
+
38
+ export interface ConnectKitContextValue {
39
+ isOpen: boolean;
40
+ open: () => void;
41
+ close: () => void;
42
+ isConnected: boolean;
43
+ address: `0x${string}` | undefined;
44
+ connectorId: ConnectorId | null;
45
+ disconnect: () => Promise<void>;
46
+ }
47
+
48
+ const ConnectKitContext = createContext<ConnectKitContextValue | null>(null);
49
+
50
+ const resolveConnectorId = (wagmiConnectorId: string | undefined): ConnectorId | null => {
51
+ if (!wagmiConnectorId) return null;
52
+ if (wagmiConnectorId === 'credit-connect') return 'CREDIT_CONNECT';
53
+ if (wagmiConnectorId === 'walletConnect') return 'WALLET_CONNECT';
54
+ const lower = wagmiConnectorId.toLowerCase();
55
+ if (lower.includes('metamask') || wagmiConnectorId === 'injected') return 'METAMASK';
56
+ if (lower.includes('walletconnect')) return 'WALLET_CONNECT';
57
+ return null;
58
+ };
59
+
60
+ export const ConnectKitProvider: FC<ConnectKitProviderProps> = ({
61
+ children,
62
+ connectors,
63
+ wcProjectId,
64
+ requiredChainId,
65
+ onConnect,
66
+ onLog,
67
+ renderConnectError,
68
+ }) => {
69
+ const [isOpen, setIsOpen] = useState(false);
70
+ const [pendingResult, setPendingResult] = useState<ConnectResult | null>(null);
71
+
72
+ const account = useAccount();
73
+ const { disconnectAsync } = useDisconnect();
74
+
75
+ // dApp의 onConnect는 ref로 잡아 effect 재실행 노이즈 차단
76
+ const onConnectRef = useRef(onConnect);
77
+ useEffect(() => {
78
+ onConnectRef.current = onConnect;
79
+ }, [onConnect]);
80
+
81
+ // chain mismatch invariant: 연결돼 있지만 요구 체인이 아니면 modal force-open
82
+ const chainMismatch =
83
+ requiredChainId != null &&
84
+ account.status === 'connected' &&
85
+ account.chainId != null &&
86
+ account.chainId !== requiredChainId;
87
+
88
+ const effectiveOpen = isOpen || chainMismatch;
89
+
90
+ const open = useCallback((): void => {
91
+ setIsOpen(true);
92
+ }, []);
93
+
94
+ const close = useCallback((): void => {
95
+ setIsOpen(false);
96
+ }, []);
97
+
98
+ const disconnect = useCallback(async (): Promise<void> => {
99
+ await disconnectAsync();
100
+ }, [disconnectAsync]);
101
+
102
+ // ConnectModal → Provider: 연결 결과를 일단 보류했다가
103
+ // chain 검증까지 통과한 후에만 dApp으로 forward
104
+ const handleModalConnect = useCallback((result: ConnectResult): void => {
105
+ setPendingResult(result);
106
+ }, []);
107
+
108
+ // pendingResult가 있고, 연결 + chain 매칭이 충족되면 dApp으로 발화
109
+ useEffect(() => {
110
+ if (!pendingResult) return;
111
+ if (account.status !== 'connected') {
112
+ // 연결이 끊겼거나 진행 중 — pending 폐기
113
+ setPendingResult(null);
114
+ return;
115
+ }
116
+ if (requiredChainId != null && account.chainId !== requiredChainId) {
117
+ // chain 아직 안 맞음 — switch 후 다시 평가
118
+ return;
119
+ }
120
+ onConnectRef.current?.(pendingResult);
121
+ setPendingResult(null);
122
+ setIsOpen(false);
123
+ }, [pendingResult, account.status, account.chainId, requiredChainId]);
124
+
125
+ const value = useMemo<ConnectKitContextValue>(
126
+ () => ({
127
+ isOpen: effectiveOpen,
128
+ open,
129
+ close,
130
+ isConnected: account.status === 'connected',
131
+ address: account.address,
132
+ connectorId: resolveConnectorId(account.connector?.id),
133
+ disconnect,
134
+ }),
135
+ [
136
+ effectiveOpen,
137
+ open,
138
+ close,
139
+ account.status,
140
+ account.address,
141
+ account.connector?.id,
142
+ disconnect,
143
+ ],
144
+ );
145
+
146
+ return (
147
+ <ConnectKitContext.Provider value={value}>
148
+ {children}
149
+ <ConnectModal
150
+ open={effectiveOpen}
151
+ connectors={connectors}
152
+ wcProjectId={wcProjectId}
153
+ requiredChainId={requiredChainId}
154
+ renderConnectError={renderConnectError}
155
+ onConnect={handleModalConnect}
156
+ onClose={close}
157
+ onLog={onLog}
158
+ />
159
+ </ConnectKitContext.Provider>
160
+ );
161
+ };
162
+
163
+ export const useConnectKit = (): ConnectKitContextValue => {
164
+ const ctx = useContext(ConnectKitContext);
165
+ if (!ctx) {
166
+ throw new Error('useConnectKit must be used within a ConnectKitProvider');
167
+ }
168
+ return ctx;
169
+ };
@@ -140,6 +140,44 @@
140
140
  &:hover {
141
141
  background: rgba(255, 255, 255, 0.22);
142
142
  }
143
+
144
+ &:disabled {
145
+ opacity: 0.6;
146
+ cursor: not-allowed;
147
+ }
148
+ }
149
+
150
+ .ck-btn-secondary {
151
+ display: inline-flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ padding: 8px 16px;
155
+ border-radius: 8px;
156
+ font-size: 14px;
157
+ font-weight: 500;
158
+ cursor: pointer;
159
+ white-space: nowrap;
160
+ color: rgba(255, 255, 255, 0.75);
161
+ background: transparent;
162
+ border: 1px solid rgba(255, 255, 255, 0.18);
163
+
164
+ &:hover {
165
+ color: #fff;
166
+ border-color: rgba(255, 255, 255, 0.32);
167
+ }
168
+ }
169
+
170
+ .ck-view__actions {
171
+ display: flex;
172
+ flex-direction: column;
173
+ gap: 8px;
174
+ width: 100%;
175
+ margin-top: 16px;
176
+ }
177
+
178
+ .ck-view__caption--error {
179
+ color: #ff8080;
180
+ margin-top: 8px;
143
181
  }
144
182
 
145
183
  // ── Connector list ────────────────────────────────────────