@gluwa/connect-kit 0.1.0-next.3 → 0.2.0-next.10
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 +68 -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/penguinwallet.png +0 -0
- package/assets/wc-logo.svg +3 -0
- package/assets/wc-search.png +0 -0
- package/dist/chunk-VE5LB7BN.js +405 -0
- package/dist/credit-connect.js +2 -394
- package/dist/index.css +2075 -0
- package/dist/index.d.ts +58 -33
- package/dist/index.js +1748 -1503
- package/dist/package.json +16 -10
- package/package.json +14 -11
- package/src/ConnectKitProvider.tsx +133 -75
- package/src/api/account.ts +21 -0
- package/src/api/asset.ts +10 -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 +72 -0
- package/src/components/qrArea/style.scss +122 -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/toast/index.tsx +41 -0
- package/src/components/toast/style.scss +108 -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 +16 -11
- package/src/core/config.ts +102 -0
- package/src/creditConnectConnector.ts +198 -184
- package/src/events/account.ts +76 -0
- package/src/hooks/useConnectBanner.ts +79 -0
- package/src/hooks/useConnectTimeout.ts +92 -0
- package/src/hooks/useMetaMaskExtension.ts +29 -0
- package/src/hooks/useWCState.ts +45 -14
- package/src/hooks/useWagmiConnect.ts +42 -11
- package/src/index.ts +38 -2
- package/src/layout/allWallets/index.tsx +52 -0
- package/src/layout/allWallets/style.scss +57 -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/asset/warningIcon.tsx +21 -0
- package/src/layout/connectDialog/idle/assets/banner-placeholder.png +0 -0
- package/src/layout/connectDialog/idle/index.tsx +55 -0
- package/src/layout/connectDialog/idle/style.scss +52 -0
- package/src/layout/connectDialog/index.tsx +432 -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 +48 -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 +175 -0
- package/src/layout/detailPanel/style.scss +70 -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 +40 -7
- package/src/utils/platform.ts +6 -7
- package/src/wallet-display-override.ts +15 -0
- package/tsconfig.json +5 -1
- package/tsup.config.ts +32 -2
- package/assets/creditwallet.png +0 -0
- 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
|
@@ -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,18 +84,35 @@ 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?.();
|
|
95
107
|
}
|
|
96
108
|
});
|
|
97
109
|
|
|
110
|
+
manager.on('sessionExpired', (payload) => {
|
|
111
|
+
// eslint-disable-next-line no-console
|
|
112
|
+
console.log('[CC connector] session expired → emit session_expired', payload);
|
|
113
|
+
emitWagmiMessage?.('session_expired', payload);
|
|
114
|
+
});
|
|
115
|
+
|
|
98
116
|
return manager;
|
|
99
117
|
};
|
|
100
118
|
|
|
@@ -128,6 +146,163 @@ export const creditConnectConnector = (
|
|
|
128
146
|
config.emitter.emit('disconnect');
|
|
129
147
|
};
|
|
130
148
|
|
|
149
|
+
const buildProvider = (): CreditConnectProviderWithEvents => {
|
|
150
|
+
const createdProvider = {
|
|
151
|
+
request: async (args) => {
|
|
152
|
+
const { method, params: rawParams } = args as {
|
|
153
|
+
method: string;
|
|
154
|
+
params?: unknown[] | object;
|
|
155
|
+
};
|
|
156
|
+
const params = Array.isArray(rawParams)
|
|
157
|
+
? [...rawParams]
|
|
158
|
+
: rawParams === undefined
|
|
159
|
+
? []
|
|
160
|
+
: [rawParams];
|
|
161
|
+
const session = getCurrentSession();
|
|
162
|
+
|
|
163
|
+
const getSelectedAddressInfo = (address?: string) => {
|
|
164
|
+
if (!session) throw new Error('No active session');
|
|
165
|
+
const evmInfo = getEvmAddressInfo(session);
|
|
166
|
+
if (evmInfo.length === 0) throw new Error('No EVM address found');
|
|
167
|
+
if (!address) return evmInfo[0];
|
|
168
|
+
const found = evmInfo.find(
|
|
169
|
+
(info) => info.address.toLowerCase() === address.toLowerCase(),
|
|
170
|
+
);
|
|
171
|
+
if (!found) throw new Error(`Address not in session: ${address}`);
|
|
172
|
+
return found;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (method === 'eth_accounts') {
|
|
176
|
+
if (!session) return [];
|
|
177
|
+
return getChecksummedAccounts(session);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (method === 'eth_chainId') {
|
|
181
|
+
if (!session) throw new Error('No active session');
|
|
182
|
+
if (selectedChainId !== null) return `0x${selectedChainId.toString(16)}`;
|
|
183
|
+
const evmInfo = getEvmAddressInfo(session);
|
|
184
|
+
if (evmInfo.length === 0) throw new Error('No EVM address found');
|
|
185
|
+
const chainId = Number.parseInt(evmInfo[0].networkIdentifier, 10);
|
|
186
|
+
if (!Number.isFinite(chainId)) throw new Error(`Invalid chainId: ${chainId}`);
|
|
187
|
+
selectedChainId = chainId;
|
|
188
|
+
return `0x${chainId.toString(16)}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (method === 'wallet_switchEthereumChain') {
|
|
192
|
+
const [{ chainId: rawChainId }] = (params ?? []) as [{ chainId?: string }];
|
|
193
|
+
if (!rawChainId) throw new Error('wallet_switchEthereumChain missing chainId param');
|
|
194
|
+
const chainId = Number.parseInt(rawChainId, 16);
|
|
195
|
+
if (!Number.isFinite(chainId)) throw new Error(`Invalid chainId: ${rawChainId}`);
|
|
196
|
+
if (!session) throw new Error('No active session');
|
|
197
|
+
|
|
198
|
+
const supported = getEvmAddressInfo(session).some(
|
|
199
|
+
(info) => Number.parseInt(info.networkIdentifier, 10) === chainId,
|
|
200
|
+
);
|
|
201
|
+
if (!supported) {
|
|
202
|
+
const err = new Error(`Unsupported chain: ${chainId}`) as Error & { code?: number };
|
|
203
|
+
err.code = 4902;
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
selectedChainId = chainId;
|
|
207
|
+
config.emitter.emit('change', { chainId });
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (method === 'personal_sign') {
|
|
212
|
+
const [messageParam, addressParam] = (params ?? []) as [unknown, unknown];
|
|
213
|
+
const message =
|
|
214
|
+
typeof messageParam === 'string' ? messageParam : String(messageParam ?? '');
|
|
215
|
+
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
216
|
+
const addressInfo = getSelectedAddressInfo(address);
|
|
217
|
+
const response = await session?.jsonRpc.request('signMessage', {
|
|
218
|
+
message,
|
|
219
|
+
addressInfo,
|
|
220
|
+
});
|
|
221
|
+
return response?.signature;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (method === 'eth_sign') {
|
|
225
|
+
const [addressParam, messageParam] = (params ?? []) as [unknown, unknown];
|
|
226
|
+
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
227
|
+
const message =
|
|
228
|
+
typeof messageParam === 'string' ? messageParam : String(messageParam ?? '');
|
|
229
|
+
const addressInfo = getSelectedAddressInfo(address);
|
|
230
|
+
const response = await session?.jsonRpc.request('signMessage', {
|
|
231
|
+
message,
|
|
232
|
+
addressInfo,
|
|
233
|
+
});
|
|
234
|
+
return response?.signature;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (method === 'eth_signTypedData' || method === 'eth_signTypedData_v4') {
|
|
238
|
+
const [addressParam, typedDataParam] = (params ?? []) as [unknown, unknown];
|
|
239
|
+
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
240
|
+
const typedData =
|
|
241
|
+
typeof typedDataParam === 'string'
|
|
242
|
+
? typedDataParam
|
|
243
|
+
: JSON.stringify(typedDataParam ?? '');
|
|
244
|
+
const addressInfo = getSelectedAddressInfo(address);
|
|
245
|
+
const response = await session?.jsonRpc.request('signTypedData', {
|
|
246
|
+
typedData,
|
|
247
|
+
addressInfo,
|
|
248
|
+
});
|
|
249
|
+
return response?.signature;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (method === 'eth_sendTransaction') {
|
|
253
|
+
const [txParams] = (params ?? []) as [Record<string, string | undefined>];
|
|
254
|
+
const addressInfo = getSelectedAddressInfo(txParams?.from);
|
|
255
|
+
const response = await session?.jsonRpc.request('sendTransaction', {
|
|
256
|
+
addressInfo,
|
|
257
|
+
to: txParams?.to,
|
|
258
|
+
value: txParams?.value,
|
|
259
|
+
data: txParams?.data,
|
|
260
|
+
gas: txParams?.gas,
|
|
261
|
+
gasPrice: txParams?.gasPrice,
|
|
262
|
+
maxFeePerGas: txParams?.maxFeePerGas,
|
|
263
|
+
maxPriorityFeePerGas: txParams?.maxPriorityFeePerGas,
|
|
264
|
+
nonce: txParams?.nonce,
|
|
265
|
+
chainId: txParams?.chainId,
|
|
266
|
+
});
|
|
267
|
+
return response?.txHash;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (method === 'credit_connect_send_message') {
|
|
271
|
+
if (!session) throw new Error('No active session');
|
|
272
|
+
const [messageParam] = (params ?? []) as [unknown];
|
|
273
|
+
const message =
|
|
274
|
+
typeof messageParam === 'string' ? messageParam : JSON.stringify(messageParam ?? '');
|
|
275
|
+
await session.msg.sendMessage(message);
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (method === 'credit_connect_jsonrpc_ping') {
|
|
280
|
+
if (!session) throw new Error('No active session');
|
|
281
|
+
return await session.jsonRpc.request('pingPong', {});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
throw new Error(`Unsupported provider method: ${method}`);
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
on: (event: string, listener: ProviderListener) => {
|
|
288
|
+
let listeners = providerListeners.get(event);
|
|
289
|
+
if (!listeners) {
|
|
290
|
+
listeners = new Set();
|
|
291
|
+
providerListeners.set(event, listeners);
|
|
292
|
+
}
|
|
293
|
+
listeners.add(listener);
|
|
294
|
+
return createdProvider;
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
removeListener: (event: string, listener: ProviderListener) => {
|
|
298
|
+
providerListeners.get(event)?.delete(listener);
|
|
299
|
+
return createdProvider;
|
|
300
|
+
},
|
|
301
|
+
} as CreditConnectProviderWithEvents;
|
|
302
|
+
|
|
303
|
+
return createdProvider;
|
|
304
|
+
};
|
|
305
|
+
|
|
131
306
|
return {
|
|
132
307
|
id: CONNECTOR_ID,
|
|
133
308
|
name: CONNECTOR_NAME,
|
|
@@ -185,32 +360,13 @@ export const creditConnectConnector = (
|
|
|
185
360
|
});
|
|
186
361
|
}
|
|
187
362
|
|
|
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
|
-
|
|
363
|
+
// 새 connect 시도 — stale state (CONNECTING/CONNECTING_WAITING_PAYLOAD) 와
|
|
364
|
+
// stale connectPromise 는 SDK 의 self-heal (cleanupHub + attempt-id) 에 위임.
|
|
365
|
+
// connector 는 단순히 새 connectNewSession() 을 호출 — 이전 attempt 는 자연 정리됨.
|
|
206
366
|
if (m.state.status !== 'CONNECTED') {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
connectPromise = m.connectNewSession();
|
|
212
|
-
}
|
|
213
|
-
|
|
367
|
+
// 이전 attempt 의 await 가 살아있어도, SDK 의 attempt-id 패턴이
|
|
368
|
+
// 그 결과를 무시하도록 보장한다. connectPromise 는 무조건 새 promise 로.
|
|
369
|
+
connectPromise = m.connectNewSession();
|
|
214
370
|
const connected = await connectPromise;
|
|
215
371
|
connectPromise = null;
|
|
216
372
|
|
|
@@ -261,11 +417,13 @@ export const creditConnectConnector = (
|
|
|
261
417
|
throw err;
|
|
262
418
|
}
|
|
263
419
|
|
|
420
|
+
const chain = config.chains.find((c) => c.id === chainId);
|
|
421
|
+
if (!chain) throw new Error(`Chain ${chainId} not in config`);
|
|
422
|
+
|
|
264
423
|
selectedChainId = chainId;
|
|
265
424
|
config.emitter.emit('change', { chainId });
|
|
425
|
+
await Promise.resolve();
|
|
266
426
|
|
|
267
|
-
const chain = config.chains.find((c) => c.id === chainId);
|
|
268
|
-
if (!chain) throw new Error(`Chain ${chainId} not in config`);
|
|
269
427
|
return chain;
|
|
270
428
|
},
|
|
271
429
|
|
|
@@ -286,6 +444,7 @@ export const creditConnectConnector = (
|
|
|
286
444
|
isLocalDisconnect = false;
|
|
287
445
|
}
|
|
288
446
|
provider = null;
|
|
447
|
+
providerPromise = null;
|
|
289
448
|
resetState();
|
|
290
449
|
config.emitter.emit('disconnect');
|
|
291
450
|
},
|
|
@@ -314,165 +473,20 @@ export const creditConnectConnector = (
|
|
|
314
473
|
|
|
315
474
|
async getProvider() {
|
|
316
475
|
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
|
-
}
|
|
476
|
+
if (providerPromise) return providerPromise;
|
|
348
477
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
}
|
|
478
|
+
providerPromise = (async (): Promise<CreditConnectProvider> => {
|
|
479
|
+
await ensureManagerInitialized();
|
|
480
|
+
const createdProvider = buildProvider();
|
|
481
|
+
provider = createdProvider;
|
|
482
|
+
return createdProvider;
|
|
483
|
+
})();
|
|
379
484
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
}
|
|
405
|
-
|
|
406
|
-
if (method === 'eth_signTypedData' || method === 'eth_signTypedData_v4') {
|
|
407
|
-
const [addressParam, typedDataParam] = (params ?? []) as [unknown, unknown];
|
|
408
|
-
const address = typeof addressParam === 'string' ? addressParam : undefined;
|
|
409
|
-
const typedData =
|
|
410
|
-
typeof typedDataParam === 'string'
|
|
411
|
-
? typedDataParam
|
|
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
|
-
}
|
|
438
|
-
|
|
439
|
-
if (method === 'credit_connect_send_message') {
|
|
440
|
-
if (!session) throw new Error('No active session');
|
|
441
|
-
const [messageParam] = (params ?? []) as [unknown];
|
|
442
|
-
const message =
|
|
443
|
-
typeof messageParam === 'string'
|
|
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;
|
|
485
|
+
try {
|
|
486
|
+
return await providerPromise;
|
|
487
|
+
} finally {
|
|
488
|
+
providerPromise = null;
|
|
489
|
+
}
|
|
476
490
|
},
|
|
477
491
|
|
|
478
492
|
onAccountsChanged(accounts) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
watchAccount as wagmiWatchAccount,
|
|
3
|
+
watchConnections as wagmiWatchConnections,
|
|
4
|
+
reconnect as wagmiReconnect,
|
|
5
|
+
connect as wagmiConnect,
|
|
6
|
+
getAccount as wagmiGetAccount,
|
|
7
|
+
disconnect as wagmiDisconnect,
|
|
8
|
+
type WatchAccountParameters,
|
|
9
|
+
type WatchConnectionsParameters,
|
|
10
|
+
type ReconnectReturnType,
|
|
11
|
+
} from '@wagmi/core';
|
|
12
|
+
import { getConfig, getKitConfig } from '../core/config';
|
|
13
|
+
|
|
14
|
+
export const watchAccount = (params: WatchAccountParameters): (() => void) => {
|
|
15
|
+
return wagmiWatchAccount(getConfig(), params);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const watchConnections = (params: WatchConnectionsParameters): (() => void) => {
|
|
19
|
+
return wagmiWatchConnections(getConfig(), params);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const reconnect = async (): Promise<ReconnectReturnType> => {
|
|
23
|
+
const config = getConfig();
|
|
24
|
+
|
|
25
|
+
if (getKitConfig().autoConnectInjected !== false) {
|
|
26
|
+
try {
|
|
27
|
+
const injected = config.connectors.find((connector) => connector.type === 'injected');
|
|
28
|
+
if (injected) {
|
|
29
|
+
const provider = (await injected.getProvider()) as
|
|
30
|
+
| { request?: (args: { method: string }) => Promise<unknown> }
|
|
31
|
+
| undefined;
|
|
32
|
+
const accounts = provider?.request
|
|
33
|
+
? await provider.request({ method: 'eth_accounts' })
|
|
34
|
+
: [];
|
|
35
|
+
if (Array.isArray(accounts) && accounts.length > 0) {
|
|
36
|
+
await wagmiConnect(config, { connector: injected });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
console.warn('injected auto-connect skipped (non-fatal):', error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (wagmiGetAccount(config).status === 'connected') {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return wagmiReconnect(config);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const disconnectAll = async (
|
|
52
|
+
onLog?: (message: string, error?: Error) => void,
|
|
53
|
+
): Promise<void> => {
|
|
54
|
+
const config = getConfig();
|
|
55
|
+
const results = await Promise.allSettled(
|
|
56
|
+
config.connectors.map((connector) =>
|
|
57
|
+
wagmiDisconnect(config, { connector }).then(() => connector.id),
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const errors: Array<{ connectorId: string; error: Error }> = [];
|
|
62
|
+
results.forEach((result, idx) => {
|
|
63
|
+
if (result.status === 'rejected') {
|
|
64
|
+
const error =
|
|
65
|
+
result.reason instanceof Error ? result.reason : new Error(String(result.reason));
|
|
66
|
+
const connectorId = config.connectors[idx].id;
|
|
67
|
+
errors.push({ connectorId, error });
|
|
68
|
+
onLog?.(`[connect-kit] disconnect failed for "${connectorId}": ${error.message}`, error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (errors.length === config.connectors.length && errors.length > 0) {
|
|
73
|
+
const messages = errors.map((e) => `${e.connectorId}: ${e.error.message}`).join('; ');
|
|
74
|
+
throw new Error(`All disconnects failed — ${messages}`);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { ConnectBanner, ConnectBannerSiteKey } from '../types';
|
|
3
|
+
|
|
4
|
+
interface ConnectBannersResponse {
|
|
5
|
+
banners: ConnectBanner[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface UseConnectBannerParams {
|
|
9
|
+
open: boolean;
|
|
10
|
+
siteKey: ConnectBannerSiteKey;
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface UseConnectBannerResult {
|
|
15
|
+
banner: ConnectBanner | null;
|
|
16
|
+
loading: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const pickRandomBanner = (banners: ConnectBanner[]): ConnectBanner | null =>
|
|
20
|
+
banners[Math.floor(Math.random() * banners.length)] ?? null;
|
|
21
|
+
|
|
22
|
+
export const useConnectBanner = ({
|
|
23
|
+
open,
|
|
24
|
+
siteKey,
|
|
25
|
+
url,
|
|
26
|
+
}: UseConnectBannerParams): UseConnectBannerResult => {
|
|
27
|
+
const [candidates, setCandidates] = useState<ConnectBanner[]>([]);
|
|
28
|
+
const [banner, setBanner] = useState<ConnectBanner | null>(null);
|
|
29
|
+
const [loading, setLoading] = useState(true);
|
|
30
|
+
const wasOpenRef = useRef(open);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!url) {
|
|
34
|
+
setCandidates([]);
|
|
35
|
+
setBanner(null);
|
|
36
|
+
setLoading(false);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
setCandidates([]);
|
|
42
|
+
setBanner(null);
|
|
43
|
+
setLoading(true);
|
|
44
|
+
|
|
45
|
+
fetch(url, { signal: controller.signal })
|
|
46
|
+
.then(async (response) => {
|
|
47
|
+
if (!response.ok) throw new Error(`Failed to fetch connect banners: ${response.status}`);
|
|
48
|
+
return (await response.json()) as ConnectBannersResponse;
|
|
49
|
+
})
|
|
50
|
+
.then(({ banners }) => {
|
|
51
|
+
if (controller.signal.aborted) return;
|
|
52
|
+
if (!Array.isArray(banners)) {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const nextCandidates = banners.filter((item) => item.siteKey !== siteKey);
|
|
58
|
+
setCandidates(nextCandidates);
|
|
59
|
+
setBanner(pickRandomBanner(nextCandidates));
|
|
60
|
+
setLoading(false);
|
|
61
|
+
})
|
|
62
|
+
.catch(() => {
|
|
63
|
+
// API 실패 시 기존 Idle placeholder를 유지한다.
|
|
64
|
+
if (!controller.signal.aborted) setLoading(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return () => controller.abort();
|
|
68
|
+
}, [siteKey, url]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const wasOpen = wasOpenRef.current;
|
|
72
|
+
wasOpenRef.current = open;
|
|
73
|
+
|
|
74
|
+
// 닫힌 dialog 안에서 다음 배너 이미지를 미리 렌더링한다.
|
|
75
|
+
if (wasOpen && !open) setBanner(pickRandomBanner(candidates));
|
|
76
|
+
}, [candidates, open]);
|
|
77
|
+
|
|
78
|
+
return { banner, loading };
|
|
79
|
+
};
|