@gluwa/connect-kit 0.1.0-next.3 → 0.2.0-next.3
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 +17 -0
- package/assets/creditwallet.png +0 -0
- package/assets/fonts/Inter-Medium.ttf +0 -0
- package/assets/fonts/Inter-Medium.woff2 +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/fonts/Inter-Regular.woff2 +0 -0
- package/assets/fonts/Inter-SemiBold.ttf +0 -0
- package/assets/fonts/Inter-SemiBold.woff2 +0 -0
- package/assets/graphic.png +0 -0
- package/assets/metamask.png +0 -0
- package/assets/wc-logo.svg +3 -0
- package/assets/wc-search.png +0 -0
- package/dist/chunk-EDNRW47A.js +401 -0
- package/dist/credit-connect.js +2 -394
- package/dist/index.css +2074 -0
- package/dist/index.d.ts +53 -33
- package/dist/index.js +1404 -1504
- package/dist/package.json +16 -10
- package/package.json +14 -11
- package/src/ConnectKitProvider.tsx +126 -75
- 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/assets.d.ts +5 -0
- package/src/components/actionItem/index.tsx +50 -0
- package/src/components/actionItem/style.scss +47 -0
- package/src/components/copyLink/index.tsx +16 -0
- package/src/components/copyLink/style.scss +16 -0
- package/src/components/qrArea/index.tsx +70 -0
- package/src/components/qrArea/style.scss +130 -0
- package/src/components/searchBar/asset/Search.tsx +20 -0
- package/src/components/searchBar/asset/XMarkCircle.tsx +24 -0
- package/src/components/searchBar/index.tsx +44 -0
- package/src/components/searchBar/style.scss +58 -0
- package/src/components/snackbar/index.tsx +25 -0
- package/src/components/snackbar/style.scss +51 -0
- package/src/components/toggle/index.tsx +27 -0
- package/src/components/toggle/style.scss +53 -0
- package/src/components/walletGrid/index.tsx +64 -0
- package/src/components/walletGrid/style.scss +108 -0
- package/src/components/walletItem/index.tsx +49 -0
- package/src/components/walletItem/style.scss +70 -0
- package/src/connector-meta.ts +12 -7
- package/src/core/config.ts +102 -0
- package/src/creditConnectConnector.ts +192 -184
- package/src/events/account.ts +49 -0
- package/src/hooks/useWCState.ts +45 -13
- package/src/hooks/useWagmiConnect.ts +13 -9
- package/src/index.ts +35 -2
- package/src/layout/allWallets/index.tsx +52 -0
- package/src/layout/allWallets/style.scss +62 -0
- package/src/layout/connectDialog/asset/backIcon.tsx +24 -0
- package/src/layout/connectDialog/asset/checkIcon.tsx +28 -0
- package/src/layout/connectDialog/asset/closeIcon.tsx +21 -0
- package/src/layout/connectDialog/asset/copyIcon.tsx +28 -0
- package/src/layout/connectDialog/asset/linkIcon.tsx +24 -0
- package/src/layout/connectDialog/asset/spinner.tsx +21 -0
- package/src/layout/connectDialog/idle/index.tsx +32 -0
- package/src/layout/connectDialog/idle/style.scss +61 -0
- package/src/layout/connectDialog/index.tsx +286 -0
- package/src/layout/connectDialog/loading/index.tsx +13 -0
- package/src/layout/connectDialog/loading/style.scss +19 -0
- package/src/layout/connectDialog/metaMask/index.tsx +61 -0
- package/src/layout/connectDialog/metaMask/style.scss +65 -0
- package/src/layout/connectDialog/qr/index.tsx +33 -0
- package/src/layout/connectDialog/style.scss +95 -0
- package/src/layout/connectDialog/timeout/index.tsx +31 -0
- package/src/layout/connectDialog/timeout/style.scss +64 -0
- package/src/layout/connectDialog/walletConnect/index.tsx +45 -0
- package/src/layout/connectDialog/walletConnect/style.scss +48 -0
- package/src/layout/connectDialog/walletQR/index.tsx +37 -0
- package/src/layout/detailPanel/index.tsx +147 -0
- package/src/layout/detailPanel/style.scss +64 -0
- package/src/layout/selectorPanel/index.tsx +100 -0
- package/src/layout/selectorPanel/style.scss +113 -0
- package/src/style/color.scss +417 -0
- package/src/style/font.scss +28 -0
- package/src/style/index.scss +5 -0
- package/src/style/mixin.scss +217 -0
- package/src/style/reset.scss +110 -0
- package/src/style/variables.scss +13 -0
- package/src/types.ts +19 -7
- package/src/utils/platform.ts +7 -4
- package/tsup.config.ts +32 -2
- package/dist/chunk-DDA6FP6U.js +0 -7
- package/src/ConnectModal.scss +0 -665
- package/src/ConnectModal.tsx +0 -559
- package/src/components/QRFrame.tsx +0 -43
- package/src/views/CreditWalletView.tsx +0 -59
- package/src/views/IdleView.tsx +0 -10
- package/src/views/MetaMaskView.tsx +0 -64
- package/src/views/SwitchChainView.tsx +0 -67
- package/src/views/WalletConnectView.tsx +0 -206
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import './style.scss';
|
|
3
|
+
import { type ConnectorId } from '../../types';
|
|
4
|
+
|
|
5
|
+
interface WalletItemProps {
|
|
6
|
+
id: ConnectorId;
|
|
7
|
+
label: string;
|
|
8
|
+
logoUrl?: string;
|
|
9
|
+
isSelected: boolean;
|
|
10
|
+
isRecent?: boolean;
|
|
11
|
+
onSelect: (id: ConnectorId) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const WalletItem: FC<WalletItemProps> = ({
|
|
15
|
+
id,
|
|
16
|
+
label,
|
|
17
|
+
logoUrl,
|
|
18
|
+
isSelected,
|
|
19
|
+
isRecent = false,
|
|
20
|
+
onSelect,
|
|
21
|
+
}) => {
|
|
22
|
+
return (
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
className={`ck-wallet-item${isSelected ? ' is-selected' : ''}`}
|
|
26
|
+
onClick={() => onSelect(id)}
|
|
27
|
+
aria-pressed={isSelected}
|
|
28
|
+
>
|
|
29
|
+
{logoUrl ? (
|
|
30
|
+
<img
|
|
31
|
+
className={`ck-wallet-item-icon${id === 'METAMASK' ? ' has-bg' : ''}`}
|
|
32
|
+
src={logoUrl}
|
|
33
|
+
alt=""
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
/>
|
|
36
|
+
) : (
|
|
37
|
+
<span className="ck-wallet-item-icon" aria-hidden="true" />
|
|
38
|
+
)}
|
|
39
|
+
<span className="ck-wallet-item-txt">
|
|
40
|
+
<span className="ck-wallet-item-label">{label}</span>
|
|
41
|
+
{isRecent && (
|
|
42
|
+
<span className="ck-wallet-item-badge" aria-label="Recently used">
|
|
43
|
+
Recent
|
|
44
|
+
</span>
|
|
45
|
+
)}
|
|
46
|
+
</span>
|
|
47
|
+
</button>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
@use '../../style/mixin.scss' as *;
|
|
2
|
+
|
|
3
|
+
.ck-root .ck-wallet-item {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 12px;
|
|
7
|
+
width: 100%;
|
|
8
|
+
padding: 14px 20px;
|
|
9
|
+
border-radius: 12px;
|
|
10
|
+
background: var(--scale-gray-100);
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
transition: background 0.15s ease;
|
|
13
|
+
|
|
14
|
+
&:hover {
|
|
15
|
+
background: var(--scale-gray-200);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&.is-selected {
|
|
19
|
+
background: var(--semantic-brand-trugi-primary);
|
|
20
|
+
|
|
21
|
+
.ck-wallet-item-label {
|
|
22
|
+
color: var(--static-white-main);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.ck-wallet-item-badge {
|
|
26
|
+
background: var(--static-white-alpha-200);
|
|
27
|
+
color: var(--static-white-main);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ck-wallet-item-icon {
|
|
33
|
+
width: 36px;
|
|
34
|
+
height: 36px;
|
|
35
|
+
border-radius: 8px;
|
|
36
|
+
flex-shrink: 0;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
object-fit: cover;
|
|
39
|
+
|
|
40
|
+
&.has-bg {
|
|
41
|
+
background: var(--static-white-main);
|
|
42
|
+
padding: 2px;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.ck-wallet-item-txt {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 6px;
|
|
50
|
+
flex: 1;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.ck-wallet-item-label {
|
|
55
|
+
@include textToken('label3-bold');
|
|
56
|
+
color: var(--scale-gray-900);
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
text-overflow: ellipsis;
|
|
59
|
+
white-space: nowrap;
|
|
60
|
+
flex-shrink: 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ck-wallet-item-badge {
|
|
64
|
+
@include textToken('caption3-bold');
|
|
65
|
+
color: var(--semantic-brand-trugi-primary);
|
|
66
|
+
background: var(--scale-blue2-alpha-100);
|
|
67
|
+
padding: 4px 8px;
|
|
68
|
+
border-radius: 500px;
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
}
|
package/src/connector-meta.ts
CHANGED
|
@@ -17,13 +17,6 @@ export const CONNECTOR_META: Record<ConnectorId, ConnectorMeta> = {
|
|
|
17
17
|
detailTitle: 'Scan with Credit Wallet',
|
|
18
18
|
downloadUrl: 'https://creditwallet.onelink.me/Jv54',
|
|
19
19
|
logoUrl: creditWalletIcon,
|
|
20
|
-
// deepLinkBase: TODO — 앱 측 CC 딥링크 스킴 확정 후 추가
|
|
21
|
-
},
|
|
22
|
-
CREDIT_WALLET: {
|
|
23
|
-
label: 'Credit Wallet',
|
|
24
|
-
detailTitle: 'Scan with Credit Wallet',
|
|
25
|
-
downloadUrl: 'https://creditwallet.onelink.me/Jv54',
|
|
26
|
-
logoUrl: creditWalletIcon,
|
|
27
20
|
deepLinkBase: 'creditwallet://',
|
|
28
21
|
},
|
|
29
22
|
METAMASK: {
|
|
@@ -39,3 +32,15 @@ export const CONNECTOR_META: Record<ConnectorId, ConnectorMeta> = {
|
|
|
39
32
|
logoUrl: wcIcon,
|
|
40
33
|
},
|
|
41
34
|
};
|
|
35
|
+
|
|
36
|
+
// wagmi connector id (예: 'credit-connect' · 'metaMaskSDK' · 'walletConnect') → kit ConnectorId.
|
|
37
|
+
// wagmi storage 의 recentConnectorId 복원 · account.connector 식별에 공용 사용.
|
|
38
|
+
export const resolveConnectorId = (wagmiConnectorId: string | undefined): ConnectorId | null => {
|
|
39
|
+
if (!wagmiConnectorId) return null;
|
|
40
|
+
if (wagmiConnectorId === 'credit-connect') return 'CREDIT_CONNECT';
|
|
41
|
+
if (wagmiConnectorId === 'walletConnect') return 'WALLET_CONNECT';
|
|
42
|
+
const lower = wagmiConnectorId.toLowerCase();
|
|
43
|
+
if (lower.includes('metamask') || wagmiConnectorId === 'injected') return 'METAMASK';
|
|
44
|
+
if (lower.includes('walletconnect')) return 'WALLET_CONNECT';
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createConfig, createStorage } from 'wagmi';
|
|
2
|
+
import { metaMask, walletConnect, injected } from 'wagmi/connectors';
|
|
3
|
+
import type { Config } from 'wagmi';
|
|
4
|
+
import type { ConnectKitConfig } from '../types';
|
|
5
|
+
import { creditConnectConnector } from '../creditConnectConnector';
|
|
6
|
+
import { LocalStorageProvider } from '@gluwa/credit-connect-storage-local';
|
|
7
|
+
import { tweetNaclE2EE } from '@gluwa/credit-connect-e2ee-tweetnacl';
|
|
8
|
+
import { wsHubFactory } from '@gluwa/credit-connect-hub-node-ws';
|
|
9
|
+
|
|
10
|
+
type KitSlot = {
|
|
11
|
+
config: Config | null;
|
|
12
|
+
kitConfig: ConnectKitConfig | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const SLOT_KEY = Symbol.for('@gluwa/connect-kit:slot');
|
|
16
|
+
|
|
17
|
+
type GlobalWithSlot = typeof globalThis & {
|
|
18
|
+
[SLOT_KEY]?: KitSlot;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getSlot = (): KitSlot => {
|
|
22
|
+
const g = globalThis as GlobalWithSlot;
|
|
23
|
+
if (!g[SLOT_KEY]) {
|
|
24
|
+
g[SLOT_KEY] = { config: null, kitConfig: null };
|
|
25
|
+
}
|
|
26
|
+
return g[SLOT_KEY];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const initConfig = (kitConfig: ConnectKitConfig): Config => {
|
|
30
|
+
const slot = getSlot();
|
|
31
|
+
if (slot.config) {
|
|
32
|
+
if (slot.kitConfig !== kitConfig) {
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.warn(
|
|
35
|
+
'[connect-kit] initConfig called again with a different kitConfig — new chains/transports ignored. Call resetConfig() first to re-init.',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return slot.config;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const connectors = [];
|
|
42
|
+
|
|
43
|
+
if (kitConfig.wcProjectId) {
|
|
44
|
+
connectors.push(walletConnect({ projectId: kitConfig.wcProjectId, showQrModal: false }));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
connectors.push(
|
|
48
|
+
metaMask({ headless: true, storage: { enabled: true } }),
|
|
49
|
+
injected({ shimDisconnect: false }),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (kitConfig.creditConnect) {
|
|
53
|
+
connectors.push(
|
|
54
|
+
creditConnectConnector({
|
|
55
|
+
projectKey: kitConfig.creditConnect.projectId,
|
|
56
|
+
serverUrl: kitConfig.creditConnect.serverUrl,
|
|
57
|
+
storage: new LocalStorageProvider(),
|
|
58
|
+
e2ee: tweetNaclE2EE,
|
|
59
|
+
createHub: wsHubFactory.createHubFromDapp,
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (kitConfig.extraConnectors?.length) {
|
|
65
|
+
connectors.push(...kitConfig.extraConnectors);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const isBrowser = typeof window !== 'undefined';
|
|
69
|
+
|
|
70
|
+
slot.config = createConfig({
|
|
71
|
+
ssr: true,
|
|
72
|
+
chains: kitConfig.chains,
|
|
73
|
+
connectors,
|
|
74
|
+
transports: kitConfig.transports,
|
|
75
|
+
storage: isBrowser ? createStorage({ storage: window.localStorage }) : undefined,
|
|
76
|
+
});
|
|
77
|
+
slot.kitConfig = kitConfig;
|
|
78
|
+
|
|
79
|
+
return slot.config;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const getConfig = (): Config => {
|
|
83
|
+
const slot = getSlot();
|
|
84
|
+
if (!slot.config) {
|
|
85
|
+
throw new Error('ConnectKit config not initialized. Make sure ConnectKitProvider is mounted.');
|
|
86
|
+
}
|
|
87
|
+
return slot.config;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const getKitConfig = (): ConnectKitConfig => {
|
|
91
|
+
const slot = getSlot();
|
|
92
|
+
if (!slot.kitConfig) {
|
|
93
|
+
throw new Error('ConnectKit config not initialized. Make sure ConnectKitProvider is mounted.');
|
|
94
|
+
}
|
|
95
|
+
return slot.kitConfig;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const resetConfig = (): void => {
|
|
99
|
+
const slot = getSlot();
|
|
100
|
+
slot.config = null;
|
|
101
|
+
slot.kitConfig = null;
|
|
102
|
+
};
|
|
@@ -41,6 +41,7 @@ export const creditConnectConnector = (
|
|
|
41
41
|
let selectedChainId: number | null = null;
|
|
42
42
|
let connectPromise: Promise<boolean> | null = null;
|
|
43
43
|
let provider: CreditConnectProvider | null = null;
|
|
44
|
+
let providerPromise: Promise<CreditConnectProvider> | null = null;
|
|
44
45
|
let isLocalDisconnect = false;
|
|
45
46
|
|
|
46
47
|
let emitWagmiMessage: ((type: string, data: unknown) => void) | null = null;
|
|
@@ -83,12 +84,23 @@ export const creditConnectConnector = (
|
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
manager.on('changeState', (state) => {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.log('[CC connector] manager state →', state.status, {
|
|
89
|
+
hasConnectingPayload: Boolean(state.connectingPayload),
|
|
90
|
+
hasSession: 'session' in state && Boolean((state as { session?: unknown }).session),
|
|
91
|
+
});
|
|
86
92
|
if (state.status === 'CONNECTING' && state.connectingPayload) {
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.log('[CC connector] emit display_uri', {
|
|
95
|
+
payloadPrefix: state.connectingPayload.slice(0, 80),
|
|
96
|
+
});
|
|
87
97
|
options.onDisplayUri?.(state.connectingPayload);
|
|
88
98
|
emitWagmiMessage?.('display_uri', state.connectingPayload);
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
if (state.status === 'DISCONNECTED' && !isLocalDisconnect) {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.log('[CC connector] DISCONNECTED (non-local) → resetState + emit');
|
|
92
104
|
resetState();
|
|
93
105
|
emitProviderEvent('disconnect');
|
|
94
106
|
emitWagmiDisconnect?.();
|
|
@@ -128,6 +140,163 @@ export const creditConnectConnector = (
|
|
|
128
140
|
config.emitter.emit('disconnect');
|
|
129
141
|
};
|
|
130
142
|
|
|
143
|
+
const buildProvider = (): CreditConnectProviderWithEvents => {
|
|
144
|
+
const createdProvider = {
|
|
145
|
+
request: async (args) => {
|
|
146
|
+
const { method, params: rawParams } = args as {
|
|
147
|
+
method: string;
|
|
148
|
+
params?: unknown[] | object;
|
|
149
|
+
};
|
|
150
|
+
const params = Array.isArray(rawParams)
|
|
151
|
+
? [...rawParams]
|
|
152
|
+
: rawParams === undefined
|
|
153
|
+
? []
|
|
154
|
+
: [rawParams];
|
|
155
|
+
const session = getCurrentSession();
|
|
156
|
+
|
|
157
|
+
const getSelectedAddressInfo = (address?: string) => {
|
|
158
|
+
if (!session) throw new Error('No active session');
|
|
159
|
+
const evmInfo = getEvmAddressInfo(session);
|
|
160
|
+
if (evmInfo.length === 0) throw new Error('No EVM address found');
|
|
161
|
+
if (!address) return evmInfo[0];
|
|
162
|
+
const found = evmInfo.find(
|
|
163
|
+
(info) => info.address.toLowerCase() === address.toLowerCase(),
|
|
164
|
+
);
|
|
165
|
+
if (!found) throw new Error(`Address not in session: ${address}`);
|
|
166
|
+
return found;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (method === 'eth_accounts') {
|
|
170
|
+
if (!session) return [];
|
|
171
|
+
return getChecksummedAccounts(session);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (method === 'eth_chainId') {
|
|
175
|
+
if (!session) throw new Error('No active session');
|
|
176
|
+
if (selectedChainId !== null) return `0x${selectedChainId.toString(16)}`;
|
|
177
|
+
const evmInfo = getEvmAddressInfo(session);
|
|
178
|
+
if (evmInfo.length === 0) throw new Error('No EVM address found');
|
|
179
|
+
const chainId = Number.parseInt(evmInfo[0].networkIdentifier, 10);
|
|
180
|
+
if (!Number.isFinite(chainId)) throw new Error(`Invalid chainId: ${chainId}`);
|
|
181
|
+
selectedChainId = chainId;
|
|
182
|
+
return `0x${chainId.toString(16)}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (method === 'wallet_switchEthereumChain') {
|
|
186
|
+
const [{ chainId: rawChainId }] = (params ?? []) as [{ chainId?: string }];
|
|
187
|
+
if (!rawChainId) throw new Error('wallet_switchEthereumChain missing chainId param');
|
|
188
|
+
const chainId = Number.parseInt(rawChainId, 16);
|
|
189
|
+
if (!Number.isFinite(chainId)) throw new Error(`Invalid chainId: ${rawChainId}`);
|
|
190
|
+
if (!session) throw new Error('No active session');
|
|
191
|
+
|
|
192
|
+
const supported = getEvmAddressInfo(session).some(
|
|
193
|
+
(info) => Number.parseInt(info.networkIdentifier, 10) === chainId,
|
|
194
|
+
);
|
|
195
|
+
if (!supported) {
|
|
196
|
+
const err = new Error(`Unsupported chain: ${chainId}`) as Error & { code?: number };
|
|
197
|
+
err.code = 4902;
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
selectedChainId = chainId;
|
|
201
|
+
config.emitter.emit('change', { chainId });
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (method === 'personal_sign') {
|
|
206
|
+
const [messageParam, addressParam] = (params ?? []) as [unknown, unknown];
|
|
207
|
+
const message =
|
|
208
|
+
typeof messageParam === 'string' ? messageParam : String(messageParam ?? '');
|
|
209
|
+
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
210
|
+
const addressInfo = getSelectedAddressInfo(address);
|
|
211
|
+
const response = await session?.jsonRpc.request('signMessage', {
|
|
212
|
+
message,
|
|
213
|
+
addressInfo,
|
|
214
|
+
});
|
|
215
|
+
return response?.signature;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (method === 'eth_sign') {
|
|
219
|
+
const [addressParam, messageParam] = (params ?? []) as [unknown, unknown];
|
|
220
|
+
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
221
|
+
const message =
|
|
222
|
+
typeof messageParam === 'string' ? messageParam : String(messageParam ?? '');
|
|
223
|
+
const addressInfo = getSelectedAddressInfo(address);
|
|
224
|
+
const response = await session?.jsonRpc.request('signMessage', {
|
|
225
|
+
message,
|
|
226
|
+
addressInfo,
|
|
227
|
+
});
|
|
228
|
+
return response?.signature;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (method === 'eth_signTypedData' || method === 'eth_signTypedData_v4') {
|
|
232
|
+
const [addressParam, typedDataParam] = (params ?? []) as [unknown, unknown];
|
|
233
|
+
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
234
|
+
const typedData =
|
|
235
|
+
typeof typedDataParam === 'string'
|
|
236
|
+
? typedDataParam
|
|
237
|
+
: JSON.stringify(typedDataParam ?? '');
|
|
238
|
+
const addressInfo = getSelectedAddressInfo(address);
|
|
239
|
+
const response = await session?.jsonRpc.request('signTypedData', {
|
|
240
|
+
typedData,
|
|
241
|
+
addressInfo,
|
|
242
|
+
});
|
|
243
|
+
return response?.signature;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (method === 'eth_sendTransaction') {
|
|
247
|
+
const [txParams] = (params ?? []) as [Record<string, string | undefined>];
|
|
248
|
+
const addressInfo = getSelectedAddressInfo(txParams?.from);
|
|
249
|
+
const response = await session?.jsonRpc.request('sendTransaction', {
|
|
250
|
+
addressInfo,
|
|
251
|
+
to: txParams?.to,
|
|
252
|
+
value: txParams?.value,
|
|
253
|
+
data: txParams?.data,
|
|
254
|
+
gas: txParams?.gas,
|
|
255
|
+
gasPrice: txParams?.gasPrice,
|
|
256
|
+
maxFeePerGas: txParams?.maxFeePerGas,
|
|
257
|
+
maxPriorityFeePerGas: txParams?.maxPriorityFeePerGas,
|
|
258
|
+
nonce: txParams?.nonce,
|
|
259
|
+
chainId: txParams?.chainId,
|
|
260
|
+
});
|
|
261
|
+
return response?.txHash;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (method === 'credit_connect_send_message') {
|
|
265
|
+
if (!session) throw new Error('No active session');
|
|
266
|
+
const [messageParam] = (params ?? []) as [unknown];
|
|
267
|
+
const message =
|
|
268
|
+
typeof messageParam === 'string' ? messageParam : JSON.stringify(messageParam ?? '');
|
|
269
|
+
await session.msg.sendMessage(message);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (method === 'credit_connect_jsonrpc_ping') {
|
|
274
|
+
if (!session) throw new Error('No active session');
|
|
275
|
+
return await session.jsonRpc.request('pingPong', {});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
throw new Error(`Unsupported provider method: ${method}`);
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
on: (event: string, listener: ProviderListener) => {
|
|
282
|
+
let listeners = providerListeners.get(event);
|
|
283
|
+
if (!listeners) {
|
|
284
|
+
listeners = new Set();
|
|
285
|
+
providerListeners.set(event, listeners);
|
|
286
|
+
}
|
|
287
|
+
listeners.add(listener);
|
|
288
|
+
return createdProvider;
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
removeListener: (event: string, listener: ProviderListener) => {
|
|
292
|
+
providerListeners.get(event)?.delete(listener);
|
|
293
|
+
return createdProvider;
|
|
294
|
+
},
|
|
295
|
+
} as CreditConnectProviderWithEvents;
|
|
296
|
+
|
|
297
|
+
return createdProvider;
|
|
298
|
+
};
|
|
299
|
+
|
|
131
300
|
return {
|
|
132
301
|
id: CONNECTOR_ID,
|
|
133
302
|
name: CONNECTOR_NAME,
|
|
@@ -185,32 +354,13 @@ export const creditConnectConnector = (
|
|
|
185
354
|
});
|
|
186
355
|
}
|
|
187
356
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (!connectPromise) {
|
|
192
|
-
connectPromise = new Promise<boolean>((resolve) => {
|
|
193
|
-
const unsub = m.on('changeState', (state) => {
|
|
194
|
-
if (state.status === 'CONNECTED') {
|
|
195
|
-
unsub();
|
|
196
|
-
resolve(true);
|
|
197
|
-
} else if (state.status === 'DISCONNECTED') {
|
|
198
|
-
unsub();
|
|
199
|
-
resolve(false);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
357
|
+
// 새 connect 시도 — stale state (CONNECTING/CONNECTING_WAITING_PAYLOAD) 와
|
|
358
|
+
// stale connectPromise 는 SDK 의 self-heal (cleanupHub + attempt-id) 에 위임.
|
|
359
|
+
// connector 는 단순히 새 connectNewSession() 을 호출 — 이전 attempt 는 자연 정리됨.
|
|
206
360
|
if (m.state.status !== 'CONNECTED') {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
connectPromise = m.connectNewSession();
|
|
212
|
-
}
|
|
213
|
-
|
|
361
|
+
// 이전 attempt 의 await 가 살아있어도, SDK 의 attempt-id 패턴이
|
|
362
|
+
// 그 결과를 무시하도록 보장한다. connectPromise 는 무조건 새 promise 로.
|
|
363
|
+
connectPromise = m.connectNewSession();
|
|
214
364
|
const connected = await connectPromise;
|
|
215
365
|
connectPromise = null;
|
|
216
366
|
|
|
@@ -261,11 +411,13 @@ export const creditConnectConnector = (
|
|
|
261
411
|
throw err;
|
|
262
412
|
}
|
|
263
413
|
|
|
414
|
+
const chain = config.chains.find((c) => c.id === chainId);
|
|
415
|
+
if (!chain) throw new Error(`Chain ${chainId} not in config`);
|
|
416
|
+
|
|
264
417
|
selectedChainId = chainId;
|
|
265
418
|
config.emitter.emit('change', { chainId });
|
|
419
|
+
await Promise.resolve();
|
|
266
420
|
|
|
267
|
-
const chain = config.chains.find((c) => c.id === chainId);
|
|
268
|
-
if (!chain) throw new Error(`Chain ${chainId} not in config`);
|
|
269
421
|
return chain;
|
|
270
422
|
},
|
|
271
423
|
|
|
@@ -286,6 +438,7 @@ export const creditConnectConnector = (
|
|
|
286
438
|
isLocalDisconnect = false;
|
|
287
439
|
}
|
|
288
440
|
provider = null;
|
|
441
|
+
providerPromise = null;
|
|
289
442
|
resetState();
|
|
290
443
|
config.emitter.emit('disconnect');
|
|
291
444
|
},
|
|
@@ -314,165 +467,20 @@ export const creditConnectConnector = (
|
|
|
314
467
|
|
|
315
468
|
async getProvider() {
|
|
316
469
|
if (provider) return provider;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const createdProvider = {
|
|
320
|
-
request: async (args) => {
|
|
321
|
-
const { method, params: rawParams } = args as {
|
|
322
|
-
method: string;
|
|
323
|
-
params?: unknown[] | object;
|
|
324
|
-
};
|
|
325
|
-
const params = Array.isArray(rawParams)
|
|
326
|
-
? [...rawParams]
|
|
327
|
-
: rawParams === undefined
|
|
328
|
-
? []
|
|
329
|
-
: [rawParams];
|
|
330
|
-
const session = getCurrentSession();
|
|
331
|
-
|
|
332
|
-
const getSelectedAddressInfo = (address?: string) => {
|
|
333
|
-
if (!session) throw new Error('No active session');
|
|
334
|
-
const evmInfo = getEvmAddressInfo(session);
|
|
335
|
-
if (evmInfo.length === 0) throw new Error('No EVM address found');
|
|
336
|
-
if (!address) return evmInfo[0];
|
|
337
|
-
const found = evmInfo.find(
|
|
338
|
-
(info) => info.address.toLowerCase() === address.toLowerCase(),
|
|
339
|
-
);
|
|
340
|
-
if (!found) throw new Error(`Address not in session: ${address}`);
|
|
341
|
-
return found;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
if (method === 'eth_accounts') {
|
|
345
|
-
if (!session) return [];
|
|
346
|
-
return getChecksummedAccounts(session);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (method === 'eth_chainId') {
|
|
350
|
-
if (!session) throw new Error('No active session');
|
|
351
|
-
if (selectedChainId !== null) return `0x${selectedChainId.toString(16)}`;
|
|
352
|
-
const evmInfo = getEvmAddressInfo(session);
|
|
353
|
-
if (evmInfo.length === 0) throw new Error('No EVM address found');
|
|
354
|
-
const chainId = Number.parseInt(evmInfo[0].networkIdentifier, 10);
|
|
355
|
-
if (!Number.isFinite(chainId)) throw new Error(`Invalid chainId: ${chainId}`);
|
|
356
|
-
selectedChainId = chainId;
|
|
357
|
-
return `0x${chainId.toString(16)}`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (method === 'wallet_switchEthereumChain') {
|
|
361
|
-
const [{ chainId: rawChainId }] = (params ?? []) as [{ chainId?: string }];
|
|
362
|
-
if (!rawChainId) throw new Error('wallet_switchEthereumChain missing chainId param');
|
|
363
|
-
const chainId = Number.parseInt(rawChainId, 16);
|
|
364
|
-
if (!Number.isFinite(chainId)) throw new Error(`Invalid chainId: ${rawChainId}`);
|
|
365
|
-
if (!session) throw new Error('No active session');
|
|
366
|
-
|
|
367
|
-
const supported = getEvmAddressInfo(session).some(
|
|
368
|
-
(info) => Number.parseInt(info.networkIdentifier, 10) === chainId,
|
|
369
|
-
);
|
|
370
|
-
if (!supported) {
|
|
371
|
-
const err = new Error(`Unsupported chain: ${chainId}`) as Error & { code?: number };
|
|
372
|
-
err.code = 4902;
|
|
373
|
-
throw err;
|
|
374
|
-
}
|
|
375
|
-
selectedChainId = chainId;
|
|
376
|
-
config.emitter.emit('change', { chainId });
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (method === 'personal_sign') {
|
|
381
|
-
const [messageParam, addressParam] = (params ?? []) as [unknown, unknown];
|
|
382
|
-
const message =
|
|
383
|
-
typeof messageParam === 'string' ? messageParam : String(messageParam ?? '');
|
|
384
|
-
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
385
|
-
const addressInfo = getSelectedAddressInfo(address);
|
|
386
|
-
const response = await session?.jsonRpc.request('signMessage', {
|
|
387
|
-
message,
|
|
388
|
-
addressInfo,
|
|
389
|
-
});
|
|
390
|
-
return response?.signature;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (method === 'eth_sign') {
|
|
394
|
-
const [addressParam, messageParam] = (params ?? []) as [unknown, unknown];
|
|
395
|
-
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
396
|
-
const message =
|
|
397
|
-
typeof messageParam === 'string' ? messageParam : String(messageParam ?? '');
|
|
398
|
-
const addressInfo = getSelectedAddressInfo(address);
|
|
399
|
-
const response = await session?.jsonRpc.request('signMessage', {
|
|
400
|
-
message,
|
|
401
|
-
addressInfo,
|
|
402
|
-
});
|
|
403
|
-
return response?.signature;
|
|
404
|
-
}
|
|
470
|
+
if (providerPromise) return providerPromise;
|
|
405
471
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
: JSON.stringify(typedDataParam ?? '');
|
|
413
|
-
const addressInfo = getSelectedAddressInfo(address);
|
|
414
|
-
const response = await session?.jsonRpc.request('signTypedData', {
|
|
415
|
-
typedData,
|
|
416
|
-
addressInfo,
|
|
417
|
-
});
|
|
418
|
-
return response?.signature;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (method === 'eth_sendTransaction') {
|
|
422
|
-
const [txParams] = (params ?? []) as [Record<string, string | undefined>];
|
|
423
|
-
const addressInfo = getSelectedAddressInfo(txParams?.from);
|
|
424
|
-
const response = await session?.jsonRpc.request('sendTransaction', {
|
|
425
|
-
addressInfo,
|
|
426
|
-
to: txParams?.to,
|
|
427
|
-
value: txParams?.value,
|
|
428
|
-
data: txParams?.data,
|
|
429
|
-
gas: txParams?.gas,
|
|
430
|
-
gasPrice: txParams?.gasPrice,
|
|
431
|
-
maxFeePerGas: txParams?.maxFeePerGas,
|
|
432
|
-
maxPriorityFeePerGas: txParams?.maxPriorityFeePerGas,
|
|
433
|
-
nonce: txParams?.nonce,
|
|
434
|
-
chainId: txParams?.chainId,
|
|
435
|
-
});
|
|
436
|
-
return response?.txHash;
|
|
437
|
-
}
|
|
472
|
+
providerPromise = (async (): Promise<CreditConnectProvider> => {
|
|
473
|
+
await ensureManagerInitialized();
|
|
474
|
+
const createdProvider = buildProvider();
|
|
475
|
+
provider = createdProvider;
|
|
476
|
+
return createdProvider;
|
|
477
|
+
})();
|
|
438
478
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
? messageParam
|
|
445
|
-
: JSON.stringify(messageParam ?? '');
|
|
446
|
-
await session.msg.sendMessage(message);
|
|
447
|
-
return true;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (method === 'credit_connect_jsonrpc_ping') {
|
|
451
|
-
if (!session) throw new Error('No active session');
|
|
452
|
-
return await session.jsonRpc.request('pingPong', {});
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
throw new Error(`Unsupported provider method: ${method}`);
|
|
456
|
-
},
|
|
457
|
-
|
|
458
|
-
on: (event: string, listener: ProviderListener) => {
|
|
459
|
-
let listeners = providerListeners.get(event);
|
|
460
|
-
if (!listeners) {
|
|
461
|
-
listeners = new Set();
|
|
462
|
-
providerListeners.set(event, listeners);
|
|
463
|
-
}
|
|
464
|
-
listeners.add(listener);
|
|
465
|
-
return createdProvider;
|
|
466
|
-
},
|
|
467
|
-
|
|
468
|
-
removeListener: (event: string, listener: ProviderListener) => {
|
|
469
|
-
providerListeners.get(event)?.delete(listener);
|
|
470
|
-
return createdProvider;
|
|
471
|
-
},
|
|
472
|
-
} as CreditConnectProviderWithEvents;
|
|
473
|
-
|
|
474
|
-
provider = createdProvider;
|
|
475
|
-
return provider;
|
|
479
|
+
try {
|
|
480
|
+
return await providerPromise;
|
|
481
|
+
} finally {
|
|
482
|
+
providerPromise = null;
|
|
483
|
+
}
|
|
476
484
|
},
|
|
477
485
|
|
|
478
486
|
onAccountsChanged(accounts) {
|