@gluwa/connect-kit 0.2.0-next.8 → 0.2.1-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 +37 -0
- package/assets/wc.png +0 -0
- package/dist/{chunk-VE5LB7BN.js → chunk-CJANPJHY.js} +4 -2
- package/dist/credit-connect.js +1 -1
- package/dist/index.css +211 -156
- package/dist/index.d.ts +4 -1
- package/dist/index.js +521 -197
- package/dist/package.json +1 -1
- package/package.json +6 -6
- package/src/ConnectKitProvider.tsx +8 -1
- package/src/components/qrArea/index.tsx +28 -4
- package/src/components/qrArea/style.scss +85 -34
- package/src/components/toast/index.tsx +41 -0
- package/src/components/toast/style.scss +108 -0
- package/src/core/config.ts +23 -2
- package/src/core/deeplinkSignal.ts +25 -0
- package/src/creditConnectConnector.ts +15 -5
- package/src/events/account.ts +15 -6
- package/src/hooks/useConnectBanner.ts +79 -0
- package/src/hooks/useConnectTimeout.ts +21 -9
- package/src/hooks/useMetaMaskExtension.ts +30 -0
- package/src/hooks/useWCState.ts +14 -10
- package/src/hooks/useWagmiConnect.ts +42 -20
- package/src/index.ts +1 -0
- package/src/layout/allWallets/index.tsx +17 -3
- package/src/layout/allWallets/style.scss +2 -7
- package/src/layout/connectDialog/asset/warningIcon.tsx +21 -0
- package/src/layout/connectDialog/idle/assets/banner-placeholder.png +0 -0
- package/src/layout/connectDialog/idle/index.tsx +40 -16
- package/src/layout/connectDialog/idle/style.scss +42 -51
- package/src/layout/connectDialog/index.tsx +196 -47
- package/src/layout/connectDialog/metaMask/index.tsx +3 -2
- package/src/layout/connectDialog/qr/index.tsx +3 -1
- package/src/layout/connectDialog/style.scss +2 -49
- package/src/layout/connectDialog/timeout/index.tsx +0 -5
- package/src/layout/connectDialog/timeout/style.scss +0 -5
- package/src/layout/connectDialog/walletQR/index.tsx +3 -1
- package/src/layout/detailPanel/index.tsx +39 -6
- package/src/layout/detailPanel/style.scss +7 -1
- package/src/layout/selectorPanel/index.tsx +1 -0
- package/src/types.ts +20 -0
- package/src/utils/platform.ts +7 -4
- package/src/wallet-display-override.ts +15 -0
- package/src/components/snackbar/index.tsx +0 -25
- package/src/components/snackbar/style.scss +0 -51
package/dist/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gluwa/connect-kit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1-next.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"qrcode.react": "^4.2.0",
|
|
27
27
|
"wagmi": "^2.15.6",
|
|
28
28
|
"@wagmi/core": "^2.16.7",
|
|
29
|
-
"@gluwa/credit-connect-
|
|
30
|
-
"@gluwa/credit-connect-
|
|
31
|
-
"@gluwa/credit-connect-
|
|
32
|
-
"@gluwa/credit-connect-hub-node-ws": "0.1.
|
|
29
|
+
"@gluwa/credit-connect-storage-local": "0.2.1-next.7",
|
|
30
|
+
"@gluwa/credit-connect-e2ee-tweetnacl": "0.2.1-next.7",
|
|
31
|
+
"@gluwa/credit-connect-sdk": "0.2.1-next.7",
|
|
32
|
+
"@gluwa/credit-connect-hub-node-ws": "0.1.1-next.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@tanstack/react-query": "^5.62.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"typescript-eslint": "^8.18.2",
|
|
44
44
|
"viem": "^2.31.7",
|
|
45
45
|
"wagmi": "^2.15.6",
|
|
46
|
-
"@gluwa/credit-connect-sdk": "0.2.
|
|
46
|
+
"@gluwa/credit-connect-sdk": "0.2.1-next.7"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@tanstack/react-query": ">=5.0.0",
|
|
@@ -18,6 +18,7 @@ import { getConfig, getKitConfig, initConfig } from './core/config';
|
|
|
18
18
|
import { resolveConnectorId } from './connector-meta';
|
|
19
19
|
import type {
|
|
20
20
|
ConnectErrorContext,
|
|
21
|
+
ConnectBannerSiteKey,
|
|
21
22
|
ConnectKitConfig,
|
|
22
23
|
ConnectKitTheme,
|
|
23
24
|
ConnectResult,
|
|
@@ -30,6 +31,8 @@ const internalQueryClient = new QueryClient();
|
|
|
30
31
|
|
|
31
32
|
export interface ConnectKitProviderProps {
|
|
32
33
|
children: ReactNode;
|
|
34
|
+
siteKey: ConnectBannerSiteKey;
|
|
35
|
+
connectBannerUrl: string;
|
|
33
36
|
kitConfig?: ConnectKitConfig;
|
|
34
37
|
connectors: Connectors;
|
|
35
38
|
requiredChainId?: number;
|
|
@@ -90,6 +93,8 @@ type ConnectKitProviderInnerProps = Omit<ConnectKitProviderProps, 'kitConfig'> &
|
|
|
90
93
|
|
|
91
94
|
const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
|
|
92
95
|
children,
|
|
96
|
+
siteKey,
|
|
97
|
+
connectBannerUrl,
|
|
93
98
|
connectors,
|
|
94
99
|
kitConfig,
|
|
95
100
|
requiredChainId,
|
|
@@ -154,7 +159,7 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
|
|
|
154
159
|
} finally {
|
|
155
160
|
if (isMountedRef.current) setIsSwitchingChain(false);
|
|
156
161
|
}
|
|
157
|
-
}, [requiredChainId, switchChainAsync]);
|
|
162
|
+
}, [requiredChainId, switchChainAsync, account.chainId]);
|
|
158
163
|
|
|
159
164
|
// ConnectDialog 의 wagmi connect 성공 → pendingResult set → 아래 useEffect 가
|
|
160
165
|
// account.status === 'connected' 확인 후 외부 onConnect 호출 + modal close.
|
|
@@ -229,6 +234,8 @@ const ConnectKitProviderInner: FC<ConnectKitProviderInnerProps> = ({
|
|
|
229
234
|
{children}
|
|
230
235
|
<ConnectDialog
|
|
231
236
|
open={effectiveOpen}
|
|
237
|
+
siteKey={siteKey}
|
|
238
|
+
connectBannerUrl={connectBannerUrl}
|
|
232
239
|
theme={theme}
|
|
233
240
|
connectors={resolveConnectorsFromConfig(connectors)}
|
|
234
241
|
wcProjectId={kitConfig.wcProjectId}
|
|
@@ -12,13 +12,22 @@ interface QrAreaProps {
|
|
|
12
12
|
hasBg?: boolean;
|
|
13
13
|
label?: string;
|
|
14
14
|
onCopy?: () => void;
|
|
15
|
+
onOpenApp?: () => void;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const BG_COLOR = '#ffffff';
|
|
18
19
|
const FG_COLOR = '#000000';
|
|
19
20
|
const FG_GRADIENT = 'url(#ck-qr-gradient)';
|
|
20
21
|
|
|
21
|
-
export const QrArea: FC<QrAreaProps> = ({
|
|
22
|
+
export const QrArea: FC<QrAreaProps> = ({
|
|
23
|
+
uri,
|
|
24
|
+
status,
|
|
25
|
+
logoUrl,
|
|
26
|
+
hasBg = false,
|
|
27
|
+
label,
|
|
28
|
+
onCopy,
|
|
29
|
+
onOpenApp,
|
|
30
|
+
}) => {
|
|
22
31
|
const bgColor = hasBg ? BG_COLOR : 'transparent';
|
|
23
32
|
const fgColor = hasBg ? FG_COLOR : FG_GRADIENT;
|
|
24
33
|
|
|
@@ -27,7 +36,9 @@ export const QrArea: FC<QrAreaProps> = ({ uri, status, logoUrl, hasBg = false, l
|
|
|
27
36
|
<div className="ck-qr-group">
|
|
28
37
|
<figure className={`ck-qr ck-qr-${status}`} aria-label="QR Code">
|
|
29
38
|
{status === 'loading' && (
|
|
30
|
-
<div className="ck-qr-skeleton" aria-busy="true" aria-label="Loading QR code"
|
|
39
|
+
<div className="ck-qr-skeleton" aria-busy="true" aria-label="Loading QR code">
|
|
40
|
+
<div className="ck-qr-skeleton-inner" />
|
|
41
|
+
</div>
|
|
31
42
|
)}
|
|
32
43
|
{status === 'ready' && uri && (
|
|
33
44
|
<div
|
|
@@ -58,10 +69,23 @@ export const QrArea: FC<QrAreaProps> = ({ uri, status, logoUrl, hasBg = false, l
|
|
|
58
69
|
</div>
|
|
59
70
|
)}
|
|
60
71
|
</figure>
|
|
61
|
-
{(label || onCopy) && (
|
|
72
|
+
{(label || onCopy || onOpenApp) && (
|
|
62
73
|
<div className="ck-qr-text">
|
|
63
74
|
{label && <p className="ck-qr-label">{label}</p>}
|
|
64
|
-
{
|
|
75
|
+
{onOpenApp &&
|
|
76
|
+
(status === 'loading' ? (
|
|
77
|
+
<span className="ck-qr-open-app-skeleton" aria-hidden="true" />
|
|
78
|
+
) : (
|
|
79
|
+
<button type="button" className="ck-qr-open-app" onClick={onOpenApp}>
|
|
80
|
+
Open app
|
|
81
|
+
</button>
|
|
82
|
+
))}
|
|
83
|
+
{onCopy &&
|
|
84
|
+
(status === 'loading' ? (
|
|
85
|
+
<span className="ck-qr-copy-skeleton" aria-hidden="true" />
|
|
86
|
+
) : (
|
|
87
|
+
<CopyLink onCopy={onCopy} />
|
|
88
|
+
))}
|
|
65
89
|
</div>
|
|
66
90
|
)}
|
|
67
91
|
</div>
|
|
@@ -2,20 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
.ck-qr-area {
|
|
4
4
|
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
5
6
|
align-items: center;
|
|
6
|
-
justify-content: center;
|
|
7
7
|
flex: 1;
|
|
8
8
|
width: 100%;
|
|
9
|
-
padding
|
|
9
|
+
padding: 32px 0;
|
|
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;
|
|
@@ -24,6 +17,10 @@
|
|
|
24
17
|
gap: 6px;
|
|
25
18
|
width: 100%;
|
|
26
19
|
margin-top: 24px;
|
|
20
|
+
|
|
21
|
+
@include tablet {
|
|
22
|
+
gap: 12px;
|
|
23
|
+
}
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
.ck-qr-label {
|
|
@@ -32,6 +29,46 @@
|
|
|
32
29
|
text-align: center;
|
|
33
30
|
}
|
|
34
31
|
|
|
32
|
+
.ck-root .ck-qr-open-app {
|
|
33
|
+
display: none;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
padding: 12px 28px;
|
|
37
|
+
border: 1px solid var(--semantic-divider-divider-4);
|
|
38
|
+
border-radius: 500px;
|
|
39
|
+
@include textToken('label4-bold');
|
|
40
|
+
color: var(--scale-gray-900);
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
transition: background 0.15s ease;
|
|
43
|
+
|
|
44
|
+
@include tablet {
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&:hover {
|
|
49
|
+
background: var(--scale-gray-100);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.ck-qr-open-app-skeleton {
|
|
54
|
+
display: none;
|
|
55
|
+
width: 116px;
|
|
56
|
+
height: 44px;
|
|
57
|
+
border-radius: 500px;
|
|
58
|
+
background: linear-gradient(
|
|
59
|
+
132deg,
|
|
60
|
+
var(--scale-gray-100) 36%,
|
|
61
|
+
var(--scale-gray-200) 51%,
|
|
62
|
+
var(--scale-gray-100) 66%
|
|
63
|
+
);
|
|
64
|
+
background-size: 200% 100%;
|
|
65
|
+
animation: ck-qr-shimmer 1.5s linear infinite;
|
|
66
|
+
|
|
67
|
+
@include tablet {
|
|
68
|
+
display: block;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
35
72
|
.ck-qr {
|
|
36
73
|
display: flex;
|
|
37
74
|
flex-direction: column;
|
|
@@ -42,18 +79,52 @@
|
|
|
42
79
|
outline: none;
|
|
43
80
|
}
|
|
44
81
|
|
|
45
|
-
.ck-qr-
|
|
82
|
+
.ck-qr-skeleton {
|
|
83
|
+
padding: 20px;
|
|
46
84
|
border: 1px solid var(--semantic-divider-divider-1);
|
|
85
|
+
border-radius: 20px;
|
|
47
86
|
}
|
|
48
87
|
|
|
88
|
+
.ck-qr-skeleton-inner {
|
|
89
|
+
display: block;
|
|
90
|
+
width: 246px;
|
|
91
|
+
height: 246px;
|
|
92
|
+
background: linear-gradient(
|
|
93
|
+
132deg,
|
|
94
|
+
var(--scale-gray-100) 36%,
|
|
95
|
+
var(--scale-gray-200) 51%,
|
|
96
|
+
var(--scale-gray-100) 66%
|
|
97
|
+
);
|
|
98
|
+
background-size: 200% 100%;
|
|
99
|
+
animation: ck-qr-shimmer 1.5s linear infinite;
|
|
100
|
+
|
|
101
|
+
@include tablet {
|
|
102
|
+
width: 198px;
|
|
103
|
+
height: 198px;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Timeout ───────────────────────────────────────────────────────────────────
|
|
49
108
|
.ck-qr-timeout {
|
|
109
|
+
width: 288px;
|
|
110
|
+
height: 288px;
|
|
50
111
|
border: 1px solid var(--semantic-divider-divider-1);
|
|
112
|
+
background: var(--scale-gray-100);
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
|
|
117
|
+
@include tablet {
|
|
118
|
+
width: 240px;
|
|
119
|
+
height: 240px;
|
|
120
|
+
}
|
|
51
121
|
}
|
|
52
122
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
width:
|
|
56
|
-
height:
|
|
123
|
+
.ck-qr-copy-skeleton {
|
|
124
|
+
display: block;
|
|
125
|
+
width: 84px;
|
|
126
|
+
height: 20px;
|
|
127
|
+
border-radius: 4px;
|
|
57
128
|
background: linear-gradient(
|
|
58
129
|
132deg,
|
|
59
130
|
var(--scale-gray-100) 36%,
|
|
@@ -62,11 +133,6 @@
|
|
|
62
133
|
);
|
|
63
134
|
background-size: 200% 100%;
|
|
64
135
|
animation: ck-qr-shimmer 1.5s linear infinite;
|
|
65
|
-
|
|
66
|
-
@include tablet {
|
|
67
|
-
width: 200px;
|
|
68
|
-
height: 200px;
|
|
69
|
-
}
|
|
70
136
|
}
|
|
71
137
|
|
|
72
138
|
@keyframes ck-qr-shimmer {
|
|
@@ -93,21 +159,6 @@
|
|
|
93
159
|
}
|
|
94
160
|
}
|
|
95
161
|
|
|
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
162
|
.ck-qr-timeout-inner {
|
|
112
163
|
display: flex;
|
|
113
164
|
flex-direction: column;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type FC, useEffect } from 'react';
|
|
2
|
+
import './style.scss';
|
|
3
|
+
import { CloseIcon } from '../../layout/connectDialog/asset/closeIcon';
|
|
4
|
+
import { CheckIcon } from '../../layout/connectDialog/asset/checkIcon';
|
|
5
|
+
import { WarningIcon } from '../../layout/connectDialog/asset/warningIcon';
|
|
6
|
+
|
|
7
|
+
export type ToastVariant = 'success' | 'warning';
|
|
8
|
+
|
|
9
|
+
const AUTO_CLOSE_MS = 3000;
|
|
10
|
+
|
|
11
|
+
interface ToastProps {
|
|
12
|
+
message: string;
|
|
13
|
+
variant?: ToastVariant;
|
|
14
|
+
onClose?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Toast: FC<ToastProps> = ({ message, variant = 'success', onClose }) => {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!onClose) return;
|
|
20
|
+
const timer = setTimeout(onClose, AUTO_CLOSE_MS);
|
|
21
|
+
return () => clearTimeout(timer);
|
|
22
|
+
}, [variant, onClose, message]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="ck-toast" data-variant={variant} role="status" aria-live="polite">
|
|
26
|
+
<div className="ck-toast-content">
|
|
27
|
+
{variant === 'warning' ? (
|
|
28
|
+
<WarningIcon className="ck-toast-icon ck-toast-icon--warning" />
|
|
29
|
+
) : (
|
|
30
|
+
<CheckIcon className="ck-toast-icon ck-toast-icon--success" />
|
|
31
|
+
)}
|
|
32
|
+
<p className="ck-toast-message">{message}</p>
|
|
33
|
+
</div>
|
|
34
|
+
{onClose && (
|
|
35
|
+
<button type="button" className="ck-toast-close" onClick={onClose} aria-label="Close">
|
|
36
|
+
<CloseIcon width={20} height={20} />
|
|
37
|
+
</button>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
@use '../../style/mixin.scss' as *;
|
|
2
|
+
|
|
3
|
+
.ck-toast-stack {
|
|
4
|
+
position: fixed;
|
|
5
|
+
top: clamp(20px, 9.2vh, 99px);
|
|
6
|
+
left: 50%;
|
|
7
|
+
z-index: 1001;
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 8px;
|
|
12
|
+
max-width: calc(100vw - 40px);
|
|
13
|
+
transform: translateX(-50%);
|
|
14
|
+
|
|
15
|
+
@include tablet {
|
|
16
|
+
top: clamp(
|
|
17
|
+
calc(env(safe-area-inset-top, 0px) + 20px),
|
|
18
|
+
calc(env(safe-area-inset-top, 0px) + 20px),
|
|
19
|
+
calc(env(safe-area-inset-top, 0px) + 20px)
|
|
20
|
+
);
|
|
21
|
+
left: 20px;
|
|
22
|
+
right: 20px;
|
|
23
|
+
align-items: stretch;
|
|
24
|
+
max-width: none;
|
|
25
|
+
transform: none;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ck-toast {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: space-between;
|
|
33
|
+
gap: 16px;
|
|
34
|
+
width: max-content;
|
|
35
|
+
min-width: min(400px, calc(100vw - 40px));
|
|
36
|
+
max-width: 100%;
|
|
37
|
+
padding: 12px 16px;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
background: var(--semantic-paper-sheet);
|
|
40
|
+
border: 1px solid var(--semantic-divider-divider-1);
|
|
41
|
+
animation: ck-toast-in 0.2s ease;
|
|
42
|
+
|
|
43
|
+
@include tablet {
|
|
44
|
+
width: 100%;
|
|
45
|
+
min-width: 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@keyframes ck-toast-in {
|
|
50
|
+
from {
|
|
51
|
+
opacity: 0;
|
|
52
|
+
transform: translateY(8px);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
to {
|
|
56
|
+
opacity: 1;
|
|
57
|
+
transform: translateY(0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ck-toast-content {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
|
|
66
|
+
@include tablet {
|
|
67
|
+
flex: 1;
|
|
68
|
+
min-width: 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.ck-toast-icon {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
width: 24px;
|
|
77
|
+
height: 24px;
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.ck-toast-icon--success {
|
|
82
|
+
color: var(--semantic-information-success);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.ck-toast-icon--warning {
|
|
86
|
+
color: var(--semantic-information-caution);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.ck-toast-message {
|
|
90
|
+
@include textToken('body3-l1-medium');
|
|
91
|
+
color: var(--scale-gray-900);
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
|
|
94
|
+
@include tablet {
|
|
95
|
+
flex: 1;
|
|
96
|
+
min-width: 0;
|
|
97
|
+
white-space: normal;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.ck-root .ck-toast-close {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
flex-shrink: 0;
|
|
106
|
+
color: var(--scale-gray-700);
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createConfig, createStorage } from 'wagmi';
|
|
2
2
|
import { metaMask, walletConnect, injected } from 'wagmi/connectors';
|
|
3
|
+
import { hasInjectedMobileProvider } from '../utils/platform';
|
|
4
|
+
import { markDeeplinkOpened } from './deeplinkSignal';
|
|
3
5
|
import type { Config } from 'wagmi';
|
|
4
6
|
import type { ConnectKitConfig } from '../types';
|
|
5
7
|
import { creditConnectConnector } from '../creditConnectConnector';
|
|
@@ -44,11 +46,26 @@ export const initConfig = (kitConfig: ConnectKitConfig): Config => {
|
|
|
44
46
|
connectors.push(walletConnect({ projectId: kitConfig.wcProjectId, showQrModal: false }));
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
// MetaMask는 metaMaskSDK 커넥터 하나로만 연결한다.
|
|
50
|
+
// generic injected 커넥터를 함께 두면 같은 window.ethereum에 이중 바인딩되어
|
|
51
|
+
// wagmi connections 가 2개가 되고, dApp의 다중연결 감지 로직이 오작동한다.
|
|
47
52
|
connectors.push(
|
|
48
|
-
metaMask({
|
|
49
|
-
|
|
53
|
+
metaMask({
|
|
54
|
+
headless: true,
|
|
55
|
+
useDeeplink: true,
|
|
56
|
+
storage: { enabled: true },
|
|
57
|
+
openDeeplink: (url) => {
|
|
58
|
+
markDeeplinkOpened();
|
|
59
|
+
window.location.replace(url);
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
50
62
|
);
|
|
51
63
|
|
|
64
|
+
// 인앱 지갑 웹뷰(모바일 + 호스트 window.ethereum 주입)
|
|
65
|
+
if (hasInjectedMobileProvider()) {
|
|
66
|
+
connectors.push(injected({ shimDisconnect: false }));
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
if (kitConfig.creditConnect) {
|
|
53
70
|
connectors.push(
|
|
54
71
|
creditConnectConnector({
|
|
@@ -73,6 +90,10 @@ export const initConfig = (kitConfig: ConnectKitConfig): Config => {
|
|
|
73
90
|
connectors,
|
|
74
91
|
transports: kitConfig.transports,
|
|
75
92
|
storage: isBrowser ? createStorage({ storage: window.localStorage }) : undefined,
|
|
93
|
+
// EIP-6963 자동 탐지 비활성화: 켜두면 MetaMask 가 io.metamask(type='injected')로
|
|
94
|
+
// 다시 발견되어 metaMaskSDK 와 이중 커넥터가 되고 connections 가 2개로 잡힌다.
|
|
95
|
+
// MetaMask 는 위 metaMaskSDK 커넥터로만 붙이므로 탐지가 필요 없다.
|
|
96
|
+
multiInjectedProviderDiscovery: false,
|
|
76
97
|
});
|
|
77
98
|
slot.kitConfig = kitConfig;
|
|
78
99
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type Signal = { opened: boolean };
|
|
2
|
+
|
|
3
|
+
const SIGNAL_KEY = Symbol.for('@gluwa/connect-kit:deeplink-signal');
|
|
4
|
+
|
|
5
|
+
type GlobalWithSignal = typeof globalThis & {
|
|
6
|
+
[SIGNAL_KEY]?: Signal;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const getSignal = (): Signal => {
|
|
10
|
+
const g = globalThis as GlobalWithSignal;
|
|
11
|
+
if (!g[SIGNAL_KEY]) {
|
|
12
|
+
g[SIGNAL_KEY] = { opened: false };
|
|
13
|
+
}
|
|
14
|
+
return g[SIGNAL_KEY];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const resetDeeplinkSignal = (): void => {
|
|
18
|
+
getSignal().opened = false;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const markDeeplinkOpened = (): void => {
|
|
22
|
+
getSignal().opened = true;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const hasDeeplinkOpened = (): boolean => getSignal().opened;
|
|
@@ -29,7 +29,10 @@ const CONNECTOR_TYPE = 'credit-connect';
|
|
|
29
29
|
const castConnectResult = <withCapabilities extends boolean>(value: unknown) =>
|
|
30
30
|
value as {
|
|
31
31
|
accounts: withCapabilities extends true
|
|
32
|
-
? ReadonlyArray<{
|
|
32
|
+
? ReadonlyArray<{
|
|
33
|
+
address: Address;
|
|
34
|
+
capabilities: Record<string, unknown>;
|
|
35
|
+
}>
|
|
33
36
|
: readonly Address[];
|
|
34
37
|
chainId: number;
|
|
35
38
|
};
|
|
@@ -92,7 +95,7 @@ export const creditConnectConnector = (
|
|
|
92
95
|
if (state.status === 'CONNECTING' && state.connectingPayload) {
|
|
93
96
|
// eslint-disable-next-line no-console
|
|
94
97
|
console.log('[CC connector] emit display_uri', {
|
|
95
|
-
|
|
98
|
+
ready: true,
|
|
96
99
|
});
|
|
97
100
|
options.onDisplayUri?.(state.connectingPayload);
|
|
98
101
|
emitWagmiMessage?.('display_uri', state.connectingPayload);
|
|
@@ -109,7 +112,9 @@ export const creditConnectConnector = (
|
|
|
109
112
|
|
|
110
113
|
manager.on('sessionExpired', (payload) => {
|
|
111
114
|
// eslint-disable-next-line no-console
|
|
112
|
-
console.log('[CC connector] session expired → emit session_expired',
|
|
115
|
+
console.log('[CC connector] session expired → emit session_expired', {
|
|
116
|
+
reason: payload.reason,
|
|
117
|
+
});
|
|
113
118
|
emitWagmiMessage?.('session_expired', payload);
|
|
114
119
|
});
|
|
115
120
|
|
|
@@ -318,7 +323,10 @@ export const creditConnectConnector = (
|
|
|
318
323
|
withCapabilities?: withCapabilities | boolean;
|
|
319
324
|
}): Promise<{
|
|
320
325
|
accounts: withCapabilities extends true
|
|
321
|
-
? ReadonlyArray<{
|
|
326
|
+
? ReadonlyArray<{
|
|
327
|
+
address: Address;
|
|
328
|
+
capabilities: Record<string, unknown>;
|
|
329
|
+
}>
|
|
322
330
|
: readonly Address[];
|
|
323
331
|
chainId: number;
|
|
324
332
|
}> {
|
|
@@ -412,7 +420,9 @@ export const creditConnectConnector = (
|
|
|
412
420
|
);
|
|
413
421
|
|
|
414
422
|
if (!supported) {
|
|
415
|
-
const err = new Error(`Unsupported chain: ${chainId}`) as Error & {
|
|
423
|
+
const err = new Error(`Unsupported chain: ${chainId}`) as Error & {
|
|
424
|
+
code?: number;
|
|
425
|
+
};
|
|
416
426
|
err.code = 4902;
|
|
417
427
|
throw err;
|
|
418
428
|
}
|
package/src/events/account.ts
CHANGED
|
@@ -24,28 +24,37 @@ export const reconnect = async (): Promise<ReconnectReturnType> => {
|
|
|
24
24
|
|
|
25
25
|
if (getKitConfig().autoConnectInjected !== false) {
|
|
26
26
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
// 자동 재연결
|
|
28
|
+
// 인앱 지갑 웹뷰: 호스트 주입 provider 는 injected 커넥터로만
|
|
29
|
+
const injectedConnector = config.connectors.find(
|
|
30
|
+
(connector) => connector.type === 'injected',
|
|
31
|
+
);
|
|
32
|
+
const metaMaskConnector = config.connectors.find(
|
|
33
|
+
(connector) => connector.id === 'metaMaskSDK',
|
|
34
|
+
);
|
|
35
|
+
const target = injectedConnector ?? metaMaskConnector;
|
|
36
|
+
if (target) {
|
|
37
|
+
const provider = (await target.getProvider()) as
|
|
30
38
|
| { request?: (args: { method: string }) => Promise<unknown> }
|
|
31
39
|
| undefined;
|
|
32
40
|
const accounts = provider?.request
|
|
33
41
|
? await provider.request({ method: 'eth_accounts' })
|
|
34
42
|
: [];
|
|
35
43
|
if (Array.isArray(accounts) && accounts.length > 0) {
|
|
36
|
-
await wagmiConnect(config, { connector:
|
|
44
|
+
await wagmiConnect(config, { connector: target });
|
|
37
45
|
}
|
|
38
46
|
}
|
|
39
47
|
} catch (error) {
|
|
40
48
|
// eslint-disable-next-line no-console
|
|
41
|
-
console.warn('
|
|
49
|
+
console.warn('auto-connect skipped (non-fatal):', error);
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
if (wagmiGetAccount(config).status === 'connected') {
|
|
46
54
|
return [];
|
|
47
55
|
}
|
|
48
|
-
|
|
56
|
+
const result = await wagmiReconnect(config);
|
|
57
|
+
return result;
|
|
49
58
|
};
|
|
50
59
|
|
|
51
60
|
export const disconnectAll = async (
|