@swype-org/react-sdk 0.1.63 → 0.1.67

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/dist/index.js CHANGED
@@ -654,6 +654,24 @@ function normalizeSignature(sig) {
654
654
  );
655
655
  }
656
656
 
657
+ // src/transferPolling.ts
658
+ async function pollTransferTick(params) {
659
+ const fetchTransfer2 = params.fetchTransfer ?? fetchTransfer;
660
+ const token = await params.getAccessToken();
661
+ if (!token) {
662
+ return { kind: "retry" };
663
+ }
664
+ try {
665
+ const transfer = await fetchTransfer2(params.apiBaseUrl, token, params.transferId);
666
+ return { kind: "success", transfer };
667
+ } catch (err) {
668
+ return {
669
+ kind: "error",
670
+ message: err instanceof Error ? err.message : "Polling error"
671
+ };
672
+ }
673
+ }
674
+
657
675
  // src/passkey-delegation.ts
658
676
  var PasskeyIframeBlockedError = class extends Error {
659
677
  constructor(message = "Passkey creation is not supported in this browser context.") {
@@ -956,20 +974,22 @@ function useTransferPolling(intervalMs = 3e3) {
956
974
  }, []);
957
975
  const poll = useCallback(async () => {
958
976
  if (!transferIdRef.current) return;
959
- try {
960
- const token = await getAccessToken();
961
- if (!token) {
962
- setError("Could not get access token");
963
- stopPolling();
964
- return;
965
- }
966
- const t = await fetchTransfer(apiBaseUrl, token, transferIdRef.current);
967
- setTransfer(t);
968
- if (t.status === "COMPLETED" || t.status === "FAILED") {
969
- stopPolling();
970
- }
971
- } catch (err) {
972
- setError(err instanceof Error ? err.message : "Polling error");
977
+ const result = await pollTransferTick({
978
+ apiBaseUrl,
979
+ transferId: transferIdRef.current,
980
+ getAccessToken
981
+ });
982
+ if (result.kind === "retry") {
983
+ return;
984
+ }
985
+ if (result.kind === "error") {
986
+ setError(result.message);
987
+ stopPolling();
988
+ return;
989
+ }
990
+ setError(null);
991
+ setTransfer(result.transfer);
992
+ if (result.transfer.status === "COMPLETED" || result.transfer.status === "FAILED") {
973
993
  stopPolling();
974
994
  }
975
995
  }, [apiBaseUrl, getAccessToken, stopPolling]);
@@ -1580,6 +1600,35 @@ function isMobileUserAgent(userAgent) {
1580
1600
  function shouldUseWalletConnector(options) {
1581
1601
  return options.useWalletConnector ?? !isMobileUserAgent(options.userAgent);
1582
1602
  }
1603
+
1604
+ // src/mobileFlow.ts
1605
+ function hasActiveWallet(accounts) {
1606
+ return accounts.some((account) => account.wallets.some((wallet) => wallet.status === "ACTIVE"));
1607
+ }
1608
+ function resolvePostAuthStep(state) {
1609
+ if (!state.hasPasskey) {
1610
+ return { step: "create-passkey", clearPersistedFlow: false };
1611
+ }
1612
+ if (state.persistedMobileFlow) {
1613
+ return { step: "open-wallet", clearPersistedFlow: false };
1614
+ }
1615
+ if ((state.accounts.length === 0 || !hasActiveWallet(state.accounts)) && !state.connectingNewAccount) {
1616
+ return { step: "wallet-picker", clearPersistedFlow: false };
1617
+ }
1618
+ return { step: "deposit", clearPersistedFlow: false };
1619
+ }
1620
+ function resolveRestoredMobileFlow(transferStatus, isSetup) {
1621
+ if (transferStatus === "AUTHORIZED") {
1622
+ return isSetup ? { kind: "resume-setup-deposit", step: "deposit", clearPersistedFlow: true } : { kind: "resume-confirm-sign", step: "confirm-sign", clearPersistedFlow: true };
1623
+ }
1624
+ if (transferStatus === "COMPLETED") {
1625
+ return { kind: "resume-success", step: "success", clearPersistedFlow: true };
1626
+ }
1627
+ if (transferStatus === "FAILED") {
1628
+ return { kind: "resume-failed", step: "success", clearPersistedFlow: true };
1629
+ }
1630
+ return { kind: "resume-open-wallet", step: "open-wallet", clearPersistedFlow: false };
1631
+ }
1583
1632
  var FOOTER_CSS = `
1584
1633
  .swype-screen-footer {
1585
1634
  padding-bottom: max(24px, env(safe-area-inset-bottom, 24px));
@@ -1908,137 +1957,6 @@ var inputStyle = (tokens, filled) => ({
1908
1957
  caretColor: tokens.borderFocus,
1909
1958
  transition: "border-color 0.15s ease"
1910
1959
  });
1911
- function LimitSlider({
1912
- value,
1913
- min,
1914
- max,
1915
- step = 1,
1916
- onChange,
1917
- ticks,
1918
- disabled
1919
- }) {
1920
- const { tokens } = useSwypeConfig();
1921
- const pct = (value - min) / (max - min) * 100;
1922
- const handleChange = useCallback(
1923
- (e) => onChange(Number(e.target.value)),
1924
- [onChange]
1925
- );
1926
- return /* @__PURE__ */ jsxs("div", { className: "swype-slider-wrap", style: wrapperStyle, children: [
1927
- /* @__PURE__ */ jsxs("div", { style: trackContainerStyle, children: [
1928
- /* @__PURE__ */ jsx("div", { style: trackBgStyle(tokens.border) }),
1929
- /* @__PURE__ */ jsx("div", { style: trackFillStyle(tokens.accent, pct) }),
1930
- /* @__PURE__ */ jsx(
1931
- "input",
1932
- {
1933
- type: "range",
1934
- min,
1935
- max,
1936
- step,
1937
- value,
1938
- onChange: handleChange,
1939
- disabled,
1940
- style: rangeInputStyle
1941
- }
1942
- )
1943
- ] }),
1944
- ticks && ticks.length > 0 && /* @__PURE__ */ jsx("div", { style: ticksStyle, children: ticks.map((tick, i) => {
1945
- const pctPos = (tick - min) / (max - min) * 100;
1946
- const isFirst = i === 0;
1947
- const isLast = i === ticks.length - 1;
1948
- const label = tick % 1 === 0 ? `$${tick}` : `$${tick.toFixed(2)}`;
1949
- return /* @__PURE__ */ jsx(
1950
- "span",
1951
- {
1952
- style: tickLabelStyle(tokens.textMuted, pctPos, isFirst, isLast),
1953
- children: label
1954
- },
1955
- tick
1956
- );
1957
- }) }),
1958
- /* @__PURE__ */ jsx("style", { children: sliderThumbCss(tokens.accent) })
1959
- ] });
1960
- }
1961
- var wrapperStyle = { width: "100%" };
1962
- var trackContainerStyle = {
1963
- position: "relative",
1964
- height: 28,
1965
- display: "flex",
1966
- alignItems: "center"
1967
- };
1968
- var trackBgStyle = (color) => ({
1969
- position: "absolute",
1970
- left: 0,
1971
- right: 0,
1972
- height: 4,
1973
- borderRadius: 2,
1974
- background: color
1975
- });
1976
- var trackFillStyle = (color, pct) => ({
1977
- position: "absolute",
1978
- left: 0,
1979
- width: `${pct}%`,
1980
- height: 4,
1981
- borderRadius: 2,
1982
- background: color
1983
- });
1984
- var rangeInputStyle = {
1985
- position: "absolute",
1986
- left: 0,
1987
- right: 0,
1988
- width: "100%",
1989
- height: 28,
1990
- margin: 0,
1991
- cursor: "pointer",
1992
- zIndex: 2,
1993
- WebkitAppearance: "none",
1994
- appearance: "none",
1995
- background: "transparent"
1996
- };
1997
- var ticksStyle = {
1998
- position: "relative",
1999
- height: 18,
2000
- marginTop: 6
2001
- };
2002
- var tickLabelStyle = (color, pct, isFirst, isLast) => ({
2003
- position: "absolute",
2004
- left: `${pct}%`,
2005
- transform: isFirst ? "none" : isLast ? "translateX(-100%)" : "translateX(-50%)",
2006
- fontSize: "0.72rem",
2007
- fontWeight: 500,
2008
- color,
2009
- whiteSpace: "nowrap"
2010
- });
2011
- var sliderThumbCss = (accent) => `
2012
- .swype-slider-wrap input[type="range"]::-webkit-slider-runnable-track {
2013
- height: 4px;
2014
- background: transparent;
2015
- }
2016
- .swype-slider-wrap input[type="range"]::-webkit-slider-thumb {
2017
- -webkit-appearance: none;
2018
- width: 20px;
2019
- height: 20px;
2020
- border-radius: 50%;
2021
- background: ${accent};
2022
- border: 3px solid #fff;
2023
- box-shadow: 0 2px 6px rgba(0,0,0,0.15);
2024
- cursor: pointer;
2025
- margin-top: -8px;
2026
- }
2027
- .swype-slider-wrap input[type="range"]::-moz-range-track {
2028
- height: 4px;
2029
- background: transparent;
2030
- border: none;
2031
- }
2032
- .swype-slider-wrap input[type="range"]::-moz-range-thumb {
2033
- width: 14px;
2034
- height: 14px;
2035
- border-radius: 50%;
2036
- background: ${accent};
2037
- border: 3px solid #fff;
2038
- box-shadow: 0 2px 6px rgba(0,0,0,0.15);
2039
- cursor: pointer;
2040
- }
2041
- `;
2042
1960
 
2043
1961
  // src/assets/logos.ts
2044
1962
  function svgToDataUri(svg) {
@@ -3000,18 +2918,6 @@ var dividerTextStyle = (color) => ({
3000
2918
  });
3001
2919
  var DEFAULT_MAX = 500;
3002
2920
  var ABSOLUTE_MIN = 1;
3003
- function buildTicks(min, max) {
3004
- if (max <= min) return [min];
3005
- const range = max - min;
3006
- const candidates = [1, 2, 5, 10, 25, 50, 100, 250];
3007
- const step = candidates.find((s) => range / s <= 5) ?? Math.ceil(range / 4);
3008
- const ticks = [];
3009
- for (let v = min; v <= max; v += step) {
3010
- ticks.push(Math.round(v * 100) / 100);
3011
- }
3012
- if (ticks[ticks.length - 1] !== max) ticks.push(max);
3013
- return ticks;
3014
- }
3015
2921
  function SetupScreen({
3016
2922
  availableBalance,
3017
2923
  tokenCount,
@@ -3027,9 +2933,22 @@ function SetupScreen({
3027
2933
  const { tokens } = useSwypeConfig();
3028
2934
  const effectiveMax = Math.floor(Math.min(DEFAULT_MAX, availableBalance > 0 ? availableBalance : DEFAULT_MAX) * 100) / 100;
3029
2935
  const effectiveMin = Math.min(ABSOLUTE_MIN, effectiveMax);
3030
- const sliderStep = effectiveMax <= 10 ? 0.5 : effectiveMax <= 50 ? 1 : 5;
3031
- const ticks = buildTicks(effectiveMin, effectiveMax);
3032
- const [limit, setLimit] = useState(() => Math.min(100, effectiveMax));
2936
+ const [limit, setLimit] = useState(() => effectiveMax);
2937
+ const [editing, setEditing] = useState(false);
2938
+ const [inputValue, setInputValue] = useState("");
2939
+ const inputRef = useRef(null);
2940
+ const startEditing = useCallback(() => {
2941
+ setInputValue(limit.toFixed(2));
2942
+ setEditing(true);
2943
+ requestAnimationFrame(() => inputRef.current?.select());
2944
+ }, [limit]);
2945
+ const commitEdit = useCallback(() => {
2946
+ const parsed = parseFloat(inputValue);
2947
+ if (!isNaN(parsed)) {
2948
+ setLimit(Math.min(effectiveMax, Math.max(effectiveMin, Math.round(parsed * 100) / 100)));
2949
+ }
2950
+ setEditing(false);
2951
+ }, [inputValue, effectiveMax, effectiveMin]);
3033
2952
  return /* @__PURE__ */ jsxs(
3034
2953
  ScreenLayout,
3035
2954
  {
@@ -3085,19 +3004,33 @@ function SetupScreen({
3085
3004
  ] }),
3086
3005
  /* @__PURE__ */ jsxs("div", { style: limitSectionStyle, children: [
3087
3006
  /* @__PURE__ */ jsx("div", { style: limitLabelStyle(tokens.textMuted), children: "Your One-Tap limit" }),
3088
- /* @__PURE__ */ jsxs("div", { style: limitValueStyle(tokens.text), children: [
3007
+ editing ? /* @__PURE__ */ jsxs("div", { style: limitValueStyle(tokens.text), children: [
3089
3008
  "$",
3090
- limit.toFixed(2)
3091
- ] }),
3092
- /* @__PURE__ */ jsx(
3093
- LimitSlider,
3009
+ /* @__PURE__ */ jsx(
3010
+ "input",
3011
+ {
3012
+ ref: inputRef,
3013
+ type: "text",
3014
+ inputMode: "decimal",
3015
+ pattern: "[0-9]*",
3016
+ value: inputValue,
3017
+ onChange: (e) => setInputValue(e.target.value),
3018
+ onBlur: commitEdit,
3019
+ onKeyDown: (e) => {
3020
+ if (e.key === "Enter") commitEdit();
3021
+ },
3022
+ style: limitInputStyle(tokens.text)
3023
+ }
3024
+ )
3025
+ ] }) : /* @__PURE__ */ jsxs(
3026
+ "div",
3094
3027
  {
3095
- value: limit,
3096
- min: effectiveMin,
3097
- max: effectiveMax,
3098
- step: sliderStep,
3099
- ticks,
3100
- onChange: setLimit
3028
+ style: { ...limitValueStyle(tokens.text), cursor: "pointer" },
3029
+ onClick: startEditing,
3030
+ children: [
3031
+ "$",
3032
+ limit.toFixed(2)
3033
+ ]
3101
3034
  }
3102
3035
  )
3103
3036
  ] }),
@@ -3204,6 +3137,19 @@ var limitValueStyle = (color) => ({
3204
3137
  color,
3205
3138
  marginBottom: 12
3206
3139
  });
3140
+ var limitInputStyle = (color) => ({
3141
+ fontSize: "2.2rem",
3142
+ fontWeight: 700,
3143
+ color,
3144
+ background: "transparent",
3145
+ border: "none",
3146
+ borderBottom: "2px solid currentColor",
3147
+ outline: "none",
3148
+ textAlign: "center",
3149
+ width: "5ch",
3150
+ fontFamily: "inherit",
3151
+ padding: 0
3152
+ });
3207
3153
  var bannerWrapStyle = { marginBottom: 16 };
3208
3154
  var linkStyle = (color) => ({
3209
3155
  background: "transparent",
@@ -4109,6 +4055,8 @@ function OpenWalletScreen({
4109
4055
  walletName,
4110
4056
  deeplinkUri,
4111
4057
  loading,
4058
+ error,
4059
+ onRetryStatus,
4112
4060
  onLogout
4113
4061
  }) {
4114
4062
  const { tokens } = useSwypeConfig();
@@ -4129,12 +4077,14 @@ function OpenWalletScreen({
4129
4077
  return /* @__PURE__ */ jsxs(
4130
4078
  ScreenLayout,
4131
4079
  {
4132
- footer: /* @__PURE__ */ jsxs(Fragment, { children: [
4080
+ footer: /* @__PURE__ */ jsxs("div", { style: footerContentStyle, children: [
4081
+ error && /* @__PURE__ */ jsx(InfoBanner, { children: error }),
4133
4082
  !loading && /* @__PURE__ */ jsxs(PrimaryButton, { onClick: handleOpen, children: [
4134
4083
  "Open ",
4135
4084
  displayName
4136
4085
  ] }),
4137
- /* @__PURE__ */ jsx("p", { style: hintStyle3(tokens.textMuted), children: loading ? "Preparing authorization..." : "If your wallet didn't open automatically, tap the button above" })
4086
+ error && onRetryStatus && /* @__PURE__ */ jsx(OutlineButton, { onClick: onRetryStatus, children: "Retry status check" }),
4087
+ /* @__PURE__ */ jsx("p", { style: hintStyle3(tokens.textMuted), children: loading ? "Preparing authorization..." : error ? "Retry the status check after returning to the browser, or reopen your wallet if needed." : "If your wallet didn't open automatically, tap the button above" })
4138
4088
  ] }),
4139
4089
  children: [
4140
4090
  /* @__PURE__ */ jsx(ScreenHeader, { right: /* @__PURE__ */ jsx(SettingsMenu, { onLogout }) }),
@@ -4160,6 +4110,11 @@ var contentStyle6 = {
4160
4110
  textAlign: "center",
4161
4111
  padding: "0 24px"
4162
4112
  };
4113
+ var footerContentStyle = {
4114
+ display: "flex",
4115
+ flexDirection: "column",
4116
+ gap: 12
4117
+ };
4163
4118
  var logoStyle = {
4164
4119
  width: 56,
4165
4120
  height: 56,
@@ -4538,6 +4493,55 @@ function SwypePaymentInner({
4538
4493
  setConnectingNewAccount(false);
4539
4494
  }
4540
4495
  }, [getAccessToken, activeCredentialId, apiBaseUrl, depositAmount]);
4496
+ const enterPersistedMobileFlow = useCallback((persisted, errorMessage) => {
4497
+ setMobileFlow(true);
4498
+ setDeeplinkUri(persisted.deeplinkUri);
4499
+ setSelectedProviderId(persisted.providerId);
4500
+ pollingTransferIdRef.current = persisted.transferId;
4501
+ mobileSetupFlowRef.current = persisted.isSetup;
4502
+ setError(errorMessage ?? null);
4503
+ setStep("open-wallet");
4504
+ polling.startPolling(persisted.transferId);
4505
+ }, [polling]);
4506
+ const handleAuthorizedMobileReturn = useCallback(async (authorizedTransfer, isSetup) => {
4507
+ setTransfer(authorizedTransfer);
4508
+ polling.stopPolling();
4509
+ if (isSetup) {
4510
+ mobileSetupFlowRef.current = false;
4511
+ clearMobileFlowState();
4512
+ try {
4513
+ await reloadAccounts();
4514
+ setError(null);
4515
+ setDeeplinkUri(null);
4516
+ setMobileFlow(false);
4517
+ setStep("deposit");
4518
+ } catch (err) {
4519
+ setError(
4520
+ err instanceof Error ? err.message : "Wallet authorized, but we could not refresh your account yet."
4521
+ );
4522
+ setStep("open-wallet");
4523
+ }
4524
+ return;
4525
+ }
4526
+ mobileSetupFlowRef.current = false;
4527
+ clearMobileFlowState();
4528
+ setError(null);
4529
+ setDeeplinkUri(null);
4530
+ setMobileFlow(false);
4531
+ setStep("confirm-sign");
4532
+ }, [polling.stopPolling, reloadAccounts]);
4533
+ const handleRetryMobileStatus = useCallback(() => {
4534
+ setError(null);
4535
+ const currentTransfer = polling.transfer ?? transfer;
4536
+ if (currentTransfer?.status === "AUTHORIZED") {
4537
+ void handleAuthorizedMobileReturn(currentTransfer, mobileSetupFlowRef.current);
4538
+ return;
4539
+ }
4540
+ const transferIdToResume = pollingTransferIdRef.current ?? currentTransfer?.id;
4541
+ if (transferIdToResume) {
4542
+ polling.startPolling(transferIdToResume);
4543
+ }
4544
+ }, [handleAuthorizedMobileReturn, polling, transfer]);
4541
4545
  useEffect(() => {
4542
4546
  if (depositAmount != null) {
4543
4547
  setAmount(depositAmount.toString());
@@ -4623,19 +4627,70 @@ function SwypePaymentInner({
4623
4627
  let cancelled = false;
4624
4628
  setError(null);
4625
4629
  resetHeadlessLogin();
4626
- const restoreOrDeposit = () => {
4630
+ const restoreOrDeposit = async (credId, token) => {
4627
4631
  const persisted = loadMobileFlowState();
4628
- if (persisted) {
4629
- setMobileFlow(true);
4630
- setDeeplinkUri(persisted.deeplinkUri);
4631
- setSelectedProviderId(persisted.providerId);
4632
- pollingTransferIdRef.current = persisted.transferId;
4633
- mobileSetupFlowRef.current = persisted.isSetup;
4634
- setStep("open-wallet");
4635
- polling.startPolling(persisted.transferId);
4636
- } else {
4637
- setStep("deposit");
4632
+ let accts = [];
4633
+ try {
4634
+ accts = await fetchAccounts(apiBaseUrl, token, credId);
4635
+ if (cancelled) return;
4636
+ } catch {
4637
+ }
4638
+ const resolved = resolvePostAuthStep({
4639
+ hasPasskey: true,
4640
+ accounts: accts,
4641
+ persistedMobileFlow: persisted,
4642
+ connectingNewAccount: false
4643
+ });
4644
+ if (resolved.clearPersistedFlow) {
4645
+ clearMobileFlowState();
4646
+ }
4647
+ if (resolved.step === "open-wallet" && persisted) {
4648
+ try {
4649
+ const existingTransfer = await fetchTransfer(apiBaseUrl, token, persisted.transferId);
4650
+ if (cancelled) return;
4651
+ const mobileResolution = resolveRestoredMobileFlow(
4652
+ existingTransfer.status,
4653
+ persisted.isSetup
4654
+ );
4655
+ if (mobileResolution.kind === "resume-setup-deposit") {
4656
+ await handleAuthorizedMobileReturn(existingTransfer, true);
4657
+ return;
4658
+ }
4659
+ if (mobileResolution.kind === "resume-confirm-sign") {
4660
+ await handleAuthorizedMobileReturn(existingTransfer, false);
4661
+ return;
4662
+ }
4663
+ if (mobileResolution.kind === "resume-success") {
4664
+ clearMobileFlowState();
4665
+ setMobileFlow(false);
4666
+ setDeeplinkUri(null);
4667
+ setTransfer(existingTransfer);
4668
+ setError(null);
4669
+ setStep("success");
4670
+ onComplete?.(existingTransfer);
4671
+ return;
4672
+ }
4673
+ if (mobileResolution.kind === "resume-failed") {
4674
+ clearMobileFlowState();
4675
+ setMobileFlow(false);
4676
+ setDeeplinkUri(null);
4677
+ setTransfer(existingTransfer);
4678
+ setError("Transfer failed.");
4679
+ setStep("success");
4680
+ return;
4681
+ }
4682
+ } catch (err) {
4683
+ if (cancelled) return;
4684
+ enterPersistedMobileFlow(
4685
+ persisted,
4686
+ err instanceof Error ? err.message : "Unable to refresh wallet authorization status."
4687
+ );
4688
+ return;
4689
+ }
4690
+ enterPersistedMobileFlow(persisted);
4691
+ return;
4638
4692
  }
4693
+ setStep(resolved.step);
4639
4694
  };
4640
4695
  const checkPasskey = async () => {
4641
4696
  try {
@@ -4652,7 +4707,7 @@ function SwypePaymentInner({
4652
4707
  return;
4653
4708
  }
4654
4709
  if (activeCredentialId && allPasskeys.some((p) => p.credentialId === activeCredentialId)) {
4655
- restoreOrDeposit();
4710
+ await restoreOrDeposit(activeCredentialId, token);
4656
4711
  return;
4657
4712
  }
4658
4713
  if (cancelled) return;
@@ -4662,7 +4717,7 @@ function SwypePaymentInner({
4662
4717
  if (matched) {
4663
4718
  setActiveCredentialId(matched);
4664
4719
  window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
4665
- restoreOrDeposit();
4720
+ await restoreOrDeposit(matched, token);
4666
4721
  return;
4667
4722
  }
4668
4723
  setStep("create-passkey");
@@ -4674,7 +4729,18 @@ function SwypePaymentInner({
4674
4729
  return () => {
4675
4730
  cancelled = true;
4676
4731
  };
4677
- }, [ready, authenticated, step, apiBaseUrl, getAccessToken, activeCredentialId, resetHeadlessLogin]);
4732
+ }, [
4733
+ ready,
4734
+ authenticated,
4735
+ step,
4736
+ apiBaseUrl,
4737
+ getAccessToken,
4738
+ activeCredentialId,
4739
+ resetHeadlessLogin,
4740
+ enterPersistedMobileFlow,
4741
+ handleAuthorizedMobileReturn,
4742
+ onComplete
4743
+ ]);
4678
4744
  const loadingDataRef = useRef(false);
4679
4745
  useEffect(() => {
4680
4746
  if (!authenticated) return;
@@ -4706,11 +4772,22 @@ function SwypePaymentInner({
4706
4772
  } else if (prov.length > 0 && !connectingNewAccount) {
4707
4773
  setSelectedProviderId(prov[0].id);
4708
4774
  }
4709
- const hasActiveWallet = accts.some(
4710
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
4711
- );
4712
- if ((accts.length === 0 || !hasActiveWallet) && step === "deposit" && !connectingNewAccount) {
4713
- setStep("wallet-picker");
4775
+ const persisted = loadMobileFlowState();
4776
+ const resolved = resolvePostAuthStep({
4777
+ hasPasskey: !!activeCredentialId,
4778
+ accounts: accts,
4779
+ persistedMobileFlow: persisted,
4780
+ mobileSetupInProgress: mobileSetupFlowRef.current,
4781
+ connectingNewAccount
4782
+ });
4783
+ if (resolved.clearPersistedFlow) {
4784
+ clearMobileFlowState();
4785
+ setMobileFlow(false);
4786
+ setDeeplinkUri(null);
4787
+ }
4788
+ const correctableSteps = ["deposit", "wallet-picker", "open-wallet"];
4789
+ if (correctableSteps.includes(step)) {
4790
+ setStep(resolved.step);
4714
4791
  }
4715
4792
  } catch (err) {
4716
4793
  if (!cancelled) {
@@ -4773,22 +4850,8 @@ function SwypePaymentInner({
4773
4850
  if (!mobileFlow) return;
4774
4851
  const polledTransfer = polling.transfer;
4775
4852
  if (!polledTransfer || polledTransfer.status !== "AUTHORIZED") return;
4776
- if (mobileSetupFlowRef.current) {
4777
- mobileSetupFlowRef.current = false;
4778
- clearMobileFlowState();
4779
- setDeeplinkUri(null);
4780
- polling.stopPolling();
4781
- setTransfer(polledTransfer);
4782
- reloadAccounts().catch(() => {
4783
- }).then(() => {
4784
- setMobileFlow(false);
4785
- setStep("deposit");
4786
- });
4787
- return;
4788
- }
4789
- setTransfer(polledTransfer);
4790
- setStep("confirm-sign");
4791
- }, [mobileFlow, polling.transfer, polling.stopPolling, transferSigning, onError, reloadAccounts]);
4853
+ void handleAuthorizedMobileReturn(polledTransfer, mobileSetupFlowRef.current);
4854
+ }, [mobileFlow, polling.transfer, handleAuthorizedMobileReturn]);
4792
4855
  useEffect(() => {
4793
4856
  if (!mobileFlow) return;
4794
4857
  const transferIdToResume = pollingTransferIdRef.current ?? transfer?.id;
@@ -5021,15 +5084,18 @@ function SwypePaymentInner({
5021
5084
  setActiveCredentialId(credentialId);
5022
5085
  window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
5023
5086
  setPasskeyPopupNeeded(false);
5024
- const hasActiveWallet = accounts.some(
5025
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
5026
- );
5027
- if (accounts.length === 0 || !hasActiveWallet) {
5028
- setStep("wallet-picker");
5029
- } else {
5030
- setStep("deposit");
5087
+ const resolved = resolvePostAuthStep({
5088
+ hasPasskey: true,
5089
+ accounts,
5090
+ persistedMobileFlow: loadMobileFlowState(),
5091
+ mobileSetupInProgress: mobileSetupFlowRef.current,
5092
+ connectingNewAccount
5093
+ });
5094
+ if (resolved.clearPersistedFlow) {
5095
+ clearMobileFlowState();
5031
5096
  }
5032
- }, [getAccessToken, apiBaseUrl, accounts]);
5097
+ setStep(resolved.step);
5098
+ }, [getAccessToken, apiBaseUrl, accounts, connectingNewAccount]);
5033
5099
  const handleRegisterPasskey = useCallback(async () => {
5034
5100
  setRegisteringPasskey(true);
5035
5101
  setError(null);
@@ -5227,6 +5293,8 @@ function SwypePaymentInner({
5227
5293
  walletName: providerName,
5228
5294
  deeplinkUri: deeplinkUri ?? "",
5229
5295
  loading: creatingTransfer || !deeplinkUri,
5296
+ error: error || polling.error,
5297
+ onRetryStatus: handleRetryMobileStatus,
5230
5298
  onLogout: handleLogout
5231
5299
  }
5232
5300
  );