@abstraxn/signer-react 3.3.7 → 3.3.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 +18 -0
- package/dist/src/hooks.d.ts +40 -40
- package/dist/src/hooks.js +115 -55
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/walletConnectMobile.d.ts +57 -3
- package/dist/src/walletConnectMobile.js +365 -40
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ 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.9] - 2026-09-17
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Mobile WalletConnect `writeContract` loader stuck after MetaMask confirm** – On iOS/Android the browser often drops the WalletConnect relay while MetaMask is open, so `eth_sendTransaction` never returns even though the tx is on-chain. `useWriteContract` now races the wallet call against an RPC scan for the matching outgoing contract tx (`from` / `to` / function selector) and clears pending WC mobile flags when the hash is recovered that way.
|
|
12
|
+
- **Stacked “Open in MetaMask?” / “All Wallets” after an already-connected session** – Auto-deeplink and WalletConnect modal restore are skipped when a WC session already exists; only the connect flow may schedule a single wallet open. Connected sessions hide the picker instead of reopening it.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **Chain-poll recovery is gated** – The on-chain hash poll runs only for WalletConnect when the device is mobile or the document is `hidden`. Desktop injected wallets and idle desktop WC keep the plain `writeContract` path (no always-on poll).
|
|
16
|
+
- **`gasLimit` on `useWriteContract`** – Mapped to viem’s `gas` option on the direct `walletClient.writeContract` path so callers that pass `gasLimit` are honored.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Public helpers: `pollForOutgoingTx`, `clearWalletConnectPendingState`, `waitUntilDocumentVisible`, `isWalletConnectConnector`, `isMobileDevice`.
|
|
20
|
+
|
|
21
|
+
## [3.3.8] - 2026-09-16
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Create Proposal never opened MetaMask (loader stuck)** – 3.3.7 force-restarted the WalletConnect relay after every return from the wallet, including after *connect*. That restart raced the next `session_request`, so Create Proposal never deeplinked to MetaMask on iOS/Android. Relay restart now happens only for an in-flight approval. New transactions send first, then explicitly open the connected wallet app so the confirmation popup appears.
|
|
25
|
+
|
|
8
26
|
## [3.3.7] - 2026-09-15
|
|
9
27
|
|
|
10
28
|
### Fixed
|
package/dist/src/hooks.d.ts
CHANGED
|
@@ -360,15 +360,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
360
360
|
transactions: includeTransactions extends true ? ({
|
|
361
361
|
input: import("viem").Hex;
|
|
362
362
|
s: import("viem").Hex;
|
|
363
|
-
|
|
363
|
+
from: Address;
|
|
364
|
+
to: Address | null;
|
|
364
365
|
value: bigint;
|
|
366
|
+
hash: import("viem").Hash;
|
|
367
|
+
type: "legacy";
|
|
365
368
|
yParity?: undefined | undefined;
|
|
366
|
-
from: Address;
|
|
367
369
|
gas: bigint;
|
|
368
|
-
hash: import("viem").Hash;
|
|
369
370
|
nonce: number;
|
|
370
371
|
r: import("viem").Hex;
|
|
371
|
-
to: Address | null;
|
|
372
372
|
typeHex: import("viem").Hex | null;
|
|
373
373
|
v: bigint;
|
|
374
374
|
accessList?: undefined | undefined;
|
|
@@ -385,15 +385,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
385
385
|
} | {
|
|
386
386
|
input: import("viem").Hex;
|
|
387
387
|
s: import("viem").Hex;
|
|
388
|
-
|
|
388
|
+
from: Address;
|
|
389
|
+
to: Address | null;
|
|
389
390
|
value: bigint;
|
|
391
|
+
hash: import("viem").Hash;
|
|
392
|
+
type: "eip2930";
|
|
390
393
|
yParity: number;
|
|
391
|
-
from: Address;
|
|
392
394
|
gas: bigint;
|
|
393
|
-
hash: import("viem").Hash;
|
|
394
395
|
nonce: number;
|
|
395
396
|
r: import("viem").Hex;
|
|
396
|
-
to: Address | null;
|
|
397
397
|
typeHex: import("viem").Hex | null;
|
|
398
398
|
v: bigint;
|
|
399
399
|
accessList: import("viem").AccessList;
|
|
@@ -410,15 +410,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
410
410
|
} | {
|
|
411
411
|
input: import("viem").Hex;
|
|
412
412
|
s: import("viem").Hex;
|
|
413
|
-
|
|
413
|
+
from: Address;
|
|
414
|
+
to: Address | null;
|
|
414
415
|
value: bigint;
|
|
416
|
+
hash: import("viem").Hash;
|
|
417
|
+
type: "eip1559";
|
|
415
418
|
yParity: number;
|
|
416
|
-
from: Address;
|
|
417
419
|
gas: bigint;
|
|
418
|
-
hash: import("viem").Hash;
|
|
419
420
|
nonce: number;
|
|
420
421
|
r: import("viem").Hex;
|
|
421
|
-
to: Address | null;
|
|
422
422
|
typeHex: import("viem").Hex | null;
|
|
423
423
|
v: bigint;
|
|
424
424
|
accessList: import("viem").AccessList;
|
|
@@ -435,15 +435,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
435
435
|
} | {
|
|
436
436
|
input: import("viem").Hex;
|
|
437
437
|
s: import("viem").Hex;
|
|
438
|
-
|
|
438
|
+
from: Address;
|
|
439
|
+
to: Address | null;
|
|
439
440
|
value: bigint;
|
|
441
|
+
hash: import("viem").Hash;
|
|
442
|
+
type: "eip4844";
|
|
440
443
|
yParity: number;
|
|
441
|
-
from: Address;
|
|
442
444
|
gas: bigint;
|
|
443
|
-
hash: import("viem").Hash;
|
|
444
445
|
nonce: number;
|
|
445
446
|
r: import("viem").Hex;
|
|
446
|
-
to: Address | null;
|
|
447
447
|
typeHex: import("viem").Hex | null;
|
|
448
448
|
v: bigint;
|
|
449
449
|
accessList: import("viem").AccessList;
|
|
@@ -460,15 +460,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
460
460
|
} | {
|
|
461
461
|
input: import("viem").Hex;
|
|
462
462
|
s: import("viem").Hex;
|
|
463
|
-
|
|
463
|
+
from: Address;
|
|
464
|
+
to: Address | null;
|
|
464
465
|
value: bigint;
|
|
466
|
+
hash: import("viem").Hash;
|
|
467
|
+
type: "eip7702";
|
|
465
468
|
yParity: number;
|
|
466
|
-
from: Address;
|
|
467
469
|
gas: bigint;
|
|
468
|
-
hash: import("viem").Hash;
|
|
469
470
|
nonce: number;
|
|
470
471
|
r: import("viem").Hex;
|
|
471
|
-
to: Address | null;
|
|
472
472
|
typeHex: import("viem").Hex | null;
|
|
473
473
|
v: bigint;
|
|
474
474
|
accessList: import("viem").AccessList;
|
|
@@ -510,15 +510,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
510
510
|
getTransaction: <blockTag extends import("viem").BlockTag = "latest">(args: import("viem").GetTransactionParameters<blockTag>) => Promise<{
|
|
511
511
|
input: import("viem").Hex;
|
|
512
512
|
s: import("viem").Hex;
|
|
513
|
-
|
|
513
|
+
from: Address;
|
|
514
|
+
to: Address | null;
|
|
514
515
|
value: bigint;
|
|
516
|
+
hash: import("viem").Hash;
|
|
517
|
+
type: "legacy";
|
|
515
518
|
yParity?: undefined | undefined;
|
|
516
|
-
from: Address;
|
|
517
519
|
gas: bigint;
|
|
518
|
-
hash: import("viem").Hash;
|
|
519
520
|
nonce: number;
|
|
520
521
|
r: import("viem").Hex;
|
|
521
|
-
to: Address | null;
|
|
522
522
|
typeHex: import("viem").Hex | null;
|
|
523
523
|
v: bigint;
|
|
524
524
|
accessList?: undefined | undefined;
|
|
@@ -535,15 +535,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
535
535
|
} | {
|
|
536
536
|
input: import("viem").Hex;
|
|
537
537
|
s: import("viem").Hex;
|
|
538
|
-
|
|
538
|
+
from: Address;
|
|
539
|
+
to: Address | null;
|
|
539
540
|
value: bigint;
|
|
541
|
+
hash: import("viem").Hash;
|
|
542
|
+
type: "eip2930";
|
|
540
543
|
yParity: number;
|
|
541
|
-
from: Address;
|
|
542
544
|
gas: bigint;
|
|
543
|
-
hash: import("viem").Hash;
|
|
544
545
|
nonce: number;
|
|
545
546
|
r: import("viem").Hex;
|
|
546
|
-
to: Address | null;
|
|
547
547
|
typeHex: import("viem").Hex | null;
|
|
548
548
|
v: bigint;
|
|
549
549
|
accessList: import("viem").AccessList;
|
|
@@ -560,15 +560,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
560
560
|
} | {
|
|
561
561
|
input: import("viem").Hex;
|
|
562
562
|
s: import("viem").Hex;
|
|
563
|
-
|
|
563
|
+
from: Address;
|
|
564
|
+
to: Address | null;
|
|
564
565
|
value: bigint;
|
|
566
|
+
hash: import("viem").Hash;
|
|
567
|
+
type: "eip1559";
|
|
565
568
|
yParity: number;
|
|
566
|
-
from: Address;
|
|
567
569
|
gas: bigint;
|
|
568
|
-
hash: import("viem").Hash;
|
|
569
570
|
nonce: number;
|
|
570
571
|
r: import("viem").Hex;
|
|
571
|
-
to: Address | null;
|
|
572
572
|
typeHex: import("viem").Hex | null;
|
|
573
573
|
v: bigint;
|
|
574
574
|
accessList: import("viem").AccessList;
|
|
@@ -585,15 +585,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
585
585
|
} | {
|
|
586
586
|
input: import("viem").Hex;
|
|
587
587
|
s: import("viem").Hex;
|
|
588
|
-
|
|
588
|
+
from: Address;
|
|
589
|
+
to: Address | null;
|
|
589
590
|
value: bigint;
|
|
591
|
+
hash: import("viem").Hash;
|
|
592
|
+
type: "eip4844";
|
|
590
593
|
yParity: number;
|
|
591
|
-
from: Address;
|
|
592
594
|
gas: bigint;
|
|
593
|
-
hash: import("viem").Hash;
|
|
594
595
|
nonce: number;
|
|
595
596
|
r: import("viem").Hex;
|
|
596
|
-
to: Address | null;
|
|
597
597
|
typeHex: import("viem").Hex | null;
|
|
598
598
|
v: bigint;
|
|
599
599
|
accessList: import("viem").AccessList;
|
|
@@ -610,15 +610,15 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
|
|
|
610
610
|
} | {
|
|
611
611
|
input: import("viem").Hex;
|
|
612
612
|
s: import("viem").Hex;
|
|
613
|
-
|
|
613
|
+
from: Address;
|
|
614
|
+
to: Address | null;
|
|
614
615
|
value: bigint;
|
|
616
|
+
hash: import("viem").Hash;
|
|
617
|
+
type: "eip7702";
|
|
615
618
|
yParity: number;
|
|
616
|
-
from: Address;
|
|
617
619
|
gas: bigint;
|
|
618
|
-
hash: import("viem").Hash;
|
|
619
620
|
nonce: number;
|
|
620
621
|
r: import("viem").Hex;
|
|
621
|
-
to: Address | null;
|
|
622
622
|
typeHex: import("viem").Hex | null;
|
|
623
623
|
v: bigint;
|
|
624
624
|
accessList: import("viem").AccessList;
|
package/dist/src/hooks.js
CHANGED
|
@@ -9,10 +9,10 @@ import { toAccount } from 'viem/accounts';
|
|
|
9
9
|
import { Connection as SolanaConnection, Transaction as SolanaTransaction, SystemProgram, PublicKey, LAMPORTS_PER_SOL, TransactionInstruction, } from '@solana/web3.js';
|
|
10
10
|
import { SolanaRelayer } from '@abstraxn/solana-relayer';
|
|
11
11
|
import { useWalletClient as useWagmiWalletClient, useAccount, useConfig, useChainId as useWagmiChainId, useSwitchChain as useWagmiSwitchChain, useSignMessage as useWagmiSignMessage, useReconnect as useWagmiReconnect } from 'wagmi';
|
|
12
|
-
import { getWalletClient, switchChain } from '@wagmi/core';
|
|
12
|
+
import { getWalletClient, switchChain, getPublicClient } from '@wagmi/core';
|
|
13
13
|
import { getConnectorMeta } from './connectors';
|
|
14
14
|
import { getChainById } from './chains';
|
|
15
|
-
import { waitUntilDocumentVisible } from './walletConnectMobile';
|
|
15
|
+
import { waitUntilDocumentVisible, pollForOutgoingTx, clearWalletConnectPendingState, isWalletConnectConnector, isMobileDevice, } from './walletConnectMobile';
|
|
16
16
|
/**
|
|
17
17
|
* Hook to check if wallet is connected
|
|
18
18
|
*/
|
|
@@ -2430,69 +2430,129 @@ export function useWriteContract() {
|
|
|
2430
2430
|
}
|
|
2431
2431
|
// Cast to any to avoid type issues with different viem versions
|
|
2432
2432
|
const walletClientAny = activeClient;
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2433
|
+
const fromAddress = account ||
|
|
2434
|
+
walletClientAny?.account?.address;
|
|
2435
|
+
const resolvedGas = gas !== undefined ? gas : gasLimit !== undefined ? gasLimit : undefined;
|
|
2436
|
+
const sendWrite = async () => {
|
|
2437
|
+
// Try using writeContract method directly if available
|
|
2438
|
+
if (walletClientAny && typeof walletClientAny.writeContract === 'function') {
|
|
2439
|
+
return walletClientAny.writeContract({
|
|
2440
|
+
address,
|
|
2441
|
+
abi: abi,
|
|
2442
|
+
functionName: functionName,
|
|
2443
|
+
args: args,
|
|
2444
|
+
value,
|
|
2445
|
+
account,
|
|
2446
|
+
gas: resolvedGas,
|
|
2447
|
+
gasPrice,
|
|
2448
|
+
maxFeePerGas,
|
|
2449
|
+
maxPriorityFeePerGas,
|
|
2450
|
+
nonce,
|
|
2451
|
+
chain: null, // Let the client determine the chain (or use current)
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
// If writeContract is not available, use getContract to create a contract instance
|
|
2455
|
+
// This works with wagmi's connectorClient
|
|
2456
|
+
const contract = getContract({
|
|
2436
2457
|
address,
|
|
2458
|
+
abi: abi,
|
|
2459
|
+
client: walletClientAny,
|
|
2460
|
+
});
|
|
2461
|
+
// Get the write function from the contract
|
|
2462
|
+
const writeFunction = contract.write?.[functionName];
|
|
2463
|
+
if (!writeFunction || typeof writeFunction !== 'function') {
|
|
2464
|
+
throw new Error(`Function "${functionName}" not found in contract ABI or is not a write function. ` +
|
|
2465
|
+
'Make sure the function exists in the ABI and is a state-changing function.');
|
|
2466
|
+
}
|
|
2467
|
+
// Prepare the call options (viem contract write methods accept options as the last parameter)
|
|
2468
|
+
const hasOptions = value !== undefined || account !== undefined || resolvedGas !== undefined ||
|
|
2469
|
+
gasPrice !== undefined || maxFeePerGas !== undefined ||
|
|
2470
|
+
maxPriorityFeePerGas !== undefined || nonce !== undefined;
|
|
2471
|
+
// Viem contract write methods expect args as an array, not spread
|
|
2472
|
+
const functionArgs = args || [];
|
|
2473
|
+
if (hasOptions) {
|
|
2474
|
+
const callOptions = {};
|
|
2475
|
+
if (value !== undefined)
|
|
2476
|
+
callOptions.value = value;
|
|
2477
|
+
if (account !== undefined)
|
|
2478
|
+
callOptions.account = account;
|
|
2479
|
+
if (resolvedGas !== undefined)
|
|
2480
|
+
callOptions.gas = resolvedGas;
|
|
2481
|
+
if (gasPrice !== undefined)
|
|
2482
|
+
callOptions.gasPrice = gasPrice;
|
|
2483
|
+
if (maxFeePerGas !== undefined)
|
|
2484
|
+
callOptions.maxFeePerGas = maxFeePerGas;
|
|
2485
|
+
if (maxPriorityFeePerGas !== undefined)
|
|
2486
|
+
callOptions.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
|
2487
|
+
if (nonce !== undefined)
|
|
2488
|
+
callOptions.nonce = nonce;
|
|
2489
|
+
return writeFunction(functionArgs, callOptions);
|
|
2490
|
+
}
|
|
2491
|
+
return writeFunction(functionArgs);
|
|
2492
|
+
};
|
|
2493
|
+
// Mobile WalletConnect: MetaMask confirms on-chain but eth_sendTransaction
|
|
2494
|
+
// never resolves if the relay WS died while the browser was backgrounded.
|
|
2495
|
+
// Only race an RPC scan in that scenario — not for desktop / injected wallets.
|
|
2496
|
+
const publicClient = getPublicClient(config, {
|
|
2497
|
+
chainId: currentChainId,
|
|
2498
|
+
});
|
|
2499
|
+
const documentHidden = typeof document !== "undefined" &&
|
|
2500
|
+
document.visibilityState === "hidden";
|
|
2501
|
+
const shouldPollForWcHang = isWalletConnectConnector(connector) &&
|
|
2502
|
+
(isMobileDevice() || documentHidden);
|
|
2503
|
+
if (!shouldPollForWcHang || !publicClient || !fromAddress) {
|
|
2504
|
+
return sendWrite();
|
|
2505
|
+
}
|
|
2506
|
+
let startBlock = 0n;
|
|
2507
|
+
try {
|
|
2508
|
+
startBlock = await publicClient.getBlockNumber();
|
|
2509
|
+
}
|
|
2510
|
+
catch {
|
|
2511
|
+
// ignore — poll will use a recent window
|
|
2512
|
+
}
|
|
2513
|
+
let dataPrefix;
|
|
2514
|
+
try {
|
|
2515
|
+
const data = encodeFunctionData({
|
|
2437
2516
|
abi: abi,
|
|
2438
2517
|
functionName: functionName,
|
|
2439
2518
|
args: args,
|
|
2440
|
-
value,
|
|
2441
|
-
account,
|
|
2442
|
-
gas,
|
|
2443
|
-
gasPrice,
|
|
2444
|
-
maxFeePerGas,
|
|
2445
|
-
maxPriorityFeePerGas,
|
|
2446
|
-
nonce,
|
|
2447
|
-
chain: null, // Let the client determine the chain (or use current)
|
|
2448
2519
|
});
|
|
2520
|
+
dataPrefix = data.slice(0, 10);
|
|
2449
2521
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2522
|
+
catch {
|
|
2523
|
+
// Matching without selector is still useful (from + to).
|
|
2524
|
+
}
|
|
2525
|
+
const cancelPoll = { cancelled: false };
|
|
2526
|
+
const sendPromise = sendWrite();
|
|
2527
|
+
const pollPromise = pollForOutgoingTx({
|
|
2528
|
+
publicClient,
|
|
2529
|
+
from: fromAddress,
|
|
2530
|
+
to: address,
|
|
2531
|
+
dataPrefix,
|
|
2532
|
+
value,
|
|
2533
|
+
startBlock,
|
|
2534
|
+
signal: cancelPoll,
|
|
2535
|
+
initialDelayMs: 10_000,
|
|
2536
|
+
timeoutMs: 90_000,
|
|
2456
2537
|
});
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2538
|
+
try {
|
|
2539
|
+
const hash = await Promise.race([sendPromise, pollPromise]);
|
|
2540
|
+
// Poll may win while WC is still hanging — ignore a late reject.
|
|
2541
|
+
void sendPromise.then(() => undefined, () => undefined);
|
|
2542
|
+
return hash;
|
|
2462
2543
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
if (value !== undefined)
|
|
2472
|
-
callOptions.value = value;
|
|
2473
|
-
if (account !== undefined)
|
|
2474
|
-
callOptions.account = account;
|
|
2475
|
-
// `gas` in viem is the gas *limit*. Allow either `gas` or `gasLimit` from the hook caller.
|
|
2476
|
-
if (gas !== undefined) {
|
|
2477
|
-
callOptions.gas = gas;
|
|
2544
|
+
finally {
|
|
2545
|
+
cancelPoll.cancelled = true;
|
|
2546
|
+
try {
|
|
2547
|
+
const getProvider = connector?.getProvider;
|
|
2548
|
+
if (typeof getProvider === 'function') {
|
|
2549
|
+
const provider = await getProvider();
|
|
2550
|
+
clearWalletConnectPendingState(provider);
|
|
2551
|
+
}
|
|
2478
2552
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2553
|
+
catch {
|
|
2554
|
+
// ignore
|
|
2481
2555
|
}
|
|
2482
|
-
if (gasPrice !== undefined)
|
|
2483
|
-
callOptions.gasPrice = gasPrice;
|
|
2484
|
-
if (maxFeePerGas !== undefined)
|
|
2485
|
-
callOptions.maxFeePerGas = maxFeePerGas;
|
|
2486
|
-
if (maxPriorityFeePerGas !== undefined)
|
|
2487
|
-
callOptions.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
|
2488
|
-
if (nonce !== undefined)
|
|
2489
|
-
callOptions.nonce = nonce;
|
|
2490
|
-
// Call the write function with args array and options object
|
|
2491
|
-
return writeFunction(functionArgs, callOptions);
|
|
2492
|
-
}
|
|
2493
|
-
else {
|
|
2494
|
-
// Call the write function with args array only
|
|
2495
|
-
return writeFunction(functionArgs);
|
|
2496
2556
|
}
|
|
2497
2557
|
})();
|
|
2498
2558
|
}, [hookClient, isPending, isError, error, isConnected, config, currentChainId, reconnect, connector]);
|
package/dist/src/index.d.ts
CHANGED
|
@@ -16,3 +16,4 @@ export type { OnboardingUIComponentProps, OnboardingUIComponentRef } from './Onb
|
|
|
16
16
|
export { ALL_CHAINS, EVM_CHAINS, SOLANA_CHAINS, getChainById, getChainsByType, getDefaultChains, toCoreChain, type ChainData, } from './chains';
|
|
17
17
|
export { CONNECTORS, getConnectorMeta, isOAuthConnector, isWalletConnector, isAuthConnector, type ConnectorType, type ConnectorMeta, } from './connectors';
|
|
18
18
|
export { createWagmiConfig } from './wagmiConfig';
|
|
19
|
+
export { pollForOutgoingTx, clearWalletConnectPendingState, waitUntilDocumentVisible, isWalletConnectConnector, isMobileDevice, } from './walletConnectMobile';
|
package/dist/src/index.js
CHANGED
|
@@ -11,4 +11,5 @@ export { OnboardingUIComponent as OnboardingUI } from './OnboardingUI';
|
|
|
11
11
|
export { ALL_CHAINS, EVM_CHAINS, SOLANA_CHAINS, getChainById, getChainsByType, getDefaultChains, toCoreChain, } from './chains';
|
|
12
12
|
export { CONNECTORS, getConnectorMeta, isOAuthConnector, isWalletConnector, isAuthConnector, } from './connectors';
|
|
13
13
|
export { createWagmiConfig } from './wagmiConfig';
|
|
14
|
+
export { pollForOutgoingTx, clearWalletConnectPendingState, waitUntilDocumentVisible, isWalletConnectConnector, isMobileDevice, } from './walletConnectMobile';
|
|
14
15
|
//# sourceMappingURL=index.js.map
|
|
@@ -34,6 +34,34 @@ export declare function getDappReturnUrl(): string;
|
|
|
34
34
|
* navigating Safari to a new URL (which reloads and drops in-memory state).
|
|
35
35
|
*/
|
|
36
36
|
export declare function getSourceBrowserNativeRedirect(): string | undefined;
|
|
37
|
+
export declare function isMobileDevice(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Undo the inline `display: none !important` we apply after connect so the
|
|
40
|
+
* WalletConnect modal can show "Continue in wallet" and process the response.
|
|
41
|
+
* Prefer hideWalletConnectModal() once a session already exists — restoring
|
|
42
|
+
* during a later tx reopens the "All Wallets" picker on top of MetaMask.
|
|
43
|
+
*/
|
|
44
|
+
export declare function restoreWalletConnectModal(): void;
|
|
45
|
+
/** Force-close / hide Reown AppKit + WalletConnect modal DOM after connect or tx. */
|
|
46
|
+
export declare function hideWalletConnectModal(provider?: any): void;
|
|
47
|
+
/**
|
|
48
|
+
* Open the already-connected wallet app so the user sees the confirmation UI.
|
|
49
|
+
* WalletConnect often sends the session_request but does not deeplink on a
|
|
50
|
+
* later tx if the QR modal is hidden — Create Proposal then sits on a loader.
|
|
51
|
+
*
|
|
52
|
+
* When a session already exists, do NOT reopen the WC "All Wallets" modal —
|
|
53
|
+
* that stacks under the iOS "Open in MetaMask?" prompt and stays after confirm.
|
|
54
|
+
*
|
|
55
|
+
* Returns false if skipped (already backgrounded / already opened once).
|
|
56
|
+
*/
|
|
57
|
+
export declare function openConnectedWalletApp(provider: any): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* At most one MetaMask deeplink per approval request.
|
|
60
|
+
* Extra retries queue iOS "Open in MetaMask?" prompts that stay on screen
|
|
61
|
+
* after the tx is already confirmed (JS cannot dismiss that system dialog).
|
|
62
|
+
* Cancel remaining work as soon as the tab is backgrounded (user opened MM).
|
|
63
|
+
*/
|
|
64
|
+
export declare function scheduleOpenConnectedWalletApp(provider: any, delaysMs?: number[]): () => void;
|
|
37
65
|
/**
|
|
38
66
|
* Lazy metadata so url/redirect are read at connect/request time, not at
|
|
39
67
|
* provider mount (which is often the homepage in Next.js layouts).
|
|
@@ -75,10 +103,36 @@ export declare function resumeWalletConnectRelayer(provider: any, options?: {
|
|
|
75
103
|
/** Resolves when the dapp tab is in the foreground again (mobile wallet return). */
|
|
76
104
|
export declare function waitUntilDocumentVisible(): Promise<void>;
|
|
77
105
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
106
|
+
* Clear mobile WC pending flags after we recover the tx hash from chain
|
|
107
|
+
* (poll won while eth_sendTransaction is still hanging).
|
|
80
108
|
*/
|
|
81
|
-
export declare function
|
|
109
|
+
export declare function clearWalletConnectPendingState(provider: any): void;
|
|
110
|
+
/**
|
|
111
|
+
* When WalletConnect never returns eth_sendTransaction after MetaMask approve,
|
|
112
|
+
* scan recent blocks for a matching outgoing tx (from/to + optional calldata prefix).
|
|
113
|
+
*/
|
|
114
|
+
export declare function pollForOutgoingTx(params: {
|
|
115
|
+
publicClient: {
|
|
116
|
+
getBlockNumber: () => Promise<bigint>;
|
|
117
|
+
getBlock: (args: {
|
|
118
|
+
blockNumber: bigint;
|
|
119
|
+
includeTransactions: boolean;
|
|
120
|
+
}) => Promise<{
|
|
121
|
+
transactions?: unknown[];
|
|
122
|
+
} | null>;
|
|
123
|
+
};
|
|
124
|
+
from: string;
|
|
125
|
+
to: string;
|
|
126
|
+
/** Match tx.input prefix (e.g. 0x + 4-byte selector). */
|
|
127
|
+
dataPrefix?: string;
|
|
128
|
+
value?: bigint;
|
|
129
|
+
startBlock: bigint;
|
|
130
|
+
signal: {
|
|
131
|
+
cancelled: boolean;
|
|
132
|
+
};
|
|
133
|
+
timeoutMs?: number;
|
|
134
|
+
initialDelayMs?: number;
|
|
135
|
+
}): Promise<`0x${string}`>;
|
|
82
136
|
/**
|
|
83
137
|
* Patch the EIP-1193 provider so signing/tx requests resume the relay first.
|
|
84
138
|
* Covers wagmi hooks, viem walletClient, and direct provider.request calls.
|
|
@@ -123,6 +123,214 @@ export function getSourceBrowserNativeRedirect() {
|
|
|
123
123
|
return "googlechrome://";
|
|
124
124
|
return undefined;
|
|
125
125
|
}
|
|
126
|
+
function isAndroid() {
|
|
127
|
+
if (typeof navigator === "undefined")
|
|
128
|
+
return false;
|
|
129
|
+
return /Android/i.test(navigator.userAgent || "");
|
|
130
|
+
}
|
|
131
|
+
export function isMobileDevice() {
|
|
132
|
+
return isIOS() || isAndroid();
|
|
133
|
+
}
|
|
134
|
+
function isWalletHttpDeepLink(url) {
|
|
135
|
+
try {
|
|
136
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
137
|
+
return (host.includes("metamask.app.link") ||
|
|
138
|
+
host.includes("link.metamask.io") ||
|
|
139
|
+
host.includes("rainbow.me") ||
|
|
140
|
+
host.includes("link.trustwallet.com") ||
|
|
141
|
+
host.includes("walletconnect.com"));
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function canOpenAsWalletApp(url) {
|
|
148
|
+
if (!url)
|
|
149
|
+
return false;
|
|
150
|
+
if (/^https?:\/\//i.test(url))
|
|
151
|
+
return isWalletHttpDeepLink(url);
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
function navigateToWalletDeepLink(target) {
|
|
155
|
+
if (!target || typeof window === "undefined")
|
|
156
|
+
return;
|
|
157
|
+
try {
|
|
158
|
+
// Prefer an invisible <a> click — more reliable than location.href behind
|
|
159
|
+
// iOS "open another application?" prompts and after a prior soft navigation.
|
|
160
|
+
const a = document.createElement("a");
|
|
161
|
+
a.href = target;
|
|
162
|
+
a.rel = "noreferrer";
|
|
163
|
+
a.style.display = "none";
|
|
164
|
+
document.body.appendChild(a);
|
|
165
|
+
a.click();
|
|
166
|
+
a.remove();
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
try {
|
|
170
|
+
window.location.href = target;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Ignore
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Undo the inline `display: none !important` we apply after connect so the
|
|
179
|
+
* WalletConnect modal can show "Continue in wallet" and process the response.
|
|
180
|
+
* Prefer hideWalletConnectModal() once a session already exists — restoring
|
|
181
|
+
* during a later tx reopens the "All Wallets" picker on top of MetaMask.
|
|
182
|
+
*/
|
|
183
|
+
export function restoreWalletConnectModal() {
|
|
184
|
+
if (typeof document === "undefined")
|
|
185
|
+
return;
|
|
186
|
+
WALLETCONNECT_MODAL_SELECTORS.forEach((sel) => {
|
|
187
|
+
try {
|
|
188
|
+
document.querySelectorAll(sel).forEach((el) => {
|
|
189
|
+
const htmlEl = el;
|
|
190
|
+
if (htmlEl?.style) {
|
|
191
|
+
htmlEl.style.removeProperty("display");
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Ignore invalid selectors in older browsers.
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/** Force-close / hide Reown AppKit + WalletConnect modal DOM after connect or tx. */
|
|
201
|
+
export function hideWalletConnectModal(provider) {
|
|
202
|
+
try {
|
|
203
|
+
if (typeof provider?.modal?.close === "function") {
|
|
204
|
+
provider.modal.close();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Ignore
|
|
209
|
+
}
|
|
210
|
+
if (typeof document === "undefined")
|
|
211
|
+
return;
|
|
212
|
+
WALLETCONNECT_MODAL_SELECTORS.forEach((sel) => {
|
|
213
|
+
try {
|
|
214
|
+
document.querySelectorAll(sel).forEach((el) => {
|
|
215
|
+
try {
|
|
216
|
+
if (typeof el.close === "function") {
|
|
217
|
+
el.close();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Ignore
|
|
222
|
+
}
|
|
223
|
+
const htmlEl = el;
|
|
224
|
+
if (htmlEl?.style) {
|
|
225
|
+
htmlEl.style.setProperty("display", "none", "important");
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// Ignore invalid selectors
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Open the already-connected wallet app so the user sees the confirmation UI.
|
|
236
|
+
* WalletConnect often sends the session_request but does not deeplink on a
|
|
237
|
+
* later tx if the QR modal is hidden — Create Proposal then sits on a loader.
|
|
238
|
+
*
|
|
239
|
+
* When a session already exists, do NOT reopen the WC "All Wallets" modal —
|
|
240
|
+
* that stacks under the iOS "Open in MetaMask?" prompt and stays after confirm.
|
|
241
|
+
*
|
|
242
|
+
* Returns false if skipped (already backgrounded / already opened once).
|
|
243
|
+
*/
|
|
244
|
+
export function openConnectedWalletApp(provider) {
|
|
245
|
+
if (typeof window === "undefined" || !isMobileDevice())
|
|
246
|
+
return false;
|
|
247
|
+
// Never queue another metamask:// while the tab is already leaving / hidden —
|
|
248
|
+
// those become stuck "Open in MetaMask?" dialogs after the user returns.
|
|
249
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
if (provider?.__abstraxnWcDeeplinkOpened) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const hasSession = hasActiveWalletConnectSession(provider);
|
|
256
|
+
// Only touch the WC modal when there is no session yet (connect flow).
|
|
257
|
+
// For later txs, deeplink straight into the peer wallet app.
|
|
258
|
+
if (!hasSession) {
|
|
259
|
+
restoreWalletConnectModal();
|
|
260
|
+
try {
|
|
261
|
+
if (typeof provider?.modal?.open === "function") {
|
|
262
|
+
void provider.modal.open();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Modal may not exist after we force-hid it post-connect.
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// Keep the picker closed while MetaMask opens.
|
|
271
|
+
hideWalletConnectModal(provider);
|
|
272
|
+
}
|
|
273
|
+
const peer = provider?.session?.peer?.metadata ??
|
|
274
|
+
provider?.signer?.session?.peer?.metadata;
|
|
275
|
+
const native = String(peer?.redirect?.native || "");
|
|
276
|
+
const universal = String(peer?.redirect?.universal || "");
|
|
277
|
+
const name = String(peer?.name || "").toLowerCase();
|
|
278
|
+
const candidates = [];
|
|
279
|
+
if (canOpenAsWalletApp(native))
|
|
280
|
+
candidates.push(native);
|
|
281
|
+
if (canOpenAsWalletApp(universal))
|
|
282
|
+
candidates.push(universal);
|
|
283
|
+
if (name.includes("metamask") || !name) {
|
|
284
|
+
candidates.push(isIOS() ? "metamask://" : "https://metamask.app.link/wc", "https://metamask.app.link/wc");
|
|
285
|
+
}
|
|
286
|
+
else if (name.includes("rainbow")) {
|
|
287
|
+
candidates.push("rainbow://");
|
|
288
|
+
}
|
|
289
|
+
else if (name.includes("trust")) {
|
|
290
|
+
candidates.push("trust://");
|
|
291
|
+
}
|
|
292
|
+
const unique = [...new Set(candidates.filter(Boolean))];
|
|
293
|
+
if (!unique.length)
|
|
294
|
+
return false;
|
|
295
|
+
provider.__abstraxnWcDeeplinkOpened = true;
|
|
296
|
+
navigateToWalletDeepLink(unique[0]);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* At most one MetaMask deeplink per approval request.
|
|
301
|
+
* Extra retries queue iOS "Open in MetaMask?" prompts that stay on screen
|
|
302
|
+
* after the tx is already confirmed (JS cannot dismiss that system dialog).
|
|
303
|
+
* Cancel remaining work as soon as the tab is backgrounded (user opened MM).
|
|
304
|
+
*/
|
|
305
|
+
export function scheduleOpenConnectedWalletApp(provider, delaysMs = [150]) {
|
|
306
|
+
if (typeof window === "undefined" || !isMobileDevice())
|
|
307
|
+
return () => { };
|
|
308
|
+
provider.__abstraxnWcDeeplinkOpened = false;
|
|
309
|
+
const timers = [];
|
|
310
|
+
let cancelled = false;
|
|
311
|
+
const cancel = () => {
|
|
312
|
+
cancelled = true;
|
|
313
|
+
timers.splice(0).forEach((id) => window.clearTimeout(id));
|
|
314
|
+
if (typeof document !== "undefined") {
|
|
315
|
+
document.removeEventListener("visibilitychange", onHidden);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
const onHidden = () => {
|
|
319
|
+
if (document.visibilityState === "hidden") {
|
|
320
|
+
// User is in (or opening) MetaMask — stop any further deeplinks.
|
|
321
|
+
cancel();
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
document.addEventListener("visibilitychange", onHidden);
|
|
325
|
+
delaysMs.forEach((ms) => {
|
|
326
|
+
timers.push(window.setTimeout(() => {
|
|
327
|
+
if (cancelled)
|
|
328
|
+
return;
|
|
329
|
+
openConnectedWalletApp(provider);
|
|
330
|
+
}, ms));
|
|
331
|
+
});
|
|
332
|
+
return cancel;
|
|
333
|
+
}
|
|
126
334
|
function getWalletConnectRedirect() {
|
|
127
335
|
const native = getSourceBrowserNativeRedirect();
|
|
128
336
|
return native ? { native } : undefined;
|
|
@@ -292,16 +500,13 @@ export async function resumeWalletConnectRelayer(provider, options) {
|
|
|
292
500
|
}
|
|
293
501
|
const run = (async () => {
|
|
294
502
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
ping.call(relayer.provider.connection),
|
|
300
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("ping timeout")), 1500)),
|
|
301
|
-
]);
|
|
503
|
+
// New requests must not restart a live session — that races the
|
|
504
|
+
// session_request and MetaMask never shows a confirmation popup.
|
|
505
|
+
if (!options?.force) {
|
|
506
|
+
if (isRelayerConnected(relayer))
|
|
302
507
|
return;
|
|
303
|
-
|
|
304
|
-
|
|
508
|
+
await restartWalletConnectTransport(relayer);
|
|
509
|
+
return;
|
|
305
510
|
}
|
|
306
511
|
await restartWalletConnectTransport(relayer);
|
|
307
512
|
const session = provider?.session ?? provider?.signer?.session;
|
|
@@ -348,25 +553,83 @@ export function waitUntilDocumentVisible() {
|
|
|
348
553
|
});
|
|
349
554
|
}
|
|
350
555
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
556
|
+
* Clear mobile WC pending flags after we recover the tx hash from chain
|
|
557
|
+
* (poll won while eth_sendTransaction is still hanging).
|
|
353
558
|
*/
|
|
354
|
-
export function
|
|
355
|
-
if (
|
|
559
|
+
export function clearWalletConnectPendingState(provider) {
|
|
560
|
+
if (!provider)
|
|
356
561
|
return;
|
|
357
|
-
|
|
562
|
+
try {
|
|
563
|
+
provider[PENDING_FLAG] = 0;
|
|
564
|
+
provider[NEEDS_RESUME_FLAG] = false;
|
|
565
|
+
provider.__abstraxnWcDeeplinkOpened = true;
|
|
566
|
+
hideWalletConnectModal(provider);
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
// ignore
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* When WalletConnect never returns eth_sendTransaction after MetaMask approve,
|
|
574
|
+
* scan recent blocks for a matching outgoing tx (from/to + optional calldata prefix).
|
|
575
|
+
*/
|
|
576
|
+
export async function pollForOutgoingTx(params) {
|
|
577
|
+
const { publicClient, from, to, dataPrefix, value, startBlock, signal, timeoutMs = 90_000, initialDelayMs = 10_000, } = params;
|
|
578
|
+
if (initialDelayMs > 0) {
|
|
579
|
+
await new Promise((r) => setTimeout(r, initialDelayMs));
|
|
580
|
+
}
|
|
581
|
+
if (signal.cancelled) {
|
|
582
|
+
throw new Error("cancelled");
|
|
583
|
+
}
|
|
584
|
+
await waitUntilDocumentVisible();
|
|
585
|
+
const fromLc = from.toLowerCase();
|
|
586
|
+
const toLc = to.toLowerCase();
|
|
587
|
+
const prefixLc = dataPrefix ? dataPrefix.toLowerCase() : undefined;
|
|
588
|
+
const started = Date.now();
|
|
589
|
+
while (!signal.cancelled && Date.now() - started < timeoutMs) {
|
|
358
590
|
try {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
591
|
+
const latest = await publicClient.getBlockNumber();
|
|
592
|
+
const fromBlock = startBlock > 0n
|
|
593
|
+
? startBlock
|
|
594
|
+
: latest > 12n
|
|
595
|
+
? latest - 12n
|
|
596
|
+
: 0n;
|
|
597
|
+
for (let b = fromBlock; b <= latest; b++) {
|
|
598
|
+
if (signal.cancelled)
|
|
599
|
+
break;
|
|
600
|
+
const block = await publicClient.getBlock({
|
|
601
|
+
blockNumber: b,
|
|
602
|
+
includeTransactions: true,
|
|
603
|
+
});
|
|
604
|
+
const txs = (block?.transactions || []);
|
|
605
|
+
for (const tx of txs) {
|
|
606
|
+
if (!tx || typeof tx === "string")
|
|
607
|
+
continue;
|
|
608
|
+
if (String(tx.from || "").toLowerCase() !== fromLc)
|
|
609
|
+
continue;
|
|
610
|
+
if (String(tx.to || "").toLowerCase() !== toLc)
|
|
611
|
+
continue;
|
|
612
|
+
if (value !== undefined && BigInt(tx.value ?? 0) !== value) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (prefixLc) {
|
|
616
|
+
const input = String(tx.input || tx.data || "").toLowerCase();
|
|
617
|
+
if (!input.startsWith(prefixLc))
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
const hash = tx.hash;
|
|
621
|
+
if (typeof hash === "string" && hash.startsWith("0x")) {
|
|
622
|
+
return hash;
|
|
623
|
+
}
|
|
363
624
|
}
|
|
364
|
-
}
|
|
625
|
+
}
|
|
365
626
|
}
|
|
366
627
|
catch {
|
|
367
|
-
//
|
|
628
|
+
// RPC blip — keep polling
|
|
368
629
|
}
|
|
369
|
-
|
|
630
|
+
await new Promise((r) => setTimeout(r, 2_000));
|
|
631
|
+
}
|
|
632
|
+
throw new Error("No WalletConnect response and no matching on-chain transaction found. If MetaMask showed confirmed, refresh and check the explorer.");
|
|
370
633
|
}
|
|
371
634
|
function getRequestMethod(args) {
|
|
372
635
|
const payload = args[0];
|
|
@@ -396,20 +659,63 @@ export function patchWalletConnectProviderForMobile(provider) {
|
|
|
396
659
|
if (!isApproval && !wasBackgrounded) {
|
|
397
660
|
return originalRequest(...args);
|
|
398
661
|
}
|
|
662
|
+
if (!isApproval && wasBackgrounded) {
|
|
663
|
+
try {
|
|
664
|
+
await resumeWalletConnectRelayer(provider, { force: true });
|
|
665
|
+
}
|
|
666
|
+
finally {
|
|
667
|
+
provider[NEEDS_RESUME_FLAG] = false;
|
|
668
|
+
}
|
|
669
|
+
return originalRequest(...args);
|
|
670
|
+
}
|
|
671
|
+
let cancelDeeplinks;
|
|
399
672
|
if (isApproval) {
|
|
400
673
|
provider[PENDING_FLAG] = (provider[PENDING_FLAG] || 0) + 1;
|
|
401
|
-
|
|
674
|
+
// Already connected: never re-open "All Wallets". Only restore modal
|
|
675
|
+
// during connect (no session yet).
|
|
676
|
+
if (hasActiveWalletConnectSession(provider)) {
|
|
677
|
+
hideWalletConnectModal(provider);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
restoreWalletConnectModal();
|
|
681
|
+
}
|
|
402
682
|
syncWalletConnectDappRedirect(provider);
|
|
683
|
+
// Mark before deeplink so a fast backgrounding still triggers force-resume.
|
|
684
|
+
provider[NEEDS_RESUME_FLAG] = true;
|
|
403
685
|
}
|
|
404
686
|
try {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
687
|
+
// Never force-restart right before sending a new tx — that is what
|
|
688
|
+
// blocked the MetaMask popup on Create Proposal after 3.3.7.
|
|
689
|
+
await Promise.race([
|
|
690
|
+
resumeWalletConnectRelayer(provider, { force: false }),
|
|
691
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
692
|
+
]);
|
|
693
|
+
const resultPromise = originalRequest(...args);
|
|
694
|
+
if (isApproval) {
|
|
695
|
+
// Do NOT auto-deeplink when a session already exists.
|
|
696
|
+
// WC ethereum-provider already opens the peer wallet; our extra
|
|
697
|
+
// metamask:// calls queue iOS "Open in MetaMask?" dialogs that stay
|
|
698
|
+
// on screen after the tx is confirmed (JS cannot dismiss them).
|
|
699
|
+
// Connect flow (no session) may still use a single scheduled open.
|
|
700
|
+
if (!hasActiveWalletConnectSession(provider)) {
|
|
701
|
+
cancelDeeplinks = scheduleOpenConnectedWalletApp(provider);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
hideWalletConnectModal(provider);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return await resultPromise;
|
|
409
708
|
}
|
|
410
709
|
finally {
|
|
710
|
+
cancelDeeplinks?.();
|
|
411
711
|
if (isApproval) {
|
|
412
712
|
provider[PENDING_FLAG] = Math.max(0, (provider[PENDING_FLAG] || 1) - 1);
|
|
713
|
+
provider.__abstraxnWcDeeplinkOpened = false;
|
|
714
|
+
// Close picker that may have been left open after approve/reject.
|
|
715
|
+
hideWalletConnectModal(provider);
|
|
716
|
+
if (!hasPendingWalletConnectApproval(provider)) {
|
|
717
|
+
provider[NEEDS_RESUME_FLAG] = false;
|
|
718
|
+
}
|
|
413
719
|
}
|
|
414
720
|
}
|
|
415
721
|
};
|
|
@@ -421,24 +727,44 @@ export function patchWalletConnectProviderForMobile(provider) {
|
|
|
421
727
|
export function subscribeWalletConnectForegroundResume(provider) {
|
|
422
728
|
if (typeof window === "undefined" || !provider)
|
|
423
729
|
return () => { };
|
|
424
|
-
|
|
425
|
-
let hiddenAt = 0;
|
|
730
|
+
const timers = [];
|
|
426
731
|
const resume = () => {
|
|
427
|
-
|
|
428
|
-
clearTimeout(timer);
|
|
429
|
-
const hiddenMs = hiddenAt ? Date.now() - hiddenAt : 0;
|
|
732
|
+
timers.splice(0).forEach((t) => clearTimeout(t));
|
|
430
733
|
const pending = hasPendingWalletConnectApproval(provider);
|
|
431
|
-
const
|
|
432
|
-
|
|
734
|
+
const needsResume = pending || !!provider[NEEDS_RESUME_FLAG];
|
|
735
|
+
// Do not restart after connect-only backgrounding — that broke the next
|
|
736
|
+
// session_request so MetaMask never opened for Create Proposal.
|
|
737
|
+
if (!needsResume) {
|
|
433
738
|
clearWalletReturnIfSameDocument();
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
// iOS often reports "visible" before networking is ready; retry force-resume.
|
|
742
|
+
const delays = [50, 300, 1000, 2000, 4000];
|
|
743
|
+
delays.forEach((ms) => {
|
|
744
|
+
timers.push(setTimeout(() => {
|
|
745
|
+
if (!hasPendingWalletConnectApproval(provider) &&
|
|
746
|
+
!provider[NEEDS_RESUME_FLAG]) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
clearWalletReturnIfSameDocument();
|
|
750
|
+
void resumeWalletConnectRelayer(provider, { force: true }).then(() => {
|
|
751
|
+
if (!hasPendingWalletConnectApproval(provider)) {
|
|
752
|
+
provider[NEEDS_RESUME_FLAG] = false;
|
|
753
|
+
hideWalletConnectModal(provider);
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
// Do not restoreWalletConnectModal here — that reopens "All Wallets".
|
|
757
|
+
if (!hasActiveWalletConnectSession(provider)) {
|
|
758
|
+
restoreWalletConnectModal();
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
hideWalletConnectModal(provider);
|
|
762
|
+
}
|
|
763
|
+
}, ms));
|
|
764
|
+
});
|
|
438
765
|
};
|
|
439
766
|
const onVisibility = () => {
|
|
440
767
|
if (document.visibilityState === "hidden") {
|
|
441
|
-
hiddenAt = Date.now();
|
|
442
768
|
if (hasPendingWalletConnectApproval(provider)) {
|
|
443
769
|
provider[NEEDS_RESUME_FLAG] = true;
|
|
444
770
|
}
|
|
@@ -451,8 +777,7 @@ export function subscribeWalletConnectForegroundResume(provider) {
|
|
|
451
777
|
window.addEventListener("pageshow", resume);
|
|
452
778
|
window.addEventListener("focus", resume);
|
|
453
779
|
return () => {
|
|
454
|
-
|
|
455
|
-
clearTimeout(timer);
|
|
780
|
+
timers.splice(0).forEach((t) => clearTimeout(t));
|
|
456
781
|
document.removeEventListener("visibilitychange", onVisibility);
|
|
457
782
|
window.removeEventListener("pageshow", resume);
|
|
458
783
|
window.removeEventListener("focus", resume);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abstraxn/signer-react",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.9",
|
|
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",
|