@gluwa/connect-kit 0.2.0-next.8 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @gluwa/wallet-kit
2
2
 
3
+ ## 0.2.0-next.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 18e631a: fix bugs
8
+
3
9
  ## 0.2.0-next.8
4
10
 
5
11
  ### Patch Changes
package/dist/index.css CHANGED
@@ -564,8 +564,9 @@
564
564
  width: 100%;
565
565
  max-width: none;
566
566
  grid-template-columns: 1fr;
567
+ grid-template-rows: minmax(0, 1fr);
567
568
  max-height: 82dvh;
568
- height: 100%;
569
+ height: auto;
569
570
  border-radius: 12px 12px 0 0;
570
571
  }
571
572
  .ck-root .ck-dialog[data-step=selector] .ck-detail {
@@ -877,6 +878,7 @@
877
878
  .ck-detail {
878
879
  display: flex;
879
880
  flex-direction: column;
881
+ min-width: 0;
880
882
  background: var(--semantic-paper-dialog);
881
883
  min-height: 0;
882
884
  position: relative;
@@ -959,6 +961,9 @@
959
961
  .ck-detail-body::-webkit-scrollbar-thumb:hover {
960
962
  background-color: var(--scale-gray-400);
961
963
  }
964
+ .ck-detail-body:has(.ck-all-wallets) {
965
+ padding-bottom: 0;
966
+ }
962
967
 
963
968
  /* src/layout/connectDialog/idle/style.scss */
964
969
  .ck-idle {
@@ -1158,15 +1163,15 @@
1158
1163
  overflow: hidden;
1159
1164
  outline: none;
1160
1165
  }
1161
- .ck-qr-loading {
1162
- border: 1px solid var(--semantic-divider-divider-1);
1163
- }
1164
- .ck-qr-timeout {
1166
+ .ck-qr-skeleton {
1167
+ padding: 20px;
1165
1168
  border: 1px solid var(--semantic-divider-divider-1);
1169
+ border-radius: 20px;
1166
1170
  }
1167
- .ck-qr-skeleton {
1168
- width: 248px;
1169
- height: 248px;
1171
+ .ck-qr-skeleton-inner {
1172
+ display: block;
1173
+ width: 246px;
1174
+ height: 246px;
1170
1175
  background:
1171
1176
  linear-gradient(
1172
1177
  132deg,
@@ -1177,9 +1182,24 @@
1177
1182
  animation: ck-qr-shimmer 1.5s linear infinite;
1178
1183
  }
1179
1184
  @media (max-width: 767px) {
1180
- .ck-qr-skeleton {
1181
- width: 200px;
1182
- height: 200px;
1185
+ .ck-qr-skeleton-inner {
1186
+ width: 198px;
1187
+ height: 198px;
1188
+ }
1189
+ }
1190
+ .ck-qr-timeout {
1191
+ width: 288px;
1192
+ height: 288px;
1193
+ border: 1px solid var(--semantic-divider-divider-1);
1194
+ background: var(--scale-gray-100);
1195
+ display: flex;
1196
+ align-items: center;
1197
+ justify-content: center;
1198
+ }
1199
+ @media (max-width: 767px) {
1200
+ .ck-qr-timeout {
1201
+ width: 240px;
1202
+ height: 240px;
1183
1203
  }
1184
1204
  }
1185
1205
  @keyframes ck-qr-shimmer {
@@ -1207,20 +1227,6 @@
1207
1227
  height: 198px;
1208
1228
  }
1209
1229
  }
1210
- .ck-qr-timeout {
1211
- width: 248px;
1212
- height: 248px;
1213
- background: var(--scale-gray-100);
1214
- display: flex;
1215
- align-items: center;
1216
- justify-content: center;
1217
- }
1218
- @media (max-width: 767px) {
1219
- .ck-qr-timeout {
1220
- width: 200px;
1221
- height: 200px;
1222
- }
1223
- }
1224
1230
  .ck-qr-timeout-inner {
1225
1231
  display: flex;
1226
1232
  flex-direction: column;
@@ -1690,15 +1696,8 @@
1690
1696
  gap: 8px;
1691
1697
  flex-shrink: 0;
1692
1698
  }
1693
- @media (max-width: 392px) {
1694
- .ck-all-wallets-controls {
1695
- flex-direction: column;
1696
- align-items: flex-end;
1697
- gap: 12px;
1698
- }
1699
- .ck-all-wallets-controls .ck-search {
1700
- width: 100%;
1701
- }
1699
+ .ck-all-wallets-controls .ck-search {
1700
+ min-width: 0;
1702
1701
  }
1703
1702
  .ck-wc-filter-btn {
1704
1703
  display: inline-flex;
package/dist/index.js CHANGED
@@ -194,13 +194,13 @@ var disconnectAll = /* @__PURE__ */ __name(async (onLog) => {
194
194
  }, "disconnectAll");
195
195
 
196
196
  // src/ConnectKitProvider.tsx
197
- import { createContext, useCallback as useCallback4, useContext, useEffect as useEffect5, useMemo as useMemo2, useRef as useRef5, useState as useState5 } from "react";
197
+ import { createContext, useCallback as useCallback4, useContext, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef5, useState as useState6 } from "react";
198
198
  import { WagmiProvider, useAccount, useConfig as useConfig3, useDisconnect, useSwitchChain } from "wagmi";
199
199
  import { getAccount as getAccount2 } from "@wagmi/core";
200
200
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
201
201
 
202
202
  // src/layout/connectDialog/index.tsx
203
- import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
203
+ import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
204
204
  import { useConfig as useConfig2 } from "wagmi";
205
205
 
206
206
  // src/layout/connectDialog/asset/closeIcon.tsx
@@ -504,7 +504,9 @@ var QrArea = /* @__PURE__ */ __name(({ uri, status, logoUrl, hasBg = false, labe
504
504
  className: "ck-qr-skeleton",
505
505
  "aria-busy": "true",
506
506
  "aria-label": "Loading QR code"
507
- }), status === "ready" && uri && /* @__PURE__ */ React.createElement("div", {
507
+ }, /* @__PURE__ */ React.createElement("div", {
508
+ className: "ck-qr-skeleton-inner"
509
+ })), status === "ready" && uri && /* @__PURE__ */ React.createElement("div", {
508
510
  className: "ck-qr-frame",
509
511
  style: {
510
512
  "background": bgColor,
@@ -869,7 +871,7 @@ var AllWallets = /* @__PURE__ */ __name(({ wallets, isLoading, onSelectWallet })
869
871
  }))), !isLoading && filtered.length === 0 ? /* @__PURE__ */ React.createElement("div", {
870
872
  className: "ck-all-wallets-empty",
871
873
  role: "status"
872
- }, /* @__PURE__ */ React.createElement("p", null, "No wallets found")) : /* @__PURE__ */ React.createElement(WalletGrid, {
874
+ }, /* @__PURE__ */ React.createElement("p", null, "No Wallet found")) : /* @__PURE__ */ React.createElement(WalletGrid, {
873
875
  wallets: filtered,
874
876
  isLoading,
875
877
  onSelect: onSelectWallet
@@ -1024,18 +1026,36 @@ var Snackbar = /* @__PURE__ */ __name(({ message, onClose }) => {
1024
1026
  })));
1025
1027
  }, "Snackbar");
1026
1028
 
1029
+ // src/hooks/useMetaMaskExtension.ts
1030
+ import { useEffect, useState as useState2 } from "react";
1031
+ var METAMASK_RDNS = "io.metamask";
1032
+ var isMetaMaskAnnouncement = /* @__PURE__ */ __name((event) => {
1033
+ var _a, _b;
1034
+ const detail = event.detail;
1035
+ return ((_b = (_a = detail == null ? void 0 : detail.info) == null ? void 0 : _a.rdns) == null ? void 0 : _b.toLowerCase()) === METAMASK_RDNS && detail.provider != null;
1036
+ }, "isMetaMaskAnnouncement");
1037
+ var useMetaMaskExtension = /* @__PURE__ */ __name(() => {
1038
+ const [isInstalled, setIsInstalled] = useState2(false);
1039
+ useEffect(() => {
1040
+ const handleAnnouncement = /* @__PURE__ */ __name((event) => {
1041
+ if (isMetaMaskAnnouncement(event)) setIsInstalled(true);
1042
+ }, "handleAnnouncement");
1043
+ window.addEventListener("eip6963:announceProvider", handleAnnouncement);
1044
+ window.dispatchEvent(new Event("eip6963:requestProvider"));
1045
+ return () => {
1046
+ window.removeEventListener("eip6963:announceProvider", handleAnnouncement);
1047
+ };
1048
+ }, []);
1049
+ return isInstalled;
1050
+ }, "useMetaMaskExtension");
1051
+
1027
1052
  // src/hooks/useWagmiConnect.ts
1028
- import { useEffect, useRef, useCallback } from "react";
1053
+ import { useEffect as useEffect2, useRef, useCallback } from "react";
1029
1054
  import { useConnect, useConfig } from "wagmi";
1030
1055
  var hasMetaMaskKeyword = /* @__PURE__ */ __name((value) => value.toLowerCase().includes("metamask"), "hasMetaMaskKeyword");
1031
1056
  var hasWalletConnectKeyword = /* @__PURE__ */ __name((value) => value.toLowerCase().includes("walletconnect"), "hasWalletConnectKeyword");
1032
1057
  var isMetaMaskConnector = /* @__PURE__ */ __name((connector) => {
1033
1058
  if (connector.id === "metaMaskSDK") return true;
1034
- if (connector.id === "injected") {
1035
- if (typeof window === "undefined") return false;
1036
- const eth = window.ethereum;
1037
- return (eth == null ? void 0 : eth.isMetaMask) === true;
1038
- }
1039
1059
  if (hasMetaMaskKeyword(connector.id) || hasMetaMaskKeyword(connector.name)) return true;
1040
1060
  if (connector.rdns) {
1041
1061
  const rdnsList = Array.isArray(connector.rdns) ? connector.rdns : [
@@ -1070,7 +1090,7 @@ var useWagmiConnect = /* @__PURE__ */ __name(({ onConnect, onError, onQrUri, onS
1070
1090
  const onErrorRef = useRef(onError);
1071
1091
  const onQrUriRef = useRef(onQrUri);
1072
1092
  const onSessionExpiredRef = useRef(onSessionExpired);
1073
- useEffect(() => {
1093
+ useEffect2(() => {
1074
1094
  onConnectRef.current = onConnect;
1075
1095
  onErrorRef.current = onError;
1076
1096
  onQrUriRef.current = onQrUri;
@@ -1082,7 +1102,7 @@ var useWagmiConnect = /* @__PURE__ */ __name(({ onConnect, onError, onQrUri, onS
1082
1102
  onSessionExpired
1083
1103
  ]);
1084
1104
  const { connectAsync, reset, isPending } = useConnect();
1085
- useEffect(() => {
1105
+ useEffect2(() => {
1086
1106
  const handler = /* @__PURE__ */ __name((data) => {
1087
1107
  var _a;
1088
1108
  const msg = data;
@@ -1155,19 +1175,19 @@ var useWagmiConnect = /* @__PURE__ */ __name(({ onConnect, onError, onQrUri, onS
1155
1175
  }, "useWagmiConnect");
1156
1176
 
1157
1177
  // src/hooks/useConnectTimeout.ts
1158
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
1178
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
1159
1179
  var DEFAULT_CONNECT_TIMEOUT_MS = 6e5;
1160
1180
  var useConnectTimeout = /* @__PURE__ */ __name(({ connectorId, active, expiredSignal = 0, onRetry }) => {
1161
- const [timedOut, setTimedOut] = useState2(false);
1181
+ const [timedOut, setTimedOut] = useState3(false);
1162
1182
  const fire = useCallback2(() => {
1163
1183
  setTimedOut(true);
1164
1184
  }, []);
1165
- useEffect2(() => {
1185
+ useEffect3(() => {
1166
1186
  setTimedOut(false);
1167
1187
  }, [
1168
1188
  connectorId
1169
1189
  ]);
1170
- useEffect2(() => {
1190
+ useEffect3(() => {
1171
1191
  if (!active || connectorId !== "METAMASK") return;
1172
1192
  const timer = setTimeout(fire, DEFAULT_CONNECT_TIMEOUT_MS);
1173
1193
  return () => clearTimeout(timer);
@@ -1177,7 +1197,7 @@ var useConnectTimeout = /* @__PURE__ */ __name(({ connectorId, active, expiredSi
1177
1197
  fire
1178
1198
  ]);
1179
1199
  const prevSignal = useRef2(expiredSignal);
1180
- useEffect2(() => {
1200
+ useEffect3(() => {
1181
1201
  if (!active || connectorId === "METAMASK" || !connectorId) {
1182
1202
  prevSignal.current = expiredSignal;
1183
1203
  return;
@@ -1205,13 +1225,22 @@ var useConnectTimeout = /* @__PURE__ */ __name(({ connectorId, active, expiredSi
1205
1225
  }, "useConnectTimeout");
1206
1226
 
1207
1227
  // src/hooks/useWCState.ts
1208
- import { useState as useState3, useRef as useRef3, useCallback as useCallback3, useEffect as useEffect3 } from "react";
1209
- var WC_EXPLORER_CREDIT_WALLET_NAME = "Credit Wallet";
1228
+ import { useState as useState4, useRef as useRef3, useCallback as useCallback3, useEffect as useEffect4 } from "react";
1229
+
1230
+ // src/wallet-display-override.ts
1231
+ var CREDIT_WALLET_LISTING_ID = "ce02f1ae74686234eabfa7d31876c442779b730dfe7924a3ca0e6559a7afa30f";
1232
+ var overrideWalletDisplay = /* @__PURE__ */ __name((wallet) => wallet.id === CREDIT_WALLET_LISTING_ID ? {
1233
+ ...wallet,
1234
+ name: "PenguinWallet",
1235
+ imageUrl: penguinwallet_default
1236
+ } : wallet, "overrideWalletDisplay");
1237
+
1238
+ // src/hooks/useWCState.ts
1210
1239
  var sortCreditWalletFirst = /* @__PURE__ */ __name((list) => [
1211
1240
  ...list
1212
1241
  ].sort((a, b) => {
1213
- if (a.name === WC_EXPLORER_CREDIT_WALLET_NAME) return -1;
1214
- if (b.name === WC_EXPLORER_CREDIT_WALLET_NAME) return 1;
1242
+ if (a.id === CREDIT_WALLET_LISTING_ID) return -1;
1243
+ if (b.id === CREDIT_WALLET_LISTING_ID) return 1;
1215
1244
  return 0;
1216
1245
  }), "sortCreditWalletFirst");
1217
1246
  var fetchWCWalletList = /* @__PURE__ */ __name(async (projectId, signal) => {
@@ -1252,16 +1281,16 @@ var fetchWCWalletList = /* @__PURE__ */ __name(async (projectId, signal) => {
1252
1281
  return Array.from(byId.values());
1253
1282
  }, "fetchWCWalletList");
1254
1283
  var useWCState = /* @__PURE__ */ __name(() => {
1255
- const [subView, setSubView] = useState3("qr");
1256
- const [walletList, setWalletList] = useState3([]);
1257
- const [walletListLoading, setWalletListLoading] = useState3(false);
1258
- const [walletListSearch, setWalletListSearch] = useState3("");
1259
- const [filterActive, setFilterActive] = useState3(false);
1260
- const [selectedWallet, setSelectedWallet] = useState3(null);
1284
+ const [subView, setSubView] = useState4("qr");
1285
+ const [walletList, setWalletList] = useState4([]);
1286
+ const [walletListLoading, setWalletListLoading] = useState4(false);
1287
+ const [walletListSearch, setWalletListSearch] = useState4("");
1288
+ const [filterActive, setFilterActive] = useState4(false);
1289
+ const [selectedWallet, setSelectedWallet] = useState4(null);
1261
1290
  const loadedRef = useRef3(false);
1262
1291
  const abortControllerRef = useRef3(null);
1263
1292
  const isMountedRef = useRef3(true);
1264
- useEffect3(() => {
1293
+ useEffect4(() => {
1265
1294
  isMountedRef.current = true;
1266
1295
  return () => {
1267
1296
  var _a;
@@ -1278,7 +1307,7 @@ var useWCState = /* @__PURE__ */ __name(() => {
1278
1307
  setWalletListLoading(true);
1279
1308
  try {
1280
1309
  const list = await fetchWCWalletList(projectId, controller.signal);
1281
- const sorted = sortCreditWalletFirst(list);
1310
+ const sorted = sortCreditWalletFirst(list).map(overrideWalletDisplay);
1282
1311
  if (isMountedRef.current) setWalletList(sorted);
1283
1312
  } catch (err) {
1284
1313
  const isAbort = err instanceof DOMException && err.name === "AbortError";
@@ -1349,10 +1378,6 @@ var tryOpenDeepLink = /* @__PURE__ */ __name((uri, deepLinkBase) => {
1349
1378
  var tryOpenCreditWalletDeeplink = /* @__PURE__ */ __name((payloadUrl) => {
1350
1379
  window.location.replace(`creditwallet://?uri=${encodeURIComponent(payloadUrl)}`);
1351
1380
  }, "tryOpenCreditWalletDeeplink");
1352
- var detectMetaMaskExtension = /* @__PURE__ */ __name(() => {
1353
- var _a;
1354
- return typeof window !== "undefined" && Boolean((_a = window.ethereum) == null ? void 0 : _a.isMetaMask);
1355
- }, "detectMetaMaskExtension");
1356
1381
 
1357
1382
  // src/layout/connectDialog/index.tsx
1358
1383
  var getContentForConnector = /* @__PURE__ */ __name((id) => {
@@ -1361,7 +1386,7 @@ var getContentForConnector = /* @__PURE__ */ __name((id) => {
1361
1386
  if (id === "WALLET_CONNECT") return "walletconnect";
1362
1387
  return "idle";
1363
1388
  }, "getContentForConnector");
1364
- var getInitialContentForConnector = /* @__PURE__ */ __name((id) => id === "METAMASK" && !detectMetaMaskExtension() ? "metamask-qr" : getContentForConnector(id), "getInitialContentForConnector");
1389
+ var getInitialContentForConnector = /* @__PURE__ */ __name((id, isMetaMaskExtensionInstalled) => id === "METAMASK" && !isMetaMaskExtensionInstalled ? "metamask-qr" : getContentForConnector(id), "getInitialContentForConnector");
1365
1390
  var ConnectDialog = /* @__PURE__ */ __name(({
1366
1391
  open,
1367
1392
  theme = "dark",
@@ -1381,14 +1406,15 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1381
1406
  onError
1382
1407
  }) => {
1383
1408
  const dialogRef = useRef4(null);
1384
- const [selectedConnector, setSelectedConnector] = useState4(selectedConnectorProp);
1385
- const [recentConnector, setRecentConnector] = useState4(null);
1386
- const [selectedWallet, setSelectedWallet] = useState4(null);
1387
- const [step, setStep] = useState4(stepProp);
1388
- const [content, setContent] = useState4("idle");
1389
- const [showCopySnackbar, setShowCopySnackbar] = useState4(false);
1390
- const [qrUri, setQrUri] = useState4(null);
1391
- const [expiredSignal, setExpiredSignal] = useState4(0);
1409
+ const [selectedConnector, setSelectedConnector] = useState5(selectedConnectorProp);
1410
+ const [recentConnector, setRecentConnector] = useState5(null);
1411
+ const [selectedWallet, setSelectedWallet] = useState5(null);
1412
+ const [step, setStep] = useState5(stepProp);
1413
+ const [content, setContent] = useState5("idle");
1414
+ const [showCopySnackbar, setShowCopySnackbar] = useState5(false);
1415
+ const [qrUri, setQrUri] = useState5(null);
1416
+ const [expiredSignal, setExpiredSignal] = useState5(0);
1417
+ const isMetaMaskExtensionInstalled = useMetaMaskExtension();
1392
1418
  const contentRef = useRef4(content);
1393
1419
  contentRef.current = content;
1394
1420
  const preTimeoutContentRef = useRef4("idle");
@@ -1418,12 +1444,12 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1418
1444
  cancelConnect();
1419
1445
  setQrUri(null);
1420
1446
  const restore = preTimeoutContentRef.current;
1421
- const nextContent = restore !== "idle" && restore !== "timeout" ? restore : getInitialContentForConnector(selectedConnector);
1447
+ const nextContent = restore !== "idle" && restore !== "timeout" ? restore : getInitialContentForConnector(selectedConnector, isMetaMaskExtensionInstalled);
1422
1448
  setContent(nextContent);
1423
1449
  triggerConnect(selectedConnector);
1424
1450
  }, "onRetry")
1425
1451
  });
1426
- useEffect4(() => {
1452
+ useEffect5(() => {
1427
1453
  if (!timedOut) return;
1428
1454
  preTimeoutContentRef.current = contentRef.current;
1429
1455
  cancelConnect();
@@ -1432,7 +1458,16 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1432
1458
  timedOut,
1433
1459
  cancelConnect
1434
1460
  ]);
1435
- useEffect4(() => {
1461
+ useEffect5(() => {
1462
+ if (isMetaMaskExtensionInstalled && selectedConnector === "METAMASK" && content === "metamask-qr") {
1463
+ setContent("metamask-ext");
1464
+ }
1465
+ }, [
1466
+ isMetaMaskExtensionInstalled,
1467
+ selectedConnector,
1468
+ content
1469
+ ]);
1470
+ useEffect5(() => {
1436
1471
  const restoreRecent = /* @__PURE__ */ __name(async () => {
1437
1472
  var _a;
1438
1473
  const wagmiId = await ((_a = config.storage) == null ? void 0 : _a.getItem("recentConnectorId"));
@@ -1446,7 +1481,7 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1446
1481
  const wc = useWCState();
1447
1482
  const handleSelect = /* @__PURE__ */ __name((id) => {
1448
1483
  setSelectedConnector(id);
1449
- setContent(getInitialContentForConnector(id));
1484
+ setContent(getInitialContentForConnector(id, isMetaMaskExtensionInstalled));
1450
1485
  setStep("detail");
1451
1486
  triggerConnect(id);
1452
1487
  onSelectConnector == null ? void 0 : onSelectConnector(id);
@@ -1478,7 +1513,7 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1478
1513
  wc.loadWalletList(wcProjectId).catch(() => void 0);
1479
1514
  }
1480
1515
  }, "handleShowAllWallets");
1481
- useEffect4(() => {
1516
+ useEffect5(() => {
1482
1517
  const dialog = dialogRef.current;
1483
1518
  if (!dialog) return;
1484
1519
  if (open && !dialog.open) dialog.showModal();
@@ -1487,12 +1522,12 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1487
1522
  open
1488
1523
  ]);
1489
1524
  const deeplinkTriedRef = useRef4(false);
1490
- useEffect4(() => {
1525
+ useEffect5(() => {
1491
1526
  deeplinkTriedRef.current = false;
1492
1527
  }, [
1493
1528
  selectedWallet
1494
1529
  ]);
1495
- useEffect4(() => {
1530
+ useEffect5(() => {
1496
1531
  if (deeplinkTriedRef.current) return;
1497
1532
  if (!selectedWallet || !qrUri || content !== "wallet-qr") return;
1498
1533
  if (!isMobileDevice()) return;
@@ -1506,12 +1541,12 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1506
1541
  content
1507
1542
  ]);
1508
1543
  const creditConnectDeeplinkTriedRef = useRef4(false);
1509
- useEffect4(() => {
1544
+ useEffect5(() => {
1510
1545
  creditConnectDeeplinkTriedRef.current = false;
1511
1546
  }, [
1512
1547
  selectedConnector
1513
1548
  ]);
1514
- useEffect4(() => {
1549
+ useEffect5(() => {
1515
1550
  if (creditConnectDeeplinkTriedRef.current) return;
1516
1551
  if (content !== "qr" || !qrUri) return;
1517
1552
  if (selectedConnector !== "CREDIT_CONNECT") return;
@@ -1523,7 +1558,7 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1523
1558
  qrUri,
1524
1559
  selectedConnector
1525
1560
  ]);
1526
- useEffect4(() => {
1561
+ useEffect5(() => {
1527
1562
  const prev = document.body.style.overflow;
1528
1563
  if (open) document.body.style.overflow = "hidden";
1529
1564
  return () => {
@@ -1532,7 +1567,7 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1532
1567
  }, [
1533
1568
  open
1534
1569
  ]);
1535
- useEffect4(() => {
1570
+ useEffect5(() => {
1536
1571
  if (!open) {
1537
1572
  cancelConnect();
1538
1573
  setQrUri(null);
@@ -1545,7 +1580,7 @@ var ConnectDialog = /* @__PURE__ */ __name(({
1545
1580
  open,
1546
1581
  cancelConnect
1547
1582
  ]);
1548
- useEffect4(() => {
1583
+ useEffect5(() => {
1549
1584
  const dialog = dialogRef.current;
1550
1585
  if (!dialog) return void 0;
1551
1586
  const handleCancel = /* @__PURE__ */ __name((e) => {
@@ -1633,27 +1668,27 @@ var ConnectKitProvider = /* @__PURE__ */ __name((props) => {
1633
1668
  }, "ConnectKitProvider");
1634
1669
  var ConnectKitProviderInner = /* @__PURE__ */ __name(({ children, connectors, kitConfig, requiredChainId, theme, onConnect, onLog }) => {
1635
1670
  var _a;
1636
- const [isOpen, setIsOpen] = useState5(false);
1637
- const [pendingResult, setPendingResult] = useState5(null);
1671
+ const [isOpen, setIsOpen] = useState6(false);
1672
+ const [pendingResult, setPendingResult] = useState6(null);
1638
1673
  const account = useAccount();
1639
1674
  const wagmiConfig = useConfig3();
1640
1675
  const { disconnectAsync } = useDisconnect();
1641
1676
  const { switchChainAsync } = useSwitchChain();
1642
1677
  const onConnectRef = useRef5(onConnect);
1643
- useEffect5(() => {
1678
+ useEffect6(() => {
1644
1679
  onConnectRef.current = onConnect;
1645
1680
  }, [
1646
1681
  onConnect
1647
1682
  ]);
1648
1683
  const isMountedRef = useRef5(true);
1649
- useEffect5(() => {
1684
+ useEffect6(() => {
1650
1685
  isMountedRef.current = true;
1651
1686
  return () => {
1652
1687
  isMountedRef.current = false;
1653
1688
  };
1654
1689
  }, []);
1655
1690
  const needsChainSwitch = requiredChainId != null && account.status === "connected" && account.chainId != null && account.chainId !== requiredChainId;
1656
- const [isSwitchingChain, setIsSwitchingChain] = useState5(false);
1691
+ const [isSwitchingChain, setIsSwitchingChain] = useState6(false);
1657
1692
  const effectiveOpen = isOpen;
1658
1693
  const open = useCallback4(() => {
1659
1694
  setIsOpen(true);
@@ -1695,7 +1730,7 @@ var ConnectKitProviderInner = /* @__PURE__ */ __name(({ children, connectors, ki
1695
1730
  }, [
1696
1731
  onLog
1697
1732
  ]);
1698
- useEffect5(() => {
1733
+ useEffect6(() => {
1699
1734
  if (!effectiveOpen) return;
1700
1735
  if (account.status !== "connected") return;
1701
1736
  setIsOpen(false);
@@ -1703,7 +1738,7 @@ var ConnectKitProviderInner = /* @__PURE__ */ __name(({ children, connectors, ki
1703
1738
  effectiveOpen,
1704
1739
  account.status
1705
1740
  ]);
1706
- useEffect5(() => {
1741
+ useEffect6(() => {
1707
1742
  var _a2;
1708
1743
  if (!pendingResult) return;
1709
1744
  if (account.status !== "connected") {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gluwa/connect-kit",
3
- "version": "0.2.0-next.8",
3
+ "version": "0.2.0-next.9",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gluwa/connect-kit",
3
- "version": "0.2.0-next.8",
3
+ "version": "0.2.0-next.9",
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-sdk": "0.2.0-next.4",
30
- "@gluwa/credit-connect-storage-local": "0.2.0-next.4",
31
29
  "@gluwa/credit-connect-e2ee-tweetnacl": "0.2.0-next.4",
32
- "@gluwa/credit-connect-hub-node-ws": "0.1.0-next.4"
30
+ "@gluwa/credit-connect-hub-node-ws": "0.1.0-next.4",
31
+ "@gluwa/credit-connect-sdk": "0.2.0-next.4",
32
+ "@gluwa/credit-connect-storage-local": "0.2.0-next.4"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@tanstack/react-query": "^5.62.0",
@@ -27,7 +27,9 @@ export const QrArea: FC<QrAreaProps> = ({ uri, status, logoUrl, hasBg = false, l
27
27
  <div className="ck-qr-group">
28
28
  <figure className={`ck-qr ck-qr-${status}`} aria-label="QR Code">
29
29
  {status === 'loading' && (
30
- <div className="ck-qr-skeleton" aria-busy="true" aria-label="Loading QR code" />
30
+ <div className="ck-qr-skeleton" aria-busy="true" aria-label="Loading QR code">
31
+ <div className="ck-qr-skeleton-inner" />
32
+ </div>
31
33
  )}
32
34
  {status === 'ready' && uri && (
33
35
  <div
@@ -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;
@@ -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) {
@@ -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,15 +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';
16
17
  import { useConnectTimeout } from '../../hooks/useConnectTimeout';
17
18
  import { useWCState } from '../../hooks/useWCState';
18
- import {
19
- detectMetaMaskExtension,
20
- isMobileDevice,
21
- tryOpenDeepLink,
22
- tryOpenCreditWalletDeeplink,
23
- } from '../../utils/platform';
19
+ import { isMobileDevice, tryOpenDeepLink, tryOpenCreditWalletDeeplink } from '../../utils/platform';
24
20
 
25
21
  export type DialogStep = 'selector' | 'detail';
26
22
 
@@ -49,8 +45,11 @@ const getContentForConnector = (id: ConnectorId): DetailContent => {
49
45
  };
50
46
 
51
47
  // MM 은 확장 감지 시 즉시 extension 연결, 아니면 QR. (select / retry 공통)
52
- const getInitialContentForConnector = (id: ConnectorId): DetailContent =>
53
- id === 'METAMASK' && !detectMetaMaskExtension() ? 'metamask-qr' : getContentForConnector(id);
48
+ const getInitialContentForConnector = (
49
+ id: ConnectorId,
50
+ isMetaMaskExtensionInstalled: boolean,
51
+ ): DetailContent =>
52
+ id === 'METAMASK' && !isMetaMaskExtensionInstalled ? 'metamask-qr' : getContentForConnector(id);
54
53
 
55
54
  export const ConnectDialog: FC<ConnectDialogProps> = ({
56
55
  open,
@@ -90,6 +89,8 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
90
89
  const [qrUri, setQrUri] = useState<string | null>(null);
91
90
  // 외부 만료 신호 카운터 — CC(서버 push session_expired) / WC(connect reject) 수신 시 증가.
92
91
  const [expiredSignal, setExpiredSignal] = useState(0);
92
+ // EIP-6963 기반 MetaMask 확장 설치 상태.
93
+ const isMetaMaskExtensionInstalled = useMetaMaskExtension();
93
94
 
94
95
  // 현재 content 를 ref 로 추적 (effect deps 없이 최신값 참조용).
95
96
  const contentRef = useRef<DetailContent>(content);
@@ -137,7 +138,7 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
137
138
  const nextContent: DetailContent =
138
139
  restore !== 'idle' && restore !== 'timeout'
139
140
  ? restore
140
- : getInitialContentForConnector(selectedConnector);
141
+ : getInitialContentForConnector(selectedConnector, isMetaMaskExtensionInstalled);
141
142
  setContent(nextContent);
142
143
  triggerConnect(selectedConnector);
143
144
  },
@@ -152,6 +153,17 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
152
153
  setContent('timeout');
153
154
  }, [timedOut, cancelConnect]);
154
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
+
155
167
  // 마운트 시 wagmi storage 의 recentConnectorId(연결 성공 시 wagmi 가 저장) 복원.
156
168
  useEffect(() => {
157
169
  const restoreRecent = async (): Promise<void> => {
@@ -168,7 +180,7 @@ export const ConnectDialog: FC<ConnectDialogProps> = ({
168
180
  const handleSelect = (id: ConnectorId) => {
169
181
  setSelectedConnector(id);
170
182
  // recent 는 클릭이 아니라 연결 성공(onConnect) 시점에 기록 — 위 useWagmiConnect onConnect 참조.
171
- setContent(getInitialContentForConnector(id));
183
+ setContent(getInitialContentForConnector(id, isMetaMaskExtensionInstalled));
172
184
  setStep('detail');
173
185
  // wagmi connect 시작 (display_uri 도착 시 setQrUri 자동 호출)
174
186
  triggerConnect(id);
@@ -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; }
@@ -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;