@abstraxn/signer-react 3.3.4 → 3.3.6

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,11 +5,28 @@ 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.6] - 2026-09-14
9
+
10
+ ### Fixed
11
+ - **WalletConnect mobile approve/reject never returned to the dapp** – After a WalletConnect action deeplinked to MetaMask (or another wallet) on iOS/Android, the browser dropped the relay WebSocket. Approve and reject results never reached the in-flight `signMessage` / `sendTransaction` / `writeContract` promise. The SDK now resumes the WalletConnect relay when the tab becomes visible again and before user-approval requests, and no longer treats a transport drop as a session disconnect.
12
+ - **WalletConnect iOS return opened Safari on the homepage** – Return metadata used `location.origin`, so MetaMask opened Safari at `/` and reloaded the app (lost in-memory UI state). Metadata now uses the current page URL. On iOS Chrome/Firefox/Edge/Brave/Opera, `redirect.native` is the source browser app scheme with no path so the existing tab can be foregrounded. On Safari, `redirect.universal` is omitted so the wallet can return to the same tab instead of navigating to `/`. If a wallet still lands on the homepage, the SDK restores the page the user transacted from.
13
+
14
+ ### Notes
15
+ - Disconnect and reconnect WalletConnect once after upgrading so the wallet stores the new return metadata. Existing sessions keep the old homepage/Safari redirect until re-paired.
16
+
8
17
  ## [3.3.4] - 2026-08-31
9
18
 
19
+ ### Fixed
20
+ - **Passkey export/sign 401** — via `@abstraxn/signer-core@2.1.4`, passkey login
21
+ registers the IndexedDB session key on Turnkey after login (fixes
22
+ `PUBLIC_KEY_NOT_FOUND` on export, sign, and proxy).
23
+
10
24
  ### Changed
11
- - Bumped `@abstraxn/signer-core` to `2.1.4` so passkey login registers the
12
- IndexedDB session key (fixes export/sign `PUBLIC_KEY_NOT_FOUND`).
25
+ - Bumped `@abstraxn/signer-core` to `2.1.4`.
26
+
27
+ ### Notes
28
+ - Deploy **wallet-service** with `POST /auth/passkey/register-session` before upgrading.
29
+ - Users on passkey must **log out and log in again** after upgrade.
13
30
 
14
31
  ## [3.3.3] - 2026-07-09
15
32
 
@@ -10,6 +10,7 @@ import { EVM_CHAINS, toCoreChain } from "../../chains";
10
10
  import { AbstraxnProviderWithWagmi } from "./AbstraxnProviderWithWagmi";
11
11
  import { AbstraxnProviderWithoutWagmi } from "./AbstraxnProviderWithoutWagmi";
12
12
  import { useQueryClientSafe, QueryClientWrapper, canUseReactQuery } from "./utils";
13
+ import { restoreAfterWalletRedirect } from "../../walletConnectMobile";
13
14
  import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
14
15
  import { PhantomWalletAdapter, SolflareWalletAdapter } from "@solana/wallet-adapter-wallets";
15
16
  export function AbstraxnProvider({ config, children }) {
@@ -26,6 +27,9 @@ export function AbstraxnProvider({ config, children }) {
26
27
  const [isHydrated, setIsHydrated] = useState(() => {
27
28
  return typeof window !== 'undefined';
28
29
  });
30
+ useEffect(() => {
31
+ restoreAfterWalletRedirect();
32
+ }, []);
29
33
  useEffect(() => {
30
34
  if (externalSsr && typeof window !== 'undefined' && !isHydrated) {
31
35
  // Wait for wallet injection scripts to run (Phantom, etc.)
@@ -12,6 +12,7 @@ import { ExternalWalletButtons, resetExternalWalletViewState, } from "../../Exte
12
12
  import { EVM_CHAINS, SOLANA_CHAINS, getChainById, toCoreChain, } from "../../chains";
13
13
  import { AbstraxnContext } from "./context";
14
14
  import { isLinkAuthCallback } from "./utils";
15
+ import { hasActiveWalletConnectSession, hasPendingWalletConnectApproval, isWalletConnectConnector, patchWalletConnectProviderForMobile, rememberCurrentPageForWalletReturn, resumeWalletConnectRelayer, subscribeWalletConnectForegroundResume, syncWalletConnectDappRedirect, } from "../../walletConnectMobile";
15
16
  /** Syncs wagmi disconnect to external wallet state; only rendered when wagmi is available (under WagmiProvider). */
16
17
  function WagmiConnectionEffectSync({ onDisconnect, }) {
17
18
  useConnectionEffect({ onDisconnect });
@@ -45,6 +46,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
45
46
  const [emailForOtp, setEmailForOtpState] = useState("");
46
47
  const explicitConnectionRef = useRef(false);
47
48
  const autoDisconnectHandledRef = useRef(false);
49
+ const wcProviderRef = useRef(null);
48
50
  useEffect(() => {
49
51
  isExternalWalletConnectedRef.current = isExternalWalletConnected;
50
52
  }, [isExternalWalletConnected]);
@@ -2555,6 +2557,14 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
2555
2557
  if (chainTypePref === "solana" && solana?.wallet?.connected) {
2556
2558
  return;
2557
2559
  }
2560
+ // Mobile WalletConnect: leaving the browser for MetaMask drops the relay
2561
+ // socket, which can make wagmi look disconnected even though the session
2562
+ // is still valid. Keep UI state and resume the relay instead of resetting.
2563
+ if (!wagmiAccount.isConnected &&
2564
+ hasActiveWalletConnectSession(wcProviderRef.current)) {
2565
+ void resumeWalletConnectRelayer(wcProviderRef.current);
2566
+ return;
2567
+ }
2558
2568
  // Prevent auto-connect (but allow restoration from persistence)
2559
2569
  // Only auto-disconnect if:
2560
2570
  // 1. wagmiAccount is connected
@@ -2664,6 +2674,8 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
2664
2674
  // Close WalletConnect QR modal when connection detected via wagmi (e.g. Next.js hydration)
2665
2675
  if (typeof document !== "undefined") {
2666
2676
  const closeWcModalDom = () => {
2677
+ if (hasPendingWalletConnectApproval(wcProviderRef.current))
2678
+ return;
2667
2679
  ["w3m-modal", "w3m-modal-backdrop", "[data-w3m-modal]", ".w3m-modal", ".w3m-modal-backdrop", "[class*='w3m-modal']", "[class*='walletconnect-qrcode']"].forEach((sel) => {
2668
2680
  try {
2669
2681
  const el = document.querySelector(sel);
@@ -2866,6 +2878,21 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
2866
2878
  });
2867
2879
  }
2868
2880
  try {
2881
+ if (isWalletConnectConnector) {
2882
+ rememberCurrentPageForWalletReturn();
2883
+ try {
2884
+ if (typeof connector.getProvider === "function") {
2885
+ const provider = await connector.getProvider();
2886
+ if (provider) {
2887
+ syncWalletConnectDappRedirect(provider);
2888
+ patchWalletConnectProviderForMobile(provider);
2889
+ }
2890
+ }
2891
+ }
2892
+ catch {
2893
+ // Provider may not exist until after connect.
2894
+ }
2895
+ }
2869
2896
  // Use connectAsync to properly await the connection
2870
2897
  const result = await wagmiConnect.connectAsync({ connector });
2871
2898
  // Use the result directly to set state immediately
@@ -2931,9 +2958,15 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
2931
2958
  connectorName.includes("walletconnect");
2932
2959
  if (isWalletConnectConnector) {
2933
2960
  const closeWalletConnectModal = async () => {
2961
+ if (hasPendingWalletConnectApproval(wcProviderRef.current))
2962
+ return;
2934
2963
  try {
2935
2964
  if (typeof connector.getProvider === "function") {
2936
2965
  const provider = await connector.getProvider();
2966
+ if (provider) {
2967
+ wcProviderRef.current = provider;
2968
+ patchWalletConnectProviderForMobile(provider);
2969
+ }
2937
2970
  if (provider?.modal?.close) {
2938
2971
  provider.modal.close();
2939
2972
  }
@@ -3113,6 +3146,11 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
3113
3146
  const handleWagmiDisconnect = useCallback(() => {
3114
3147
  if (!externalWalletsEnabledRef.current)
3115
3148
  return;
3149
+ // Transport drop (mobile deeplink) is not a session disconnect.
3150
+ if (hasActiveWalletConnectSession(wcProviderRef.current)) {
3151
+ void resumeWalletConnectRelayer(wcProviderRef.current);
3152
+ return;
3153
+ }
3116
3154
  setIsExternalWalletConnected(false);
3117
3155
  setExternalWalletAddress(null);
3118
3156
  setExternalWalletChainId(null);
@@ -3152,6 +3190,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
3152
3190
  }
3153
3191
  const connector = wagmiAccount.connector;
3154
3192
  let mounted = true;
3193
+ let unsubscribeForeground;
3155
3194
  const setup = async () => {
3156
3195
  try {
3157
3196
  const getProvider = connector.getProvider ?? connector.getEthereumProvider;
@@ -3160,15 +3199,39 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
3160
3199
  const p = await getProvider();
3161
3200
  if (!mounted || !p)
3162
3201
  return;
3202
+ const isWc = isWalletConnectConnector(connector);
3203
+ if (isWc) {
3204
+ wcProviderRef.current = p;
3205
+ syncWalletConnectDappRedirect(p);
3206
+ patchWalletConnectProviderForMobile(p);
3207
+ unsubscribeForeground = subscribeWalletConnectForegroundResume(p);
3208
+ if (!mounted) {
3209
+ unsubscribeForeground();
3210
+ unsubscribeForeground = undefined;
3211
+ wcProviderRef.current = null;
3212
+ return;
3213
+ }
3214
+ }
3215
+ else {
3216
+ wcProviderRef.current = null;
3217
+ }
3163
3218
  const onDisconnectFromProvider = () => {
3164
3219
  if (!isExternalWalletConnectedRef.current)
3165
3220
  return;
3221
+ if (hasActiveWalletConnectSession(p)) {
3222
+ void resumeWalletConnectRelayer(p);
3223
+ return;
3224
+ }
3166
3225
  handleWagmiDisconnect();
3167
3226
  };
3168
3227
  const onAccountsChanged = (accounts) => {
3169
3228
  const list = Array.isArray(accounts) ? accounts : [];
3170
- if (list.length === 0)
3229
+ if (list.length === 0) {
3230
+ // Empty accounts during mobile resume is often a transport blip.
3231
+ if (hasActiveWalletConnectSession(p))
3232
+ return;
3171
3233
  onDisconnectFromProvider();
3234
+ }
3172
3235
  };
3173
3236
  p.on?.("accountsChanged", onAccountsChanged);
3174
3237
  p.on?.("disconnect", onDisconnectFromProvider);
@@ -3188,6 +3251,8 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
3188
3251
  });
3189
3252
  return () => {
3190
3253
  mounted = false;
3254
+ unsubscribeForeground?.();
3255
+ wcProviderRef.current = null;
3191
3256
  const cleanup = providerListenerCleanupRef.current;
3192
3257
  providerListenerCleanupRef.current = null;
3193
3258
  if (typeof cleanup === "function")
@@ -358,16 +358,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
358
358
  withdrawals?: import("viem").Withdrawal[] | undefined | undefined;
359
359
  withdrawalsRoot?: `0x${string}` | undefined;
360
360
  transactions: includeTransactions extends true ? ({
361
+ input: import("viem").Hex;
362
+ s: import("viem").Hex;
361
363
  type: "legacy";
362
364
  value: bigint;
363
365
  yParity?: undefined | undefined;
364
366
  from: Address;
365
367
  gas: bigint;
366
368
  hash: import("viem").Hash;
367
- input: import("viem").Hex;
368
369
  nonce: number;
369
370
  r: import("viem").Hex;
370
- s: import("viem").Hex;
371
371
  to: Address | null;
372
372
  typeHex: import("viem").Hex | null;
373
373
  v: bigint;
@@ -383,16 +383,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
383
383
  blockNumber: (blockTag extends "pending" ? true : false) extends infer T_1 ? T_1 extends (blockTag extends "pending" ? true : false) ? T_1 extends true ? null : bigint : never : never;
384
384
  transactionIndex: (blockTag extends "pending" ? true : false) extends infer T_2 ? T_2 extends (blockTag extends "pending" ? true : false) ? T_2 extends true ? null : number : never : never;
385
385
  } | {
386
+ input: import("viem").Hex;
387
+ s: import("viem").Hex;
386
388
  type: "eip2930";
387
389
  value: bigint;
388
390
  yParity: number;
389
391
  from: Address;
390
392
  gas: bigint;
391
393
  hash: import("viem").Hash;
392
- input: import("viem").Hex;
393
394
  nonce: number;
394
395
  r: import("viem").Hex;
395
- s: import("viem").Hex;
396
396
  to: Address | null;
397
397
  typeHex: import("viem").Hex | null;
398
398
  v: bigint;
@@ -408,16 +408,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
408
408
  blockNumber: (blockTag extends "pending" ? true : false) extends infer T_4 ? T_4 extends (blockTag extends "pending" ? true : false) ? T_4 extends true ? null : bigint : never : never;
409
409
  transactionIndex: (blockTag extends "pending" ? true : false) extends infer T_5 ? T_5 extends (blockTag extends "pending" ? true : false) ? T_5 extends true ? null : number : never : never;
410
410
  } | {
411
+ input: import("viem").Hex;
412
+ s: import("viem").Hex;
411
413
  type: "eip1559";
412
414
  value: bigint;
413
415
  yParity: number;
414
416
  from: Address;
415
417
  gas: bigint;
416
418
  hash: import("viem").Hash;
417
- input: import("viem").Hex;
418
419
  nonce: number;
419
420
  r: import("viem").Hex;
420
- s: import("viem").Hex;
421
421
  to: Address | null;
422
422
  typeHex: import("viem").Hex | null;
423
423
  v: bigint;
@@ -433,16 +433,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
433
433
  blockNumber: (blockTag extends "pending" ? true : false) extends infer T_7 ? T_7 extends (blockTag extends "pending" ? true : false) ? T_7 extends true ? null : bigint : never : never;
434
434
  transactionIndex: (blockTag extends "pending" ? true : false) extends infer T_8 ? T_8 extends (blockTag extends "pending" ? true : false) ? T_8 extends true ? null : number : never : never;
435
435
  } | {
436
+ input: import("viem").Hex;
437
+ s: import("viem").Hex;
436
438
  type: "eip4844";
437
439
  value: bigint;
438
440
  yParity: number;
439
441
  from: Address;
440
442
  gas: bigint;
441
443
  hash: import("viem").Hash;
442
- input: import("viem").Hex;
443
444
  nonce: number;
444
445
  r: import("viem").Hex;
445
- s: import("viem").Hex;
446
446
  to: Address | null;
447
447
  typeHex: import("viem").Hex | null;
448
448
  v: bigint;
@@ -458,16 +458,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
458
458
  blockNumber: (blockTag extends "pending" ? true : false) extends infer T_10 ? T_10 extends (blockTag extends "pending" ? true : false) ? T_10 extends true ? null : bigint : never : never;
459
459
  transactionIndex: (blockTag extends "pending" ? true : false) extends infer T_11 ? T_11 extends (blockTag extends "pending" ? true : false) ? T_11 extends true ? null : number : never : never;
460
460
  } | {
461
+ input: import("viem").Hex;
462
+ s: import("viem").Hex;
461
463
  type: "eip7702";
462
464
  value: bigint;
463
465
  yParity: number;
464
466
  from: Address;
465
467
  gas: bigint;
466
468
  hash: import("viem").Hash;
467
- input: import("viem").Hex;
468
469
  nonce: number;
469
470
  r: import("viem").Hex;
470
- s: import("viem").Hex;
471
471
  to: Address | null;
472
472
  typeHex: import("viem").Hex | null;
473
473
  v: bigint;
@@ -508,16 +508,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
508
508
  } | undefined) => Promise<import("viem").EstimateMaxPriorityFeePerGasReturnType>;
509
509
  getStorageAt: (args: import("viem").GetStorageAtParameters) => Promise<import("viem").GetStorageAtReturnType>;
510
510
  getTransaction: <blockTag extends import("viem").BlockTag = "latest">(args: import("viem").GetTransactionParameters<blockTag>) => Promise<{
511
+ input: import("viem").Hex;
512
+ s: import("viem").Hex;
511
513
  type: "legacy";
512
514
  value: bigint;
513
515
  yParity?: undefined | undefined;
514
516
  from: Address;
515
517
  gas: bigint;
516
518
  hash: import("viem").Hash;
517
- input: import("viem").Hex;
518
519
  nonce: number;
519
520
  r: import("viem").Hex;
520
- s: import("viem").Hex;
521
521
  to: Address | null;
522
522
  typeHex: import("viem").Hex | null;
523
523
  v: bigint;
@@ -533,16 +533,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
533
533
  blockNumber: (blockTag extends "pending" ? true : false) extends infer T_1 ? T_1 extends (blockTag extends "pending" ? true : false) ? T_1 extends true ? null : bigint : never : never;
534
534
  transactionIndex: (blockTag extends "pending" ? true : false) extends infer T_2 ? T_2 extends (blockTag extends "pending" ? true : false) ? T_2 extends true ? null : number : never : never;
535
535
  } | {
536
+ input: import("viem").Hex;
537
+ s: import("viem").Hex;
536
538
  type: "eip2930";
537
539
  value: bigint;
538
540
  yParity: number;
539
541
  from: Address;
540
542
  gas: bigint;
541
543
  hash: import("viem").Hash;
542
- input: import("viem").Hex;
543
544
  nonce: number;
544
545
  r: import("viem").Hex;
545
- s: import("viem").Hex;
546
546
  to: Address | null;
547
547
  typeHex: import("viem").Hex | null;
548
548
  v: bigint;
@@ -558,16 +558,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
558
558
  blockNumber: (blockTag extends "pending" ? true : false) extends infer T_4 ? T_4 extends (blockTag extends "pending" ? true : false) ? T_4 extends true ? null : bigint : never : never;
559
559
  transactionIndex: (blockTag extends "pending" ? true : false) extends infer T_5 ? T_5 extends (blockTag extends "pending" ? true : false) ? T_5 extends true ? null : number : never : never;
560
560
  } | {
561
+ input: import("viem").Hex;
562
+ s: import("viem").Hex;
561
563
  type: "eip1559";
562
564
  value: bigint;
563
565
  yParity: number;
564
566
  from: Address;
565
567
  gas: bigint;
566
568
  hash: import("viem").Hash;
567
- input: import("viem").Hex;
568
569
  nonce: number;
569
570
  r: import("viem").Hex;
570
- s: import("viem").Hex;
571
571
  to: Address | null;
572
572
  typeHex: import("viem").Hex | null;
573
573
  v: bigint;
@@ -583,16 +583,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
583
583
  blockNumber: (blockTag extends "pending" ? true : false) extends infer T_7 ? T_7 extends (blockTag extends "pending" ? true : false) ? T_7 extends true ? null : bigint : never : never;
584
584
  transactionIndex: (blockTag extends "pending" ? true : false) extends infer T_8 ? T_8 extends (blockTag extends "pending" ? true : false) ? T_8 extends true ? null : number : never : never;
585
585
  } | {
586
+ input: import("viem").Hex;
587
+ s: import("viem").Hex;
586
588
  type: "eip4844";
587
589
  value: bigint;
588
590
  yParity: number;
589
591
  from: Address;
590
592
  gas: bigint;
591
593
  hash: import("viem").Hash;
592
- input: import("viem").Hex;
593
594
  nonce: number;
594
595
  r: import("viem").Hex;
595
- s: import("viem").Hex;
596
596
  to: Address | null;
597
597
  typeHex: import("viem").Hex | null;
598
598
  v: bigint;
@@ -608,16 +608,16 @@ export declare function usePublicClient(chain: Chain, rpcUrl: string): {
608
608
  blockNumber: (blockTag extends "pending" ? true : false) extends infer T_10 ? T_10 extends (blockTag extends "pending" ? true : false) ? T_10 extends true ? null : bigint : never : never;
609
609
  transactionIndex: (blockTag extends "pending" ? true : false) extends infer T_11 ? T_11 extends (blockTag extends "pending" ? true : false) ? T_11 extends true ? null : number : never : never;
610
610
  } | {
611
+ input: import("viem").Hex;
612
+ s: import("viem").Hex;
611
613
  type: "eip7702";
612
614
  value: bigint;
613
615
  yParity: number;
614
616
  from: Address;
615
617
  gas: bigint;
616
618
  hash: import("viem").Hash;
617
- input: import("viem").Hex;
618
619
  nonce: number;
619
620
  r: import("viem").Hex;
620
- s: import("viem").Hex;
621
621
  to: Address | null;
622
622
  typeHex: import("viem").Hex | null;
623
623
  v: bigint;
@@ -8162,9 +8162,9 @@ export declare function useWalletClient(chain: any, rpcUrl?: string): {
8162
8162
  [x: `address[${string}]`]: undefined;
8163
8163
  [x: `bool[${string}]`]: undefined;
8164
8164
  [x: `bytes[${string}]`]: undefined;
8165
+ [x: `bytes3[${string}]`]: undefined;
8165
8166
  [x: `bytes1[${string}]`]: undefined;
8166
8167
  [x: `bytes2[${string}]`]: undefined;
8167
- [x: `bytes3[${string}]`]: undefined;
8168
8168
  [x: `bytes4[${string}]`]: undefined;
8169
8169
  [x: `bytes5[${string}]`]: undefined;
8170
8170
  [x: `bytes6[${string}]`]: undefined;
@@ -8264,9 +8264,9 @@ export declare function useWalletClient(chain: any, rpcUrl?: string): {
8264
8264
  address?: undefined;
8265
8265
  bool?: undefined;
8266
8266
  bytes?: undefined;
8267
+ bytes3?: undefined;
8267
8268
  bytes1?: undefined;
8268
8269
  bytes2?: undefined;
8269
- bytes3?: undefined;
8270
8270
  bytes4?: undefined;
8271
8271
  bytes5?: undefined;
8272
8272
  bytes6?: undefined;
@@ -13137,9 +13137,9 @@ export declare function useExternalWalletClient(): {
13137
13137
  [x: `address[${string}]`]: undefined;
13138
13138
  [x: `bool[${string}]`]: undefined;
13139
13139
  [x: `bytes[${string}]`]: undefined;
13140
+ [x: `bytes3[${string}]`]: undefined;
13140
13141
  [x: `bytes1[${string}]`]: undefined;
13141
13142
  [x: `bytes2[${string}]`]: undefined;
13142
- [x: `bytes3[${string}]`]: undefined;
13143
13143
  [x: `bytes4[${string}]`]: undefined;
13144
13144
  [x: `bytes5[${string}]`]: undefined;
13145
13145
  [x: `bytes6[${string}]`]: undefined;
@@ -13239,9 +13239,9 @@ export declare function useExternalWalletClient(): {
13239
13239
  address?: undefined;
13240
13240
  bool?: undefined;
13241
13241
  bytes?: undefined;
13242
+ bytes3?: undefined;
13242
13243
  bytes1?: undefined;
13243
13244
  bytes2?: undefined;
13244
- bytes3?: undefined;
13245
13245
  bytes4?: undefined;
13246
13246
  bytes5?: undefined;
13247
13247
  bytes6?: undefined;
@@ -4,6 +4,7 @@
4
4
  import { createConfig, createStorage, http, cookieStorage, } from "@wagmi/core";
5
5
  import { injected, walletConnect } from "wagmi/connectors";
6
6
  import { SUPPORTED_CHAINS, } from "@abstraxn/signer-core";
7
+ import { createWalletConnectDappMetadata } from "./walletConnectMobile";
7
8
  /**
8
9
  * Convert internal Chain type to viem Chain type
9
10
  * Uses viem's built-in chain definitions when available for better compatibility
@@ -79,14 +80,7 @@ export function createWagmiConfig(chains, walletConnectProjectId, enabledConnect
79
80
  else {
80
81
  connectors.push(walletConnect({
81
82
  projectId: walletConnectProjectId,
82
- metadata: {
83
- name: "Abstraxn",
84
- description: "Abstraxn Wallet",
85
- url: typeof window !== "undefined"
86
- ? window.location.origin
87
- : "https://abstraxn.com",
88
- icons: ["https://avatars.githubusercontent.com/u/37784886"],
89
- },
83
+ metadata: createWalletConnectDappMetadata(),
90
84
  showQrModal: true,
91
85
  qrModalOptions: {
92
86
  themeMode: theme === "dark" ? "dark" : "light",
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Mobile WalletConnect helpers.
3
+ *
4
+ * On iOS/Android the browser suspends JS and drops the WalletConnect relay
5
+ * WebSocket when the user is sent to MetaMask (or another wallet) via deeplink.
6
+ * The wallet still approves/rejects, but the in-flight JSON-RPC promise never
7
+ * settles unless the relay is resumed when the dapp becomes visible again.
8
+ *
9
+ * After approve/reject, MetaMask opens metadata.redirect / metadata.url. Using
10
+ * only `location.origin` sends iOS users to Safari on the homepage (full reload,
11
+ * lost React state). We:
12
+ * - keep metadata.url as the current page (not origin)
13
+ * - on iOS Chrome/Firefox/etc. set redirect.native to the browser app scheme
14
+ * with no path, so the existing tab is foregrounded
15
+ * - on Safari omit redirect so MetaMask can `goBack()` to the same tab
16
+ */
17
+ /** Selectors used to close/restore Reown AppKit + WalletConnect modal DOM. */
18
+ export declare const WALLETCONNECT_MODAL_SELECTORS: string[];
19
+ export declare function isWalletConnectConnector(connector: {
20
+ id?: string;
21
+ name?: string;
22
+ } | null | undefined): boolean;
23
+ export declare function isUserApprovalRpcMethod(method: unknown): boolean;
24
+ export declare function hasPendingWalletConnectApproval(provider?: any): boolean;
25
+ export declare function hasActiveWalletConnectSession(provider: any): boolean;
26
+ /**
27
+ * Current dapp page, including path/query/hash. Never origin-only — that is
28
+ * what made MetaMask open the homepage after approve/reject.
29
+ */
30
+ export declare function getDappReturnUrl(): string;
31
+ /**
32
+ * iOS-only app scheme for the browser the user is actually in. No path:
33
+ * opening `googlechrome://` foregrounds Chrome's existing tab instead of
34
+ * navigating Safari to a new URL (which reloads and drops in-memory state).
35
+ */
36
+ export declare function getSourceBrowserNativeRedirect(): string | undefined;
37
+ /**
38
+ * Lazy metadata so url/redirect are read at connect/request time, not at
39
+ * provider mount (which is often the homepage in Next.js layouts).
40
+ */
41
+ export declare function createWalletConnectDappMetadata(): {
42
+ name: string;
43
+ description: string;
44
+ icons: string[];
45
+ url: string;
46
+ redirect?: {
47
+ native?: string;
48
+ universal?: string;
49
+ };
50
+ };
51
+ export declare function rememberCurrentPageForWalletReturn(): void;
52
+ export declare function clearWalletReturnIfSameDocument(): void;
53
+ /**
54
+ * If the wallet opened a fresh Safari tab on the homepage, send the user back
55
+ * to the page they transacted from. This still reloads that page (React state
56
+ * cannot be recovered), but it is better than leaving them on `/`.
57
+ */
58
+ export declare function restoreAfterWalletRedirect(): void;
59
+ /**
60
+ * Push the current page + source-browser scheme onto the live WC provider so
61
+ * MetaMask does not keep a stale homepage redirect from first init.
62
+ */
63
+ export declare function syncWalletConnectDappRedirect(provider: any): void;
64
+ /**
65
+ * Re-open the WalletConnect relay after the mobile browser comes back
66
+ * from the wallet app so pending approve/reject responses can be delivered.
67
+ */
68
+ export declare function resumeWalletConnectRelayer(provider: any): Promise<void>;
69
+ /**
70
+ * Undo the inline `display: none !important` we apply after connect so the
71
+ * WalletConnect modal can show "Continue in wallet" and process the response.
72
+ */
73
+ export declare function restoreWalletConnectModal(): void;
74
+ /**
75
+ * Patch the EIP-1193 provider so signing/tx requests resume the relay first.
76
+ * Covers wagmi hooks, viem walletClient, and direct provider.request calls.
77
+ */
78
+ export declare function patchWalletConnectProviderForMobile(provider: any): void;
79
+ /**
80
+ * When the dapp tab is shown again (user left MetaMask), restart the relay so
81
+ * the pending WalletConnect response can be received.
82
+ */
83
+ export declare function subscribeWalletConnectForegroundResume(provider: any): () => void;
@@ -0,0 +1,399 @@
1
+ /**
2
+ * Mobile WalletConnect helpers.
3
+ *
4
+ * On iOS/Android the browser suspends JS and drops the WalletConnect relay
5
+ * WebSocket when the user is sent to MetaMask (or another wallet) via deeplink.
6
+ * The wallet still approves/rejects, but the in-flight JSON-RPC promise never
7
+ * settles unless the relay is resumed when the dapp becomes visible again.
8
+ *
9
+ * After approve/reject, MetaMask opens metadata.redirect / metadata.url. Using
10
+ * only `location.origin` sends iOS users to Safari on the homepage (full reload,
11
+ * lost React state). We:
12
+ * - keep metadata.url as the current page (not origin)
13
+ * - on iOS Chrome/Firefox/etc. set redirect.native to the browser app scheme
14
+ * with no path, so the existing tab is foregrounded
15
+ * - on Safari omit redirect so MetaMask can `goBack()` to the same tab
16
+ */
17
+ const PATCHED_FLAG = "__abstraxnWcMobilePatched";
18
+ const PENDING_FLAG = "__abstraxnWcPendingApprovals";
19
+ const RETURN_HREF_KEY = "abstraxn_wc_return_href";
20
+ const RETURN_AT_KEY = "abstraxn_wc_return_at";
21
+ const RETURN_TTL_MS = 3 * 60 * 1000;
22
+ const USER_APPROVAL_METHODS = new Set([
23
+ "personal_sign",
24
+ "eth_sign",
25
+ "eth_signTypedData",
26
+ "eth_signTypedData_v3",
27
+ "eth_signTypedData_v4",
28
+ "eth_signTransaction",
29
+ "eth_sendTransaction",
30
+ "eth_sendRawTransaction",
31
+ "wallet_sendCalls",
32
+ "wallet_sendCallsSync",
33
+ "wallet_grantPermissions",
34
+ "wallet_requestPermissions",
35
+ "wallet_watchAsset",
36
+ "wallet_addEthereumChain",
37
+ "wallet_switchEthereumChain",
38
+ ]);
39
+ /** Selectors used to close/restore Reown AppKit + WalletConnect modal DOM. */
40
+ export const WALLETCONNECT_MODAL_SELECTORS = [
41
+ "w3m-modal",
42
+ "w3m-modal-backdrop",
43
+ "wcm-modal",
44
+ "[data-w3m-modal]",
45
+ ".w3m-modal",
46
+ ".w3m-modal-backdrop",
47
+ "#w3m-modal",
48
+ "[class*='w3m-modal']",
49
+ "[id*='w3m-modal']",
50
+ "[class*='walletconnect-qrcode']",
51
+ "[id*='walletconnect']",
52
+ ];
53
+ export function isWalletConnectConnector(connector) {
54
+ if (!connector)
55
+ return false;
56
+ const id = String(connector.id || "").toLowerCase();
57
+ const name = String(connector.name || "").toLowerCase();
58
+ return (id.includes("walletconnect") ||
59
+ id.includes("wallet_connect") ||
60
+ name.includes("walletconnect") ||
61
+ name.includes("wallet connect"));
62
+ }
63
+ export function isUserApprovalRpcMethod(method) {
64
+ return typeof method === "string" && USER_APPROVAL_METHODS.has(method);
65
+ }
66
+ export function hasPendingWalletConnectApproval(provider) {
67
+ if (provider && typeof provider[PENDING_FLAG] === "number") {
68
+ return provider[PENDING_FLAG] > 0;
69
+ }
70
+ return false;
71
+ }
72
+ export function hasActiveWalletConnectSession(provider) {
73
+ const session = provider?.session ?? provider?.signer?.session;
74
+ if (!session?.topic)
75
+ return false;
76
+ if (typeof session.expiry === "number" &&
77
+ session.expiry > 0 &&
78
+ session.expiry * 1000 < Date.now()) {
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+ function isIOS() {
84
+ if (typeof navigator === "undefined")
85
+ return false;
86
+ const ua = navigator.userAgent || "";
87
+ const platform = navigator.platform || "";
88
+ return (/iPad|iPhone|iPod/i.test(ua) ||
89
+ (platform === "MacIntel" && (navigator.maxTouchPoints || 0) > 1));
90
+ }
91
+ /**
92
+ * Current dapp page, including path/query/hash. Never origin-only — that is
93
+ * what made MetaMask open the homepage after approve/reject.
94
+ */
95
+ export function getDappReturnUrl() {
96
+ if (typeof window === "undefined")
97
+ return "https://abstraxn.com";
98
+ const url = new URL(window.location.href);
99
+ url.searchParams.delete("wc_ev");
100
+ url.searchParams.delete("wc_res");
101
+ return `${url.origin}${url.pathname}${url.search}${url.hash}`;
102
+ }
103
+ /**
104
+ * iOS-only app scheme for the browser the user is actually in. No path:
105
+ * opening `googlechrome://` foregrounds Chrome's existing tab instead of
106
+ * navigating Safari to a new URL (which reloads and drops in-memory state).
107
+ */
108
+ export function getSourceBrowserNativeRedirect() {
109
+ if (typeof navigator === "undefined" || !isIOS())
110
+ return undefined;
111
+ const ua = navigator.userAgent || "";
112
+ if (/FxiOS/i.test(ua))
113
+ return "firefox://";
114
+ if (/EdgiOS/i.test(ua))
115
+ return "microsoft-edge://";
116
+ if (/OPiOS/i.test(ua) || /OPT\//i.test(ua))
117
+ return "touch://";
118
+ if (/Brave/i.test(ua))
119
+ return "brave://";
120
+ if (/CriOS/i.test(ua))
121
+ return "googlechrome://";
122
+ return undefined;
123
+ }
124
+ function getWalletConnectRedirect() {
125
+ const native = getSourceBrowserNativeRedirect();
126
+ return native ? { native } : undefined;
127
+ }
128
+ /**
129
+ * Lazy metadata so url/redirect are read at connect/request time, not at
130
+ * provider mount (which is often the homepage in Next.js layouts).
131
+ */
132
+ export function createWalletConnectDappMetadata() {
133
+ return {
134
+ name: "Abstraxn",
135
+ description: "Abstraxn Wallet",
136
+ icons: ["https://avatars.githubusercontent.com/u/37784886"],
137
+ get url() {
138
+ return getDappReturnUrl();
139
+ },
140
+ get redirect() {
141
+ return getWalletConnectRedirect();
142
+ },
143
+ };
144
+ }
145
+ export function rememberCurrentPageForWalletReturn() {
146
+ if (typeof window === "undefined")
147
+ return;
148
+ try {
149
+ window.localStorage.setItem(RETURN_HREF_KEY, getDappReturnUrl());
150
+ window.localStorage.setItem(RETURN_AT_KEY, String(Date.now()));
151
+ }
152
+ catch {
153
+ // Ignore quota / private-mode errors.
154
+ }
155
+ }
156
+ export function clearWalletReturnIfSameDocument() {
157
+ if (typeof window === "undefined")
158
+ return;
159
+ try {
160
+ const saved = window.localStorage.getItem(RETURN_HREF_KEY);
161
+ if (!saved)
162
+ return;
163
+ const savedUrl = new URL(saved, window.location.origin);
164
+ if (savedUrl.origin !== window.location.origin)
165
+ return;
166
+ if (savedUrl.pathname === window.location.pathname &&
167
+ savedUrl.search === window.location.search &&
168
+ savedUrl.hash === window.location.hash) {
169
+ window.localStorage.removeItem(RETURN_HREF_KEY);
170
+ window.localStorage.removeItem(RETURN_AT_KEY);
171
+ }
172
+ }
173
+ catch {
174
+ // Ignore
175
+ }
176
+ }
177
+ /**
178
+ * If the wallet opened a fresh Safari tab on the homepage, send the user back
179
+ * to the page they transacted from. This still reloads that page (React state
180
+ * cannot be recovered), but it is better than leaving them on `/`.
181
+ */
182
+ export function restoreAfterWalletRedirect() {
183
+ if (typeof window === "undefined")
184
+ return;
185
+ try {
186
+ const at = Number(window.localStorage.getItem(RETURN_AT_KEY) || 0);
187
+ const saved = window.localStorage.getItem(RETURN_HREF_KEY);
188
+ if (!saved || !at || Date.now() - at > RETURN_TTL_MS) {
189
+ window.localStorage.removeItem(RETURN_HREF_KEY);
190
+ window.localStorage.removeItem(RETURN_AT_KEY);
191
+ return;
192
+ }
193
+ const savedUrl = new URL(saved, window.location.origin);
194
+ if (savedUrl.origin !== window.location.origin) {
195
+ window.localStorage.removeItem(RETURN_HREF_KEY);
196
+ window.localStorage.removeItem(RETURN_AT_KEY);
197
+ return;
198
+ }
199
+ const savedPath = `${savedUrl.pathname}${savedUrl.search}${savedUrl.hash}`;
200
+ const currentPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
201
+ if (savedPath === currentPath) {
202
+ window.localStorage.removeItem(RETURN_HREF_KEY);
203
+ window.localStorage.removeItem(RETURN_AT_KEY);
204
+ return;
205
+ }
206
+ window.localStorage.removeItem(RETURN_HREF_KEY);
207
+ window.localStorage.removeItem(RETURN_AT_KEY);
208
+ window.location.replace(savedUrl.href);
209
+ }
210
+ catch {
211
+ // Ignore
212
+ }
213
+ }
214
+ function applyDappMetadata(meta, url, redirect) {
215
+ if (!meta || typeof meta !== "object")
216
+ return;
217
+ meta.url = url;
218
+ if (redirect) {
219
+ meta.redirect = { native: redirect.native };
220
+ }
221
+ else if (meta.redirect) {
222
+ delete meta.redirect;
223
+ }
224
+ }
225
+ /**
226
+ * Push the current page + source-browser scheme onto the live WC provider so
227
+ * MetaMask does not keep a stale homepage redirect from first init.
228
+ */
229
+ export function syncWalletConnectDappRedirect(provider) {
230
+ if (!provider || typeof window === "undefined")
231
+ return;
232
+ const url = getDappReturnUrl();
233
+ const redirect = getWalletConnectRedirect();
234
+ rememberCurrentPageForWalletReturn();
235
+ applyDappMetadata(provider.metadata, url, redirect);
236
+ applyDappMetadata(provider.signer?.metadata, url, redirect);
237
+ applyDappMetadata(provider.session?.self?.metadata, url, redirect);
238
+ applyDappMetadata(provider.signer?.session?.self?.metadata, url, redirect);
239
+ }
240
+ function getWalletConnectRelayer(provider) {
241
+ return (provider?.signer?.client?.core?.relayer ||
242
+ provider?.client?.core?.relayer ||
243
+ provider?.rpc?.provider?.signer?.client?.core?.relayer ||
244
+ provider?.universalProvider?.client?.core?.relayer ||
245
+ null);
246
+ }
247
+ function isRelayerConnected(relayer) {
248
+ return (relayer?.connected === true ||
249
+ relayer?.provider?.connected === true ||
250
+ relayer?.provider?.connection?.connected === true);
251
+ }
252
+ /**
253
+ * Re-open the WalletConnect relay after the mobile browser comes back
254
+ * from the wallet app so pending approve/reject responses can be delivered.
255
+ */
256
+ export async function resumeWalletConnectRelayer(provider) {
257
+ if (!provider)
258
+ return;
259
+ const relayer = getWalletConnectRelayer(provider);
260
+ if (!relayer)
261
+ return;
262
+ try {
263
+ if (isRelayerConnected(relayer)) {
264
+ try {
265
+ const ping = relayer.provider?.connection?.ping;
266
+ if (typeof ping === "function") {
267
+ await Promise.race([
268
+ ping.call(relayer.provider.connection),
269
+ new Promise((_, reject) => setTimeout(() => reject(new Error("ping timeout")), 1500)),
270
+ ]);
271
+ }
272
+ return;
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?.();
284
+ }
285
+ catch {
286
+ // Ignore close errors and still try to open.
287
+ }
288
+ await relayer.transportOpen();
289
+ }
290
+ const session = provider?.session ?? provider?.signer?.session;
291
+ const topic = session?.topic;
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
+ }
300
+ }
301
+ }
302
+ catch {
303
+ // Best-effort: a failed resume should not break the original request.
304
+ }
305
+ }
306
+ /**
307
+ * Undo the inline `display: none !important` we apply after connect so the
308
+ * WalletConnect modal can show "Continue in wallet" and process the response.
309
+ */
310
+ export function restoreWalletConnectModal() {
311
+ if (typeof document === "undefined")
312
+ return;
313
+ WALLETCONNECT_MODAL_SELECTORS.forEach((sel) => {
314
+ try {
315
+ document.querySelectorAll(sel).forEach((el) => {
316
+ const htmlEl = el;
317
+ if (htmlEl?.style) {
318
+ htmlEl.style.removeProperty("display");
319
+ }
320
+ });
321
+ }
322
+ catch {
323
+ // Ignore invalid selectors in older browsers.
324
+ }
325
+ });
326
+ }
327
+ function getRequestMethod(args) {
328
+ const payload = args[0];
329
+ if (typeof payload === "string")
330
+ return payload;
331
+ if (payload && typeof payload === "object" && "method" in payload) {
332
+ return payload.method;
333
+ }
334
+ return undefined;
335
+ }
336
+ /**
337
+ * Patch the EIP-1193 provider so signing/tx requests resume the relay first.
338
+ * Covers wagmi hooks, viem walletClient, and direct provider.request calls.
339
+ */
340
+ export function patchWalletConnectProviderForMobile(provider) {
341
+ if (!provider || typeof provider.request !== "function")
342
+ return;
343
+ if (provider[PATCHED_FLAG])
344
+ return;
345
+ const originalRequest = provider.request.bind(provider);
346
+ provider[PATCHED_FLAG] = true;
347
+ provider[PENDING_FLAG] = 0;
348
+ provider.request = async (...args) => {
349
+ const method = getRequestMethod(args);
350
+ if (!isUserApprovalRpcMethod(method)) {
351
+ return originalRequest(...args);
352
+ }
353
+ provider[PENDING_FLAG] = (provider[PENDING_FLAG] || 0) + 1;
354
+ restoreWalletConnectModal();
355
+ syncWalletConnectDappRedirect(provider);
356
+ try {
357
+ await resumeWalletConnectRelayer(provider);
358
+ return await originalRequest(...args);
359
+ }
360
+ finally {
361
+ provider[PENDING_FLAG] = Math.max(0, (provider[PENDING_FLAG] || 1) - 1);
362
+ }
363
+ };
364
+ }
365
+ /**
366
+ * When the dapp tab is shown again (user left MetaMask), restart the relay so
367
+ * the pending WalletConnect response can be received.
368
+ */
369
+ export function subscribeWalletConnectForegroundResume(provider) {
370
+ if (typeof window === "undefined" || !provider)
371
+ return () => { };
372
+ let timer = null;
373
+ const resume = () => {
374
+ if (timer)
375
+ clearTimeout(timer);
376
+ timer = setTimeout(() => {
377
+ clearWalletReturnIfSameDocument();
378
+ void resumeWalletConnectRelayer(provider);
379
+ if (hasPendingWalletConnectApproval(provider)) {
380
+ restoreWalletConnectModal();
381
+ }
382
+ }, 250);
383
+ };
384
+ const onVisibility = () => {
385
+ if (document.visibilityState === "visible")
386
+ resume();
387
+ };
388
+ document.addEventListener("visibilitychange", onVisibility);
389
+ window.addEventListener("pageshow", resume);
390
+ window.addEventListener("focus", resume);
391
+ return () => {
392
+ if (timer)
393
+ clearTimeout(timer);
394
+ document.removeEventListener("visibilitychange", onVisibility);
395
+ window.removeEventListener("pageshow", resume);
396
+ window.removeEventListener("focus", resume);
397
+ };
398
+ }
399
+ //# sourceMappingURL=walletConnectMobile.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abstraxn/signer-react",
3
- "version": "3.3.4",
3
+ "version": "3.3.6",
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",