@abstraxn/signer-react 3.3.9 → 4.0.0

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/src/ConnectButton.css.d.ts +2 -0
  3. package/dist/src/ConnectButton.css.js +1 -0
  4. package/dist/src/ConnectButton.d.ts +0 -1
  5. package/dist/src/ConnectButton.js +7 -6
  6. package/dist/src/ExternalWalletButtons.css.d.ts +2 -0
  7. package/dist/src/ExternalWalletButtons.css.js +1 -0
  8. package/dist/src/ExternalWalletButtons.d.ts +4 -1
  9. package/dist/src/ExternalWalletButtons.js +6 -1
  10. package/dist/src/WalletModal.css +2 -0
  11. package/dist/src/WalletModal.css.d.ts +2 -0
  12. package/dist/src/WalletModal.css.js +1 -0
  13. package/dist/src/WalletModal.d.ts +2 -2
  14. package/dist/src/WalletModal.js +113 -59
  15. package/dist/src/components/AbstraxnLogo.d.ts +17 -0
  16. package/dist/src/components/AbstraxnLogo.js +29 -0
  17. package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js +406 -338
  18. package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.d.ts +7 -0
  19. package/dist/src/components/AbstraxnProvider/useAbstraxnProviderBase.js +9 -0
  20. package/dist/src/components/AbstraxnProvider/useWalletInitialization.js +41 -6
  21. package/dist/src/components/OnboardingUI/OnboardingUI.css +99 -4
  22. package/dist/src/components/OnboardingUI/OnboardingUI.css.d.ts +2 -0
  23. package/dist/src/components/OnboardingUI/OnboardingUI.css.js +1 -0
  24. package/dist/src/components/OnboardingUI/OnboardingUIReact.d.ts +0 -1
  25. package/dist/src/components/OnboardingUI/OnboardingUIReact.js +46 -19
  26. package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +24 -1
  27. package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +348 -160
  28. package/dist/src/components/OnboardingUI/components/Divider.d.ts +7 -0
  29. package/dist/src/components/OnboardingUI/components/Divider.js +9 -0
  30. package/dist/src/components/OnboardingUI/components/EmailForm.d.ts +0 -1
  31. package/dist/src/components/OnboardingUI/components/EmailForm.js +1 -2
  32. package/dist/src/components/OnboardingUI/components/MfaForm.d.ts +0 -1
  33. package/dist/src/components/OnboardingUI/components/MfaForm.js +2 -3
  34. package/dist/src/components/OnboardingUI/components/Modal.d.ts +0 -1
  35. package/dist/src/components/OnboardingUI/components/Modal.js +12 -4
  36. package/dist/src/components/OnboardingUI/components/OtpForm.d.ts +0 -1
  37. package/dist/src/components/OnboardingUI/components/OtpForm.js +2 -3
  38. package/dist/src/components/OnboardingUI/components/PasskeyButton.d.ts +0 -1
  39. package/dist/src/components/OnboardingUI/components/PasskeyButton.js +2 -2
  40. package/dist/src/components/OnboardingUI/components/SocialButtons.d.ts +0 -1
  41. package/dist/src/components/OnboardingUI/components/SocialButtons.js +3 -3
  42. package/dist/src/components/OnboardingUI/onboardingShadowContainer.d.ts +9 -0
  43. package/dist/src/components/OnboardingUI/onboardingShadowContainer.js +36 -0
  44. package/dist/src/components/ShadowHost/ShadowHost.d.ts +18 -0
  45. package/dist/src/components/ShadowHost/ShadowHost.js +123 -0
  46. package/dist/src/components/ShadowHost/ShadowHostContext.d.ts +10 -0
  47. package/dist/src/components/ShadowHost/ShadowHostContext.js +14 -0
  48. package/dist/src/components/ShadowHost/index.d.ts +2 -0
  49. package/dist/src/components/ShadowHost/index.js +3 -0
  50. package/dist/src/components/WalletModal/components/ChainSelector.css +9 -0
  51. package/dist/src/components/WalletModal/components/ChainSelector.css.d.ts +2 -0
  52. package/dist/src/components/WalletModal/components/ChainSelector.css.js +1 -0
  53. package/dist/src/components/WalletModal/components/ChainSelector.d.ts +0 -1
  54. package/dist/src/components/WalletModal/components/ChainSelector.js +78 -23
  55. package/dist/src/components/WalletModal/components/CodeInput.d.ts +12 -0
  56. package/dist/src/components/WalletModal/components/CodeInput.js +39 -0
  57. package/dist/src/components/WalletModal/components/ExportKeyModal.css.d.ts +2 -0
  58. package/dist/src/components/WalletModal/components/ExportKeyModal.css.js +1 -0
  59. package/dist/src/components/WalletModal/components/ExportKeyModal.d.ts +0 -2
  60. package/dist/src/components/WalletModal/components/ExportKeyModal.js +2 -4
  61. package/dist/src/components/WalletModal/components/ExportWarningModal.css.d.ts +2 -0
  62. package/dist/src/components/WalletModal/components/ExportWarningModal.css.js +1 -0
  63. package/dist/src/components/WalletModal/components/ExportWarningModal.d.ts +0 -2
  64. package/dist/src/components/WalletModal/components/ExportWarningModal.js +2 -4
  65. package/dist/src/components/WalletModal/components/ManageWalletModal.css +27 -1
  66. package/dist/src/components/WalletModal/components/ManageWalletModal.css.d.ts +2 -0
  67. package/dist/src/components/WalletModal/components/ManageWalletModal.css.js +1 -0
  68. package/dist/src/components/WalletModal/components/ManageWalletModal.d.ts +0 -1
  69. package/dist/src/components/WalletModal/components/ManageWalletModal.js +66 -148
  70. package/dist/src/components/WalletModal/components/MfaVerifyModal.d.ts +15 -0
  71. package/dist/src/components/WalletModal/components/MfaVerifyModal.js +98 -0
  72. package/dist/src/components/WalletModal/components/PreviewTransactionModal.css +2 -12
  73. package/dist/src/components/WalletModal/components/PreviewTransactionModal.css.d.ts +2 -0
  74. package/dist/src/components/WalletModal/components/PreviewTransactionModal.css.js +1 -0
  75. package/dist/src/components/WalletModal/components/PreviewTransactionModal.d.ts +0 -1
  76. package/dist/src/components/WalletModal/components/PreviewTransactionModal.js +0 -1
  77. package/dist/src/components/WalletModal/components/ReceiveModal.css.d.ts +2 -0
  78. package/dist/src/components/WalletModal/components/ReceiveModal.css.js +1 -0
  79. package/dist/src/components/WalletModal/components/ReceiveModal.d.ts +0 -1
  80. package/dist/src/components/WalletModal/components/ReceiveModal.js +2 -2
  81. package/dist/src/components/WalletModal/components/SendModal.css.d.ts +2 -0
  82. package/dist/src/components/WalletModal/components/SendModal.css.js +1 -0
  83. package/dist/src/components/WalletModal/components/SendModal.d.ts +5 -2
  84. package/dist/src/components/WalletModal/components/SendModal.js +25 -82
  85. package/dist/src/components/WalletModal/components/SignMfaModalHost.d.ts +1 -0
  86. package/dist/src/components/WalletModal/components/SignMfaModalHost.js +50 -0
  87. package/dist/src/components/WalletModal/components/SuccessModal.css.d.ts +2 -0
  88. package/dist/src/components/WalletModal/components/SuccessModal.css.js +1 -0
  89. package/dist/src/components/WalletModal/components/SuccessModal.d.ts +0 -1
  90. package/dist/src/components/WalletModal/components/SuccessModal.js +0 -1
  91. package/dist/src/components/WalletModal/components/TokenSelectorModal.css.d.ts +2 -0
  92. package/dist/src/components/WalletModal/components/TokenSelectorModal.css.js +1 -0
  93. package/dist/src/components/WalletModal/components/TokenSelectorModal.d.ts +0 -1
  94. package/dist/src/components/WalletModal/components/TokenSelectorModal.js +0 -1
  95. package/dist/src/components/WalletModal/components/UserAvatar.js +2 -1
  96. package/dist/src/components/WalletModal/components/index.d.ts +3 -0
  97. package/dist/src/components/WalletModal/components/index.js +2 -0
  98. package/dist/src/components/WalletModal/hooks/useSendTransaction.js +18 -6
  99. package/dist/src/components/icons.d.ts +2 -0
  100. package/dist/src/components/icons.js +4 -0
  101. package/dist/src/hooks.d.ts +81 -20
  102. package/dist/src/hooks.js +194 -36
  103. package/dist/src/index.d.ts +2 -2
  104. package/dist/src/index.js +2 -2
  105. package/dist/src/signMfaBridge.d.ts +7 -0
  106. package/dist/src/signMfaBridge.js +20 -0
  107. package/dist/src/types.d.ts +4 -0
  108. package/dist/src/utils/shadowFocus.d.ts +8 -0
  109. package/dist/src/utils/shadowFocus.js +15 -0
  110. package/dist/src/utils/shadowRegistry.d.ts +23 -0
  111. package/dist/src/utils/shadowRegistry.js +36 -0
  112. package/dist/src/walletConnectMobile.d.ts +9 -5
  113. package/dist/src/walletConnectMobile.js +152 -51
  114. package/package.json +9 -4
package/dist/src/hooks.js CHANGED
@@ -4,6 +4,8 @@
4
4
  import { useState, useCallback, useEffect, useMemo } from 'react';
5
5
  import { useAbstraxnWallet } from './AbstraxnProvider';
6
6
  import { InvalidDeviceIdError, SignMfaRequiredError } from '@abstraxn/signer-core';
7
+ import { requestSignMfaCode } from './signMfaBridge';
8
+ import { TURNKEY_SUPPORTED_CAIP2, NATIVE_ASSET_SENTINEL, chainIdToCaip2, isNativeAssetEntry, extractAssetAddress, } from '@abstraxn/signer-core';
7
9
  import { createPublicClient, createWalletClient, http, getContract, serializeTransaction, encodeFunctionData, hashTypedData, hashMessage, parseSignature } from 'viem';
8
10
  import { toAccount } from 'viem/accounts';
9
11
  import { Connection as SolanaConnection, Transaction as SolanaTransaction, SystemProgram, PublicKey, LAMPORTS_PER_SOL, TransactionInstruction, } from '@solana/web3.js';
@@ -12,7 +14,7 @@ import { useWalletClient as useWagmiWalletClient, useAccount, useConfig, useChai
12
14
  import { getWalletClient, switchChain, getPublicClient } from '@wagmi/core';
13
15
  import { getConnectorMeta } from './connectors';
14
16
  import { getChainById } from './chains';
15
- import { waitUntilDocumentVisible, pollForOutgoingTx, clearWalletConnectPendingState, isWalletConnectConnector, isMobileDevice, } from './walletConnectMobile';
17
+ import { waitUntilDocumentVisible, pollForOutgoingTx, clearWalletConnectPendingState, isWalletConnectConnector, isMobileDevice, isUserRejectionError, toUserRejectionError, } from './walletConnectMobile';
16
18
  /**
17
19
  * Hook to check if wallet is connected
18
20
  */
@@ -233,15 +235,8 @@ function isInvalidDeviceIdError(error) {
233
235
  candidate?.name === 'InvalidDeviceIdError' ||
234
236
  (typeof candidate?.message === 'string' && candidate.message.includes('INVALID_DEVICE_ID')));
235
237
  }
236
- function promptForSignMfaCode() {
237
- if (typeof window === 'undefined' || typeof window.prompt !== 'function') {
238
- throw new Error('MFA step-up is required to sign, but no prompt UI is available in this environment.');
239
- }
240
- const code = window.prompt('Enter your MFA code to approve this signing request');
241
- if (!code || code.trim() === '') {
242
- throw new Error('MFA code is required to continue signing.');
243
- }
244
- return code.trim().toUpperCase();
238
+ async function requestSignMfaCodeFromUi() {
239
+ return requestSignMfaCode();
245
240
  }
246
241
  async function runWithSignStepUpRetry(performSign, verifySignMfa) {
247
242
  try {
@@ -257,7 +252,7 @@ async function runWithSignStepUpRetry(performSign, verifySignMfa) {
257
252
  if (!verifySignMfa) {
258
253
  throw new Error('MFA step-up is required, but sign verification is not available.');
259
254
  }
260
- const code = promptForSignMfaCode();
255
+ const code = await requestSignMfaCodeFromUi();
261
256
  let verifyResponse;
262
257
  try {
263
258
  verifyResponse = await verifySignMfa(code);
@@ -399,11 +394,11 @@ export function useEnableMfa() {
399
394
  * auth (otp/recovery) -> confirm disable.
400
395
  */
401
396
  export function useDisableMfa() {
402
- const { verifyMfa, disableMfaWithSignedPayload } = useAbstraxnWallet();
397
+ const { verifySignMfa, disableMfaWithSignedPayload } = useAbstraxnWallet();
403
398
  const [disableState, setDisableState] = useState(null);
404
399
  const [isLoading, setIsLoading] = useState(false);
405
400
  const [error, setError] = useState(null);
406
- const canDisableMfaFlow = !!verifyMfa && !!disableMfaWithSignedPayload;
401
+ const canDisableMfaFlow = !!verifySignMfa && !!disableMfaWithSignedPayload;
407
402
  const reset = useCallback(() => {
408
403
  setDisableState(null);
409
404
  setIsLoading(false);
@@ -420,7 +415,8 @@ export function useDisableMfa() {
420
415
  setDisableState({ step: 'auth', method });
421
416
  }, []);
422
417
  const verifyCode = useCallback(async (code) => {
423
- if (!verifyMfa || disableState?.step !== 'auth')
418
+ // Sign step-up is required: disable MFA signs a payload via /sign/transaction.
419
+ if (!verifySignMfa || disableState?.step !== 'auth')
424
420
  return false;
425
421
  const { method } = disableState;
426
422
  const trimmed = method === 'recovery' ? code.trim().toUpperCase() : code.trim();
@@ -434,7 +430,7 @@ export function useDisableMfa() {
434
430
  setIsLoading(true);
435
431
  setError(null);
436
432
  try {
437
- await verifyMfa(trimmed);
433
+ await verifySignMfa(trimmed);
438
434
  setDisableState({ step: 'confirm' });
439
435
  return true;
440
436
  }
@@ -446,7 +442,7 @@ export function useDisableMfa() {
446
442
  finally {
447
443
  setIsLoading(false);
448
444
  }
449
- }, [verifyMfa, disableState]);
445
+ }, [verifySignMfa, disableState]);
450
446
  const confirmDisable = useCallback(async () => {
451
447
  if (!disableMfaWithSignedPayload)
452
448
  return null;
@@ -1049,7 +1045,7 @@ export function useSolanaPublicKey() {
1049
1045
  * RPC endpoint is taken from the current Solana ChainData (configured in AbstraxnProvider).
1050
1046
  */
1051
1047
  export function useSolanaBalance() {
1052
- const { currentChain, whoami, solanaWallet, solanaConnection } = useAbstraxnWallet();
1048
+ const { currentChain, whoami, solanaWallet, solanaConnection, wallet } = useAbstraxnWallet();
1053
1049
  const [lamports, setLamports] = useState(null);
1054
1050
  const [formattedBalance, setFormattedBalance] = useState('0');
1055
1051
  const [loading, setLoading] = useState(false);
@@ -1062,6 +1058,9 @@ export function useSolanaBalance() {
1062
1058
  : null) ||
1063
1059
  // Fallback to provider connection endpoint
1064
1060
  (providerConnection && providerConnection.rpcEndpoint) || null;
1061
+ // An external wallet (Phantom) address isn't a Turnkey wallet-account - only try
1062
+ // Turnkey when using the embedded wallet's own address.
1063
+ const isExternalSolanaWallet = !!solanaWallet?.publicKey;
1065
1064
  const address = (solanaWallet?.publicKey && solanaWallet.publicKey.toBase58()) ||
1066
1065
  whoami?.solanaAddress ||
1067
1066
  null;
@@ -1074,14 +1073,35 @@ export function useSolanaBalance() {
1074
1073
  return;
1075
1074
  }
1076
1075
  let cancelled = false;
1076
+ const fetchViaRpc = async () => {
1077
+ const connection = new SolanaConnection(rpcUrl, 'confirmed');
1078
+ const balance = await connection.getBalance(new PublicKey(address), 'confirmed');
1079
+ return BigInt(balance);
1080
+ };
1077
1081
  const fetchBalance = async () => {
1078
1082
  setLoading(true);
1079
1083
  try {
1080
- const connection = new SolanaConnection(rpcUrl, 'confirmed');
1081
- const balance = await connection.getBalance(new PublicKey(address), 'confirmed');
1084
+ let asBigInt = null;
1085
+ if (!isExternalSolanaWallet && wallet && currentChain) {
1086
+ const caip2 = chainIdToCaip2(currentChain.id, TURNKEY_SUPPORTED_CAIP2);
1087
+ if (caip2) {
1088
+ try {
1089
+ const entries = await wallet.getTurnkeyBalances(caip2, address);
1090
+ const nativeEntry = entries.find((entry) => isNativeAssetEntry({ caip19: entry.caip19, symbol: entry.symbol, decimals: entry.decimals }, currentChain.nativeCurrency));
1091
+ if (nativeEntry?.balance !== undefined) {
1092
+ asBigInt = BigInt(nativeEntry.balance);
1093
+ }
1094
+ }
1095
+ catch (turnkeyError) {
1096
+ console.warn('Turnkey Solana balance fetch failed, falling back to RPC:', turnkeyError);
1097
+ }
1098
+ }
1099
+ }
1100
+ if (asBigInt === null) {
1101
+ asBigInt = await fetchViaRpc();
1102
+ }
1082
1103
  if (cancelled)
1083
1104
  return;
1084
- const asBigInt = BigInt(balance);
1085
1105
  setLamports(asBigInt);
1086
1106
  const sol = Number(asBigInt) / LAMPORTS_PER_SOL;
1087
1107
  setFormattedBalance(sol.toFixed(6));
@@ -1101,7 +1121,7 @@ export function useSolanaBalance() {
1101
1121
  return () => {
1102
1122
  cancelled = true;
1103
1123
  };
1104
- }, [providerConnection, rpcUrl, address]);
1124
+ }, [providerConnection, rpcUrl, address, isExternalSolanaWallet, wallet, currentChain]);
1105
1125
  return {
1106
1126
  address,
1107
1127
  lamports,
@@ -1110,6 +1130,124 @@ export function useSolanaBalance() {
1110
1130
  error,
1111
1131
  };
1112
1132
  }
1133
+ /**
1134
+ * Hook to get the full non-zero token list (native + SPL) for the active embedded
1135
+ * Solana address, sourced from Turnkey with an RPC-derived native-only fallback.
1136
+ * Mirrors useSolanaBalance()'s shape. Safe to call unconditionally regardless of the
1137
+ * current chain type - no-ops (returns an empty, non-loading result) when the current
1138
+ * chain isn't Solana or there's no embedded Solana address.
1139
+ */
1140
+ export function useSolanaTokenBalances() {
1141
+ const { currentChain, whoami, solanaWallet, wallet } = useAbstraxnWallet();
1142
+ const [tokens, setTokens] = useState([]);
1143
+ const [loading, setLoading] = useState(false);
1144
+ const [error, setError] = useState(null);
1145
+ const isSolanaChain = currentChain && currentChain.type === 'solana';
1146
+ const isExternalSolanaWallet = !!solanaWallet?.publicKey;
1147
+ const address = !isExternalSolanaWallet ? whoami?.solanaAddress ?? null : null;
1148
+ useEffect(() => {
1149
+ setTokens([]);
1150
+ setError(null);
1151
+ if (!isSolanaChain || !address || !wallet || !currentChain) {
1152
+ return;
1153
+ }
1154
+ const caip2 = chainIdToCaip2(currentChain.id, TURNKEY_SUPPORTED_CAIP2);
1155
+ if (!caip2) {
1156
+ return;
1157
+ }
1158
+ let cancelled = false;
1159
+ const fetchTokens = async () => {
1160
+ setLoading(true);
1161
+ try {
1162
+ const entries = await wallet.getTurnkeyBalances(caip2, address);
1163
+ if (cancelled)
1164
+ return;
1165
+ const nextTokens = [];
1166
+ for (const entry of entries) {
1167
+ try {
1168
+ const isNative = isNativeAssetEntry({ caip19: entry.caip19, symbol: entry.symbol, decimals: entry.decimals }, currentChain.nativeCurrency);
1169
+ const tokenAddress = isNative
1170
+ ? NATIVE_ASSET_SENTINEL
1171
+ : extractAssetAddress({ caip19: entry.caip19 });
1172
+ if (!isNative && tokenAddress === undefined) {
1173
+ console.warn('Skipping unrecognized Solana asset in balance list:', entry.caip19);
1174
+ continue;
1175
+ }
1176
+ nextTokens.push({
1177
+ address: tokenAddress,
1178
+ symbol: entry.symbol ?? '',
1179
+ name: entry.name ?? entry.symbol ?? '',
1180
+ decimals: entry.decimals ?? 9,
1181
+ balance: entry.balance !== undefined ? BigInt(entry.balance) : 0n,
1182
+ isNative,
1183
+ });
1184
+ }
1185
+ catch (entryError) {
1186
+ console.warn('Skipping malformed Solana balance entry:', entryError);
1187
+ }
1188
+ }
1189
+ setTokens(nextTokens);
1190
+ setError(null);
1191
+ }
1192
+ catch (turnkeyError) {
1193
+ console.warn('Turnkey Solana token list fetch failed, falling back to RPC native-only:', turnkeyError);
1194
+ // Fall back to a single native-only entry via RPC, so the list stays
1195
+ // consistent with useSolanaBalance()'s own RPC fallback rather than going
1196
+ // empty on a Turnkey outage.
1197
+ try {
1198
+ const connection = new SolanaConnection(currentChain.rpcUrl, 'confirmed');
1199
+ const lamports = await connection.getBalance(new PublicKey(address), 'confirmed');
1200
+ if (cancelled)
1201
+ return;
1202
+ setTokens([
1203
+ {
1204
+ address: NATIVE_ASSET_SENTINEL,
1205
+ symbol: currentChain.nativeCurrency.symbol,
1206
+ name: currentChain.nativeCurrency.name,
1207
+ decimals: currentChain.nativeCurrency.decimals,
1208
+ balance: BigInt(lamports),
1209
+ isNative: true,
1210
+ },
1211
+ ]);
1212
+ setError(null);
1213
+ }
1214
+ catch (rpcError) {
1215
+ if (cancelled)
1216
+ return;
1217
+ setTokens([]);
1218
+ setError(rpcError instanceof Error ? rpcError : new Error('Failed to load Solana token balances'));
1219
+ }
1220
+ }
1221
+ finally {
1222
+ if (!cancelled) {
1223
+ setLoading(false);
1224
+ }
1225
+ }
1226
+ };
1227
+ fetchTokens();
1228
+ return () => {
1229
+ cancelled = true;
1230
+ };
1231
+ }, [isSolanaChain, address, wallet, currentChain]);
1232
+ return { tokens, loading, error };
1233
+ }
1234
+ /**
1235
+ * Hook to get the full non-zero token list (native + ERC-20) for the current EVM
1236
+ * chain, sourced from AbstraxnProvider's centralized state (Turnkey-first with an
1237
+ * RPC-derived native-only fallback - see AbstraxnProviderInner's refetchWalletBalance).
1238
+ * Safe to call unconditionally regardless of the current chain type - no-ops (returns
1239
+ * an empty, non-loading result) when the current chain isn't EVM.
1240
+ */
1241
+ export function useTokenBalances() {
1242
+ const { currentChain, tokenBalances, tokenBalancesLoading, tokenBalancesError, refetchTokenBalances } = useAbstraxnWallet();
1243
+ const isEvmChain = currentChain && currentChain.type === 'evm';
1244
+ return {
1245
+ tokens: isEvmChain ? tokenBalances : [],
1246
+ loading: isEvmChain ? tokenBalancesLoading : false,
1247
+ error: isEvmChain ? tokenBalancesError : null,
1248
+ refetch: refetchTokenBalances,
1249
+ };
1250
+ }
1113
1251
  function base64ToUint8Array(base64) {
1114
1252
  if (typeof atob !== 'function') {
1115
1253
  throw new Error('Base64 decoding is not available in this environment');
@@ -1794,18 +1932,20 @@ export function useSignTxn(provider) {
1794
1932
  value: valueInWei,
1795
1933
  nonce,
1796
1934
  };
1797
- // Add gas properties if they exist
1935
+ // Add gas properties if they exist. Never set both legacy and EIP-1559 —
1936
+ // viem serializes as type-2 whenever maxFeePerGas is present, which can
1937
+ // underpay Ethermint minimum-global-fee when gasPrice was the real floor.
1798
1938
  const unsignedTx = { ...baseTx };
1799
1939
  if (gas?.gasLimit !== undefined) {
1800
1940
  unsignedTx.gas = gas.gasLimit;
1801
1941
  }
1802
- if (gas?.gasPrice !== undefined) {
1942
+ const hasEip1559 = gas?.maxFeePerGas !== undefined && gas?.maxPriorityFeePerGas !== undefined;
1943
+ const hasLegacy = gas?.gasPrice !== undefined;
1944
+ if (hasLegacy && (!hasEip1559 || gas.gasPrice >= gas.maxFeePerGas)) {
1803
1945
  unsignedTx.gasPrice = gas.gasPrice;
1804
1946
  }
1805
- if (gas?.maxFeePerGas !== undefined) {
1947
+ else if (hasEip1559) {
1806
1948
  unsignedTx.maxFeePerGas = gas.maxFeePerGas;
1807
- }
1808
- if (gas?.maxPriorityFeePerGas !== undefined) {
1809
1949
  unsignedTx.maxPriorityFeePerGas = gas.maxPriorityFeePerGas;
1810
1950
  }
1811
1951
  // Serialize transaction
@@ -1999,18 +2139,20 @@ export function useSignAndSendTxn(provider) {
1999
2139
  value: valueInWei,
2000
2140
  nonce,
2001
2141
  };
2002
- // Add gas properties if they exist
2142
+ // Add gas properties if they exist. Never set both legacy and EIP-1559 —
2143
+ // viem serializes as type-2 whenever maxFeePerGas is present, which can
2144
+ // underpay Ethermint minimum-global-fee when gasPrice was the real floor.
2003
2145
  const unsignedTx = { ...baseTx };
2004
2146
  if (gas?.gasLimit !== undefined) {
2005
2147
  unsignedTx.gas = gas.gasLimit;
2006
2148
  }
2007
- if (gas?.gasPrice !== undefined) {
2149
+ const hasEip1559 = gas?.maxFeePerGas !== undefined && gas?.maxPriorityFeePerGas !== undefined;
2150
+ const hasLegacy = gas?.gasPrice !== undefined;
2151
+ if (hasLegacy && (!hasEip1559 || gas.gasPrice >= gas.maxFeePerGas)) {
2008
2152
  unsignedTx.gasPrice = gas.gasPrice;
2009
2153
  }
2010
- if (gas?.maxFeePerGas !== undefined) {
2154
+ else if (hasEip1559) {
2011
2155
  unsignedTx.maxFeePerGas = gas.maxFeePerGas;
2012
- }
2013
- if (gas?.maxPriorityFeePerGas !== undefined) {
2014
2156
  unsignedTx.maxPriorityFeePerGas = gas.maxPriorityFeePerGas;
2015
2157
  }
2016
2158
  // Serialize transaction
@@ -2057,7 +2199,7 @@ export function useSignAndSendTxn(provider) {
2057
2199
  * ```
2058
2200
  */
2059
2201
  export function useWaitForTxnReceipt(provider) {
2060
- const waitForTxnReceipt = useCallback(async ({ hash, confirmations, timeout, }) => {
2202
+ const waitForTxnReceipt = useCallback(async ({ hash, confirmations, timeout, throwOnRevert = true, }) => {
2061
2203
  if (!provider) {
2062
2204
  throw new Error('Provider (publicClient) is required');
2063
2205
  }
@@ -2080,9 +2222,18 @@ export function useWaitForTxnReceipt(provider) {
2080
2222
  timeout: Math.min(20_000, remaining),
2081
2223
  pollingInterval: 1_500,
2082
2224
  });
2225
+ if (throwOnRevert &&
2226
+ (receipt.status === 'reverted' ||
2227
+ receipt.status === 0 ||
2228
+ receipt.status === '0x0')) {
2229
+ throw new Error('Transaction reverted');
2230
+ }
2083
2231
  return receipt;
2084
2232
  }
2085
2233
  catch (error) {
2234
+ if (error instanceof Error && error.message === 'Transaction reverted') {
2235
+ throw error;
2236
+ }
2086
2237
  lastError = error;
2087
2238
  await waitUntilDocumentVisible();
2088
2239
  await new Promise((resolve) => setTimeout(resolve, 1_000));
@@ -2096,6 +2247,9 @@ export function useWaitForTxnReceipt(provider) {
2096
2247
  : new Error('Failed to wait for transaction receipt');
2097
2248
  }
2098
2249
  catch (error) {
2250
+ if (error instanceof Error && error.message === 'Transaction reverted') {
2251
+ throw error;
2252
+ }
2099
2253
  if (error instanceof Error && error.message.startsWith('Failed to wait for transaction receipt')) {
2100
2254
  throw error;
2101
2255
  }
@@ -2523,7 +2677,12 @@ export function useWriteContract() {
2523
2677
  // Matching without selector is still useful (from + to).
2524
2678
  }
2525
2679
  const cancelPoll = { cancelled: false };
2526
- const sendPromise = sendWrite();
2680
+ const sendPromise = sendWrite().then((hash) => hash, (err) => {
2681
+ if (isUserRejectionError(err))
2682
+ throw toUserRejectionError(err);
2683
+ throw err;
2684
+ });
2685
+ // Success-only chain recovery when WC hash never arrives.
2527
2686
  const pollPromise = pollForOutgoingTx({
2528
2687
  publicClient,
2529
2688
  from: fromAddress,
@@ -2532,12 +2691,11 @@ export function useWriteContract() {
2532
2691
  value,
2533
2692
  startBlock,
2534
2693
  signal: cancelPoll,
2535
- initialDelayMs: 10_000,
2536
- timeoutMs: 90_000,
2694
+ initialDelayMs: 1_000,
2695
+ timeoutMs: 60_000,
2537
2696
  });
2538
2697
  try {
2539
2698
  const hash = await Promise.race([sendPromise, pollPromise]);
2540
- // Poll may win while WC is still hanging — ignore a late reject.
2541
2699
  void sendPromise.then(() => undefined, () => undefined);
2542
2700
  return hash;
2543
2701
  }
@@ -4,7 +4,7 @@
4
4
  * when used in a React project
5
5
  */
6
6
  export { AbstraxnProvider, useAbstraxnWallet } from './AbstraxnProvider';
7
- export { useIsConnected, useAddress, useAuthContext, useChainId, useError, useEnableMfa, useDisableMfa, useAuthMethodLinking, useEmailForOtp, useWallet, useSmartAccountOwner, useExportWallet, useWhoami, useExternalWallet, useExternalWalletBalance, useExternalWalletChain, useExternalWalletInfo, usePublicClient, useWalletClient, useContract, usePrepareRawTxn, useSignTxn, useSignTypedTx, useSignAndSendTxn, useWaitForTxnReceipt, useReadContract, useExternalWalletClient, useWriteContract, useConnectionType, useSwitchChain, useSignMessage, useEstimateGas, useGetGasPrice, useSolanaConnection, useSolanaPublicKey, useSolanaBalance, usePrepareSolanaTxn, useSolanaExternalWallet, usePrepareSolanaProgramTxn, useSolanaProgramTransaction, useSignSolanaTxn, useSignAndSendSolanaTxn, useSolanaGasless, useSignAndSendSolanaGaslessTxn, useWaitForSolanaConfirmation, usePolicies, usePolicy, useCreatePolicy, useUpdatePolicy, useDeletePolicy, usePublishPolicy, useUnpublishPolicy, usePolicyTemplates, } from './hooks';
7
+ export { useIsConnected, useAddress, useAuthContext, useChainId, useError, useEnableMfa, useDisableMfa, useAuthMethodLinking, useEmailForOtp, useWallet, useSmartAccountOwner, useExportWallet, useWhoami, useExternalWallet, useExternalWalletBalance, useExternalWalletChain, useExternalWalletInfo, usePublicClient, useWalletClient, useContract, usePrepareRawTxn, useSignTxn, useSignTypedTx, useSignAndSendTxn, useWaitForTxnReceipt, useReadContract, useExternalWalletClient, useWriteContract, useConnectionType, useSwitchChain, useSignMessage, useEstimateGas, useGetGasPrice, useSolanaConnection, useSolanaPublicKey, useSolanaBalance, useSolanaTokenBalances, useTokenBalances, usePrepareSolanaTxn, useSolanaExternalWallet, usePrepareSolanaProgramTxn, useSolanaProgramTransaction, useSignSolanaTxn, useSignAndSendSolanaTxn, useSolanaGasless, useSignAndSendSolanaGaslessTxn, useWaitForSolanaConfirmation, usePolicies, usePolicy, useCreatePolicy, useUpdatePolicy, useDeletePolicy, usePublishPolicy, useUnpublishPolicy, usePolicyTemplates, } from './hooks';
8
8
  export { ConnectButton } from './ConnectButton';
9
9
  export type { ConnectButtonProps, ConnectButtonVariant, ConnectButtonSize } from './ConnectButton';
10
10
  export { WalletModal } from './WalletModal';
@@ -16,4 +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';
19
+ export { pollForOutgoingTx, clearWalletConnectPendingState, waitUntilDocumentVisible, isWalletConnectConnector, isMobileDevice, forceResumeWalletConnectOnReturn, isUserRejectionError, toUserRejectionError, } from './walletConnectMobile';
package/dist/src/index.js CHANGED
@@ -4,12 +4,12 @@
4
4
  * when used in a React project
5
5
  */
6
6
  export { AbstraxnProvider, useAbstraxnWallet } from './AbstraxnProvider';
7
- export { useIsConnected, useAddress, useAuthContext, useChainId, useError, useEnableMfa, useDisableMfa, useAuthMethodLinking, useEmailForOtp, useWallet, useSmartAccountOwner, useExportWallet, useWhoami, useExternalWallet, useExternalWalletBalance, useExternalWalletChain, useExternalWalletInfo, usePublicClient, useWalletClient, useContract, usePrepareRawTxn, useSignTxn, useSignTypedTx, useSignAndSendTxn, useWaitForTxnReceipt, useReadContract, useExternalWalletClient, useWriteContract, useConnectionType, useSwitchChain, useSignMessage, useEstimateGas, useGetGasPrice, useSolanaConnection, useSolanaPublicKey, useSolanaBalance, usePrepareSolanaTxn, useSolanaExternalWallet, usePrepareSolanaProgramTxn, useSolanaProgramTransaction, useSignSolanaTxn, useSignAndSendSolanaTxn, useSolanaGasless, useSignAndSendSolanaGaslessTxn, useWaitForSolanaConfirmation, usePolicies, usePolicy, useCreatePolicy, useUpdatePolicy, useDeletePolicy, usePublishPolicy, useUnpublishPolicy, usePolicyTemplates, } from './hooks';
7
+ export { useIsConnected, useAddress, useAuthContext, useChainId, useError, useEnableMfa, useDisableMfa, useAuthMethodLinking, useEmailForOtp, useWallet, useSmartAccountOwner, useExportWallet, useWhoami, useExternalWallet, useExternalWalletBalance, useExternalWalletChain, useExternalWalletInfo, usePublicClient, useWalletClient, useContract, usePrepareRawTxn, useSignTxn, useSignTypedTx, useSignAndSendTxn, useWaitForTxnReceipt, useReadContract, useExternalWalletClient, useWriteContract, useConnectionType, useSwitchChain, useSignMessage, useEstimateGas, useGetGasPrice, useSolanaConnection, useSolanaPublicKey, useSolanaBalance, useSolanaTokenBalances, useTokenBalances, usePrepareSolanaTxn, useSolanaExternalWallet, usePrepareSolanaProgramTxn, useSolanaProgramTransaction, useSignSolanaTxn, useSignAndSendSolanaTxn, useSolanaGasless, useSignAndSendSolanaGaslessTxn, useWaitForSolanaConfirmation, usePolicies, usePolicy, useCreatePolicy, useUpdatePolicy, useDeletePolicy, usePublishPolicy, useUnpublishPolicy, usePolicyTemplates, } from './hooks';
8
8
  export { ConnectButton } from './ConnectButton';
9
9
  export { WalletModal } from './WalletModal';
10
10
  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
+ export { pollForOutgoingTx, clearWalletConnectPendingState, waitUntilDocumentVisible, isWalletConnectConnector, isMobileDevice, forceResumeWalletConnectOnReturn, isUserRejectionError, toUserRejectionError, } from './walletConnectMobile';
15
15
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Bridge so non-React signing helpers can request an MFA code via the
3
+ * provider-mounted MfaVerifyModal (replaces window.prompt).
4
+ */
5
+ export type SignMfaCodeRequester = () => Promise<string>;
6
+ export declare function registerSignMfaCodeRequester(requester: SignMfaCodeRequester | null): () => void;
7
+ export declare function requestSignMfaCode(): Promise<string>;
@@ -0,0 +1,20 @@
1
+ let signMfaCodeRequester = null;
2
+ export function registerSignMfaCodeRequester(requester) {
3
+ signMfaCodeRequester = requester;
4
+ return () => {
5
+ if (signMfaCodeRequester === requester) {
6
+ signMfaCodeRequester = null;
7
+ }
8
+ };
9
+ }
10
+ export async function requestSignMfaCode() {
11
+ if (!signMfaCodeRequester) {
12
+ throw new Error("MFA step-up is required to sign, but no MFA UI is available. Ensure AbstraxnProvider is mounted.");
13
+ }
14
+ const code = await signMfaCodeRequester();
15
+ if (!code || code.trim() === "") {
16
+ throw new Error("MFA code is required to continue signing.");
17
+ }
18
+ return code.trim().toUpperCase();
19
+ }
20
+ //# sourceMappingURL=signMfaBridge.js.map
@@ -313,6 +313,10 @@ export interface AbstraxnContextValue {
313
313
  walletBalance?: bigint | null;
314
314
  /** Refetch wallet balance from RPC (Abstraxn or external). Call when UI screen changes (e.g. back button, modal open/close). */
315
315
  refetchBalance?: () => Promise<void>;
316
+ tokenBalances: import("./components/WalletModal/components/TokenSelectorModal").TokenData[];
317
+ tokenBalancesLoading: boolean;
318
+ tokenBalancesError: Error | null;
319
+ refetchTokenBalances: () => Promise<void>;
316
320
  connectionType?: string | null;
317
321
  emailForOtp?: string;
318
322
  setEmailForOtp?: (email: string) => void;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * document.activeElement returns the shadow HOST element when focus is
3
+ * actually on a descendant inside its shadow tree, not the focused element
4
+ * itself. Tab-trap logic comparing `document.activeElement === someElement`
5
+ * silently breaks once that element lives inside a shadow root — this walks
6
+ * down through nested shadow roots to find the real focused element.
7
+ */
8
+ export declare function getDeepActiveElement(): Element | null;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * document.activeElement returns the shadow HOST element when focus is
3
+ * actually on a descendant inside its shadow tree, not the focused element
4
+ * itself. Tab-trap logic comparing `document.activeElement === someElement`
5
+ * silently breaks once that element lives inside a shadow root — this walks
6
+ * down through nested shadow roots to find the real focused element.
7
+ */
8
+ export function getDeepActiveElement() {
9
+ let active = document.activeElement;
10
+ while (active && active.shadowRoot && active.shadowRoot.activeElement) {
11
+ active = active.shadowRoot.activeElement;
12
+ }
13
+ return active;
14
+ }
15
+ //# sourceMappingURL=shadowFocus.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared registry of every currently-mounted shadow root (both React
3
+ * ShadowHost instances and the vanilla OnboardingUIWeb instances). Plain TS,
4
+ * no framework dependency, so both sides can use it.
5
+ *
6
+ * Invariant: every registerShadowRoot() call must be paired with exactly one
7
+ * call to its returned unregister function when — and only when — that
8
+ * specific shadow root is torn down. Violating this either leaks a reference
9
+ * to a detached shadow root (memory leak) or silently drops an active
10
+ * boundary from cross-boundary queries (correctness bug).
11
+ */
12
+ export declare function registerShadowRoot(root: ShadowRoot): () => void;
13
+ /**
14
+ * Unions document.querySelectorAll(selector) with the same query run against
15
+ * every currently-registered shadow root. Restores the pre-shadow-DOM
16
+ * cross-boundary "hide the other open modal overlay" behavior without
17
+ * requiring the separate shadow roots to nest inside one another.
18
+ *
19
+ * One-directional in practice today: only OnboardingUIWeb.showLoadingModal()
20
+ * calls this (to hide an open WalletModal). No symmetric call exists on the
21
+ * WalletModal/React side, matching pre-migration behavior exactly.
22
+ */
23
+ export declare function queryAllAcrossShadowRoots(selector: string): Element[];
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared registry of every currently-mounted shadow root (both React
3
+ * ShadowHost instances and the vanilla OnboardingUIWeb instances). Plain TS,
4
+ * no framework dependency, so both sides can use it.
5
+ *
6
+ * Invariant: every registerShadowRoot() call must be paired with exactly one
7
+ * call to its returned unregister function when — and only when — that
8
+ * specific shadow root is torn down. Violating this either leaks a reference
9
+ * to a detached shadow root (memory leak) or silently drops an active
10
+ * boundary from cross-boundary queries (correctness bug).
11
+ */
12
+ const shadowRoots = new Set();
13
+ export function registerShadowRoot(root) {
14
+ shadowRoots.add(root);
15
+ return () => {
16
+ shadowRoots.delete(root);
17
+ };
18
+ }
19
+ /**
20
+ * Unions document.querySelectorAll(selector) with the same query run against
21
+ * every currently-registered shadow root. Restores the pre-shadow-DOM
22
+ * cross-boundary "hide the other open modal overlay" behavior without
23
+ * requiring the separate shadow roots to nest inside one another.
24
+ *
25
+ * One-directional in practice today: only OnboardingUIWeb.showLoadingModal()
26
+ * calls this (to hide an open WalletModal). No symmetric call exists on the
27
+ * WalletModal/React side, matching pre-migration behavior exactly.
28
+ */
29
+ export function queryAllAcrossShadowRoots(selector) {
30
+ const results = Array.from(document.querySelectorAll(selector));
31
+ for (const root of shadowRoots) {
32
+ results.push(...Array.from(root.querySelectorAll(selector)));
33
+ }
34
+ return results;
35
+ }
36
+ //# sourceMappingURL=shadowRegistry.js.map
@@ -107,9 +107,12 @@ export declare function waitUntilDocumentVisible(): Promise<void>;
107
107
  * (poll won while eth_sendTransaction is still hanging).
108
108
  */
109
109
  export declare function clearWalletConnectPendingState(provider: any): void;
110
+ /** EIP-1193 4001 / WC 5000 (and common message shapes). */
111
+ export declare function isUserRejectionError(error: unknown): boolean;
112
+ export declare function toUserRejectionError(error?: unknown): Error;
110
113
  /**
111
- * When WalletConnect never returns eth_sendTransaction after MetaMask approve,
112
- * scan recent blocks for a matching outgoing tx (from/to + optional calldata prefix).
114
+ * Success-only recovery: find an on-chain hash when WC never returns one.
115
+ * Does not infer reject (that is handled by relay resume + reject fallback).
113
116
  */
114
117
  export declare function pollForOutgoingTx(params: {
115
118
  publicClient: {
@@ -123,7 +126,6 @@ export declare function pollForOutgoingTx(params: {
123
126
  };
124
127
  from: string;
125
128
  to: string;
126
- /** Match tx.input prefix (e.g. 0x + 4-byte selector). */
127
129
  dataPrefix?: string;
128
130
  value?: bigint;
129
131
  startBlock: bigint;
@@ -133,13 +135,15 @@ export declare function pollForOutgoingTx(params: {
133
135
  timeoutMs?: number;
134
136
  initialDelayMs?: number;
135
137
  }): Promise<`0x${string}`>;
138
+ /** Time-bounded force-resume (must not hang on transportOpen). */
139
+ export declare function forceResumeWalletConnectOnReturn(provider: any, timeoutMs?: number): Promise<void>;
136
140
  /**
137
141
  * Patch the EIP-1193 provider so signing/tx requests resume the relay first.
138
142
  * Covers wagmi hooks, viem walletClient, and direct provider.request calls.
139
143
  */
140
144
  export declare function patchWalletConnectProviderForMobile(provider: any): void;
141
145
  /**
142
- * When the dapp tab is shown again (user left MetaMask), restart the relay so
143
- * the pending WalletConnect response can be received.
146
+ * When the dapp tab is shown again, restart the relay so pending
147
+ * approve/reject responses can be delivered (WalletConnect mobile linking).
144
148
  */
145
149
  export declare function subscribeWalletConnectForegroundResume(provider: any): () => void;