@abstraxn/signer-react 3.3.6 → 3.3.7
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
|
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.3.7] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **WalletConnect loader stuck after MetaMask approve (create proposal / deposit)** – Returning from the wallet left a zombie relay socket (`connected === true` on a dead WebSocket). The SDK skipped `restartTransport()`, so the tx hash never reached the dapp even though MetaMask confirmed on-chain. After a backgrounded deeplink the relay is now force-restarted, receipt polling retries when the tab is visible again, and React Query refetches on reconnect/focus so status can update without a manual refresh.
|
|
12
|
+
|
|
8
13
|
## [3.3.6] - 2026-09-14
|
|
9
14
|
|
|
10
15
|
### Fixed
|
|
@@ -68,8 +68,11 @@ export function useQueryClientSafe() {
|
|
|
68
68
|
return new QueryClient({
|
|
69
69
|
defaultOptions: {
|
|
70
70
|
queries: {
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// Recover waitForTransactionReceipt / balance queries after returning
|
|
72
|
+
// from a mobile wallet deeplink (tab was frozen / network dropped).
|
|
73
|
+
refetchOnWindowFocus: true,
|
|
74
|
+
refetchOnReconnect: true,
|
|
75
|
+
retry: 2,
|
|
73
76
|
},
|
|
74
77
|
},
|
|
75
78
|
});
|
package/dist/src/hooks.js
CHANGED
|
@@ -12,6 +12,7 @@ import { useWalletClient as useWagmiWalletClient, useAccount, useConfig, useChai
|
|
|
12
12
|
import { getWalletClient, switchChain } from '@wagmi/core';
|
|
13
13
|
import { getConnectorMeta } from './connectors';
|
|
14
14
|
import { getChainById } from './chains';
|
|
15
|
+
import { waitUntilDocumentVisible } from './walletConnectMobile';
|
|
15
16
|
/**
|
|
16
17
|
* Hook to check if wallet is connected
|
|
17
18
|
*/
|
|
@@ -2064,15 +2065,40 @@ export function useWaitForTxnReceipt(provider) {
|
|
|
2064
2065
|
throw new Error('Transaction hash is required');
|
|
2065
2066
|
}
|
|
2066
2067
|
try {
|
|
2067
|
-
//
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2068
|
+
// After MetaMask deeplink the tab was frozen; wait until we are visible
|
|
2069
|
+
// again and retry receipt polls so the UI is not stuck on a loader.
|
|
2070
|
+
await waitUntilDocumentVisible();
|
|
2071
|
+
const overallTimeout = timeout ?? 180_000;
|
|
2072
|
+
const startedAt = Date.now();
|
|
2073
|
+
let lastError;
|
|
2074
|
+
while (Date.now() - startedAt < overallTimeout) {
|
|
2075
|
+
const remaining = overallTimeout - (Date.now() - startedAt);
|
|
2076
|
+
try {
|
|
2077
|
+
const receipt = await provider.waitForTransactionReceipt({
|
|
2078
|
+
hash,
|
|
2079
|
+
confirmations,
|
|
2080
|
+
timeout: Math.min(20_000, remaining),
|
|
2081
|
+
pollingInterval: 1_500,
|
|
2082
|
+
});
|
|
2083
|
+
return receipt;
|
|
2084
|
+
}
|
|
2085
|
+
catch (error) {
|
|
2086
|
+
lastError = error;
|
|
2087
|
+
await waitUntilDocumentVisible();
|
|
2088
|
+
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (lastError instanceof Error) {
|
|
2092
|
+
throw new Error(`Failed to wait for transaction receipt: ${lastError.message}`);
|
|
2093
|
+
}
|
|
2094
|
+
throw lastError instanceof Error
|
|
2095
|
+
? lastError
|
|
2096
|
+
: new Error('Failed to wait for transaction receipt');
|
|
2074
2097
|
}
|
|
2075
2098
|
catch (error) {
|
|
2099
|
+
if (error instanceof Error && error.message.startsWith('Failed to wait for transaction receipt')) {
|
|
2100
|
+
throw error;
|
|
2101
|
+
}
|
|
2076
2102
|
if (error instanceof Error) {
|
|
2077
2103
|
throw new Error(`Failed to wait for transaction receipt: ${error.message}`);
|
|
2078
2104
|
}
|
|
@@ -64,8 +64,16 @@ export declare function syncWalletConnectDappRedirect(provider: any): void;
|
|
|
64
64
|
/**
|
|
65
65
|
* Re-open the WalletConnect relay after the mobile browser comes back
|
|
66
66
|
* from the wallet app so pending approve/reject responses can be delivered.
|
|
67
|
+
*
|
|
68
|
+
* `force` must be used after the tab was backgrounded: iOS/Android often leave
|
|
69
|
+
* `relayer.connected === true` on a dead socket, so a ping/early-return skips
|
|
70
|
+
* the restart and the in-flight tx hash never arrives (loader hangs until refresh).
|
|
67
71
|
*/
|
|
68
|
-
export declare function resumeWalletConnectRelayer(provider: any
|
|
72
|
+
export declare function resumeWalletConnectRelayer(provider: any, options?: {
|
|
73
|
+
force?: boolean;
|
|
74
|
+
}): Promise<void>;
|
|
75
|
+
/** Resolves when the dapp tab is in the foreground again (mobile wallet return). */
|
|
76
|
+
export declare function waitUntilDocumentVisible(): Promise<void>;
|
|
69
77
|
/**
|
|
70
78
|
* Undo the inline `display: none !important` we apply after connect so the
|
|
71
79
|
* WalletConnect modal can show "Continue in wallet" and process the response.
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
const PATCHED_FLAG = "__abstraxnWcMobilePatched";
|
|
18
18
|
const PENDING_FLAG = "__abstraxnWcPendingApprovals";
|
|
19
|
+
const NEEDS_RESUME_FLAG = "__abstraxnWcNeedsResume";
|
|
20
|
+
const RESUME_LOCK = "__abstraxnWcResumeLock";
|
|
19
21
|
const RETURN_HREF_KEY = "abstraxn_wc_return_href";
|
|
20
22
|
const RETURN_AT_KEY = "abstraxn_wc_return_at";
|
|
21
23
|
const RETURN_TTL_MS = 3 * 60 * 1000;
|
|
@@ -249,60 +251,102 @@ function isRelayerConnected(relayer) {
|
|
|
249
251
|
relayer?.provider?.connected === true ||
|
|
250
252
|
relayer?.provider?.connection?.connected === true);
|
|
251
253
|
}
|
|
254
|
+
async function restartWalletConnectTransport(relayer) {
|
|
255
|
+
if (typeof relayer.restartTransport === "function") {
|
|
256
|
+
await relayer.restartTransport();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (typeof relayer.transportOpen === "function") {
|
|
260
|
+
try {
|
|
261
|
+
await relayer.transportClose?.();
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Ignore close errors and still try to open.
|
|
265
|
+
}
|
|
266
|
+
await relayer.transportOpen();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
252
269
|
/**
|
|
253
270
|
* Re-open the WalletConnect relay after the mobile browser comes back
|
|
254
271
|
* from the wallet app so pending approve/reject responses can be delivered.
|
|
272
|
+
*
|
|
273
|
+
* `force` must be used after the tab was backgrounded: iOS/Android often leave
|
|
274
|
+
* `relayer.connected === true` on a dead socket, so a ping/early-return skips
|
|
275
|
+
* the restart and the in-flight tx hash never arrives (loader hangs until refresh).
|
|
255
276
|
*/
|
|
256
|
-
export async function resumeWalletConnectRelayer(provider) {
|
|
277
|
+
export async function resumeWalletConnectRelayer(provider, options) {
|
|
257
278
|
if (!provider)
|
|
258
279
|
return;
|
|
259
280
|
const relayer = getWalletConnectRelayer(provider);
|
|
260
281
|
if (!relayer)
|
|
261
282
|
return;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
283
|
+
const existing = provider[RESUME_LOCK];
|
|
284
|
+
if (existing && !options?.force) {
|
|
285
|
+
try {
|
|
286
|
+
await existing;
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// Previous resume failed.
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const run = (async () => {
|
|
294
|
+
try {
|
|
295
|
+
if (!options?.force && isRelayerConnected(relayer)) {
|
|
265
296
|
const ping = relayer.provider?.connection?.ping;
|
|
266
297
|
if (typeof ping === "function") {
|
|
267
298
|
await Promise.race([
|
|
268
299
|
ping.call(relayer.provider.connection),
|
|
269
300
|
new Promise((_, reject) => setTimeout(() => reject(new Error("ping timeout")), 1500)),
|
|
270
301
|
]);
|
|
302
|
+
return;
|
|
271
303
|
}
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
catch {
|
|
275
|
-
// Ping failed — transport looks stale, restart below.
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
if (typeof relayer.restartTransport === "function") {
|
|
279
|
-
await relayer.restartTransport();
|
|
280
|
-
}
|
|
281
|
-
else if (typeof relayer.transportOpen === "function") {
|
|
282
|
-
try {
|
|
283
|
-
await relayer.transportClose?.();
|
|
304
|
+
// No ping API: do not assume a zombie "connected" socket is healthy.
|
|
284
305
|
}
|
|
285
|
-
|
|
286
|
-
|
|
306
|
+
await restartWalletConnectTransport(relayer);
|
|
307
|
+
const session = provider?.session ?? provider?.signer?.session;
|
|
308
|
+
const topic = session?.topic;
|
|
309
|
+
const client = provider?.signer?.client || provider?.client;
|
|
310
|
+
if (topic && typeof client?.ping === "function") {
|
|
311
|
+
try {
|
|
312
|
+
await client.ping({ topic });
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Session ping is best-effort; the request may still complete.
|
|
316
|
+
}
|
|
287
317
|
}
|
|
288
|
-
await relayer.transportOpen();
|
|
289
318
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const client = provider?.signer?.client || provider?.client;
|
|
293
|
-
if (topic && typeof client?.ping === "function") {
|
|
294
|
-
try {
|
|
295
|
-
await client.ping({ topic });
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
// Session ping is best-effort; the request may still complete.
|
|
299
|
-
}
|
|
319
|
+
catch {
|
|
320
|
+
// Best-effort: a failed resume should not break the original request.
|
|
300
321
|
}
|
|
322
|
+
})();
|
|
323
|
+
provider[RESUME_LOCK] = run;
|
|
324
|
+
try {
|
|
325
|
+
await run;
|
|
301
326
|
}
|
|
302
|
-
|
|
303
|
-
|
|
327
|
+
finally {
|
|
328
|
+
if (provider[RESUME_LOCK] === run)
|
|
329
|
+
provider[RESUME_LOCK] = null;
|
|
304
330
|
}
|
|
305
331
|
}
|
|
332
|
+
/** Resolves when the dapp tab is in the foreground again (mobile wallet return). */
|
|
333
|
+
export function waitUntilDocumentVisible() {
|
|
334
|
+
if (typeof document === "undefined")
|
|
335
|
+
return Promise.resolve();
|
|
336
|
+
if (document.visibilityState === "visible")
|
|
337
|
+
return Promise.resolve();
|
|
338
|
+
return new Promise((resolve) => {
|
|
339
|
+
const onVis = () => {
|
|
340
|
+
if (document.visibilityState === "visible") {
|
|
341
|
+
document.removeEventListener("visibilitychange", onVis);
|
|
342
|
+
window.removeEventListener("pageshow", onVis);
|
|
343
|
+
resolve();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
document.addEventListener("visibilitychange", onVis);
|
|
347
|
+
window.addEventListener("pageshow", onVis);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
306
350
|
/**
|
|
307
351
|
* Undo the inline `display: none !important` we apply after connect so the
|
|
308
352
|
* WalletConnect modal can show "Continue in wallet" and process the response.
|
|
@@ -347,18 +391,26 @@ export function patchWalletConnectProviderForMobile(provider) {
|
|
|
347
391
|
provider[PENDING_FLAG] = 0;
|
|
348
392
|
provider.request = async (...args) => {
|
|
349
393
|
const method = getRequestMethod(args);
|
|
350
|
-
|
|
394
|
+
const isApproval = isUserApprovalRpcMethod(method);
|
|
395
|
+
const wasBackgrounded = !!provider[NEEDS_RESUME_FLAG];
|
|
396
|
+
if (!isApproval && !wasBackgrounded) {
|
|
351
397
|
return originalRequest(...args);
|
|
352
398
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
399
|
+
if (isApproval) {
|
|
400
|
+
provider[PENDING_FLAG] = (provider[PENDING_FLAG] || 0) + 1;
|
|
401
|
+
restoreWalletConnectModal();
|
|
402
|
+
syncWalletConnectDappRedirect(provider);
|
|
403
|
+
}
|
|
356
404
|
try {
|
|
357
|
-
await resumeWalletConnectRelayer(provider);
|
|
405
|
+
await resumeWalletConnectRelayer(provider, { force: wasBackgrounded });
|
|
406
|
+
if (wasBackgrounded)
|
|
407
|
+
provider[NEEDS_RESUME_FLAG] = false;
|
|
358
408
|
return await originalRequest(...args);
|
|
359
409
|
}
|
|
360
410
|
finally {
|
|
361
|
-
|
|
411
|
+
if (isApproval) {
|
|
412
|
+
provider[PENDING_FLAG] = Math.max(0, (provider[PENDING_FLAG] || 1) - 1);
|
|
413
|
+
}
|
|
362
414
|
}
|
|
363
415
|
};
|
|
364
416
|
}
|
|
@@ -370,18 +422,28 @@ export function subscribeWalletConnectForegroundResume(provider) {
|
|
|
370
422
|
if (typeof window === "undefined" || !provider)
|
|
371
423
|
return () => { };
|
|
372
424
|
let timer = null;
|
|
425
|
+
let hiddenAt = 0;
|
|
373
426
|
const resume = () => {
|
|
374
427
|
if (timer)
|
|
375
428
|
clearTimeout(timer);
|
|
429
|
+
const hiddenMs = hiddenAt ? Date.now() - hiddenAt : 0;
|
|
430
|
+
const pending = hasPendingWalletConnectApproval(provider);
|
|
431
|
+
const force = pending || hiddenMs > 400 || !!provider[NEEDS_RESUME_FLAG];
|
|
376
432
|
timer = setTimeout(() => {
|
|
377
433
|
clearWalletReturnIfSameDocument();
|
|
378
|
-
void resumeWalletConnectRelayer(provider);
|
|
379
|
-
if (
|
|
434
|
+
void resumeWalletConnectRelayer(provider, { force });
|
|
435
|
+
if (pending)
|
|
380
436
|
restoreWalletConnectModal();
|
|
381
|
-
|
|
382
|
-
}, 250);
|
|
437
|
+
}, pending || force ? 50 : 250);
|
|
383
438
|
};
|
|
384
439
|
const onVisibility = () => {
|
|
440
|
+
if (document.visibilityState === "hidden") {
|
|
441
|
+
hiddenAt = Date.now();
|
|
442
|
+
if (hasPendingWalletConnectApproval(provider)) {
|
|
443
|
+
provider[NEEDS_RESUME_FLAG] = true;
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
385
447
|
if (document.visibilityState === "visible")
|
|
386
448
|
resume();
|
|
387
449
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abstraxn/signer-react",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.7",
|
|
4
4
|
"description": "React SDK for Abstraxn Wallet - React components, hooks, and providers for seamless Web3 wallet integration",
|
|
5
5
|
"main": "./dist/src/index.js",
|
|
6
6
|
"module": "./dist/src/index.js",
|