@swype-org/react-sdk 0.1.88 → 0.1.89
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.cjs +1415 -1097
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1416 -1098
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useRef, useState, useCallback, useMemo, useContext, useEffect, Component } from 'react';
|
|
1
|
+
import { createContext, useRef, useState, useCallback, useMemo, useContext, useEffect, useReducer, Component } from 'react';
|
|
2
2
|
import { PrivyProvider, usePrivy, useLoginWithEmail, useLoginWithSms, useLoginWithOAuth } from '@privy-io/react-auth';
|
|
3
3
|
import { createConfig, http, WagmiProvider, useConfig, useConnect, useSwitchChain } from 'wagmi';
|
|
4
4
|
import { mainnet, arbitrum, base } from 'wagmi/chains';
|
|
@@ -1580,43 +1580,6 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
1580
1580
|
);
|
|
1581
1581
|
return { signing, signPayload, error, signTransfer: signTransfer2 };
|
|
1582
1582
|
}
|
|
1583
|
-
function Spinner({ size = 40, label }) {
|
|
1584
|
-
const { tokens } = useSwypeConfig();
|
|
1585
|
-
return /* @__PURE__ */ jsxs(
|
|
1586
|
-
"div",
|
|
1587
|
-
{
|
|
1588
|
-
style: {
|
|
1589
|
-
display: "flex",
|
|
1590
|
-
flexDirection: "column",
|
|
1591
|
-
alignItems: "center",
|
|
1592
|
-
gap: "12px"
|
|
1593
|
-
},
|
|
1594
|
-
children: [
|
|
1595
|
-
/* @__PURE__ */ jsx(
|
|
1596
|
-
"div",
|
|
1597
|
-
{
|
|
1598
|
-
style: {
|
|
1599
|
-
width: size,
|
|
1600
|
-
height: size,
|
|
1601
|
-
border: `4px solid ${tokens.bgInput}`,
|
|
1602
|
-
borderTopColor: tokens.accent,
|
|
1603
|
-
borderRightColor: tokens.accent + "66",
|
|
1604
|
-
borderRadius: "50%",
|
|
1605
|
-
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.1)",
|
|
1606
|
-
animation: "swype-spin 0.9s linear infinite"
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
),
|
|
1610
|
-
label && /* @__PURE__ */ jsx("p", { style: { color: tokens.textSecondary, fontSize: "0.875rem", margin: 0 }, children: label }),
|
|
1611
|
-
/* @__PURE__ */ jsx("style", { children: `
|
|
1612
|
-
@keyframes swype-spin {
|
|
1613
|
-
to { transform: rotate(360deg); }
|
|
1614
|
-
}
|
|
1615
|
-
` })
|
|
1616
|
-
]
|
|
1617
|
-
}
|
|
1618
|
-
);
|
|
1619
|
-
}
|
|
1620
1583
|
|
|
1621
1584
|
// src/auth.ts
|
|
1622
1585
|
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -1778,6 +1741,426 @@ function resolveDataLoadAction({
|
|
|
1778
1741
|
}
|
|
1779
1742
|
return "load";
|
|
1780
1743
|
}
|
|
1744
|
+
|
|
1745
|
+
// src/paymentHelpers.ts
|
|
1746
|
+
var ACTIVE_CREDENTIAL_STORAGE_KEY = "swype_active_credential_id";
|
|
1747
|
+
var MOBILE_FLOW_STORAGE_KEY = "swype_mobile_flow";
|
|
1748
|
+
var MIN_SEND_AMOUNT_USD = 0.25;
|
|
1749
|
+
function persistMobileFlowState(data) {
|
|
1750
|
+
try {
|
|
1751
|
+
sessionStorage.setItem(MOBILE_FLOW_STORAGE_KEY, JSON.stringify(data));
|
|
1752
|
+
} catch {
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
function loadMobileFlowState() {
|
|
1756
|
+
try {
|
|
1757
|
+
const raw = sessionStorage.getItem(MOBILE_FLOW_STORAGE_KEY);
|
|
1758
|
+
if (!raw) return null;
|
|
1759
|
+
return JSON.parse(raw);
|
|
1760
|
+
} catch {
|
|
1761
|
+
return null;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function clearMobileFlowState() {
|
|
1765
|
+
try {
|
|
1766
|
+
sessionStorage.removeItem(MOBILE_FLOW_STORAGE_KEY);
|
|
1767
|
+
} catch {
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
function computeSmartDefaults(accts, transferAmount) {
|
|
1771
|
+
if (accts.length === 0) return null;
|
|
1772
|
+
for (const acct of accts) {
|
|
1773
|
+
for (const wallet of acct.wallets) {
|
|
1774
|
+
if (wallet.status === "ACTIVE") {
|
|
1775
|
+
const bestSource = wallet.sources.find(
|
|
1776
|
+
(s) => s.balance.available.amount >= transferAmount
|
|
1777
|
+
);
|
|
1778
|
+
if (bestSource) {
|
|
1779
|
+
return { accountId: acct.id, walletId: wallet.id };
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
let bestAccount = null;
|
|
1785
|
+
let bestWallet = null;
|
|
1786
|
+
let bestBalance = -1;
|
|
1787
|
+
let bestIsActive = false;
|
|
1788
|
+
for (const acct of accts) {
|
|
1789
|
+
for (const wallet of acct.wallets) {
|
|
1790
|
+
const walletBal = wallet.balance.available.amount;
|
|
1791
|
+
const isActive = wallet.status === "ACTIVE";
|
|
1792
|
+
if (walletBal > bestBalance || walletBal === bestBalance && isActive && !bestIsActive) {
|
|
1793
|
+
bestBalance = walletBal;
|
|
1794
|
+
bestAccount = acct;
|
|
1795
|
+
bestWallet = wallet;
|
|
1796
|
+
bestIsActive = isActive;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
if (bestAccount) {
|
|
1801
|
+
return {
|
|
1802
|
+
accountId: bestAccount.id,
|
|
1803
|
+
walletId: bestWallet?.id ?? null
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
return { accountId: accts[0].id, walletId: null };
|
|
1807
|
+
}
|
|
1808
|
+
function parseRawBalance(rawBalance, decimals) {
|
|
1809
|
+
const parsed = Number(rawBalance);
|
|
1810
|
+
if (!Number.isFinite(parsed)) return 0;
|
|
1811
|
+
return parsed / 10 ** decimals;
|
|
1812
|
+
}
|
|
1813
|
+
function buildSelectSourceChoices(options) {
|
|
1814
|
+
const chainChoices = [];
|
|
1815
|
+
const chainIndexByName = /* @__PURE__ */ new Map();
|
|
1816
|
+
for (const option of options) {
|
|
1817
|
+
const { chainName, tokenSymbol } = option;
|
|
1818
|
+
const balance = parseRawBalance(option.rawBalance, option.decimals);
|
|
1819
|
+
let chainChoice;
|
|
1820
|
+
const existingIdx = chainIndexByName.get(chainName);
|
|
1821
|
+
if (existingIdx === void 0) {
|
|
1822
|
+
chainChoice = { chainName, balance: 0, tokens: [] };
|
|
1823
|
+
chainIndexByName.set(chainName, chainChoices.length);
|
|
1824
|
+
chainChoices.push(chainChoice);
|
|
1825
|
+
} else {
|
|
1826
|
+
chainChoice = chainChoices[existingIdx];
|
|
1827
|
+
}
|
|
1828
|
+
chainChoice.balance += balance;
|
|
1829
|
+
const existing = chainChoice.tokens.find((t) => t.tokenSymbol === tokenSymbol);
|
|
1830
|
+
if (existing) {
|
|
1831
|
+
existing.balance += balance;
|
|
1832
|
+
} else {
|
|
1833
|
+
chainChoice.tokens.push({ tokenSymbol, balance });
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return chainChoices;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/paymentReducer.ts
|
|
1840
|
+
function deriveSourceTypeAndId(state) {
|
|
1841
|
+
if (state.connectingNewAccount) {
|
|
1842
|
+
return { sourceType: "providerId", sourceId: state.selectedProviderId ?? "" };
|
|
1843
|
+
}
|
|
1844
|
+
if (state.selectedWalletId) {
|
|
1845
|
+
return { sourceType: "walletId", sourceId: state.selectedWalletId };
|
|
1846
|
+
}
|
|
1847
|
+
if (state.selectedAccountId) {
|
|
1848
|
+
return { sourceType: "accountId", sourceId: state.selectedAccountId };
|
|
1849
|
+
}
|
|
1850
|
+
return { sourceType: "providerId", sourceId: state.selectedProviderId ?? "" };
|
|
1851
|
+
}
|
|
1852
|
+
function createInitialState(config) {
|
|
1853
|
+
return {
|
|
1854
|
+
step: "login",
|
|
1855
|
+
error: null,
|
|
1856
|
+
providers: [],
|
|
1857
|
+
accounts: [],
|
|
1858
|
+
chains: [],
|
|
1859
|
+
loadingData: false,
|
|
1860
|
+
selectedAccountId: null,
|
|
1861
|
+
selectedWalletId: null,
|
|
1862
|
+
selectedProviderId: null,
|
|
1863
|
+
connectingNewAccount: false,
|
|
1864
|
+
amount: config.depositAmount != null ? config.depositAmount.toString() : "",
|
|
1865
|
+
transfer: null,
|
|
1866
|
+
creatingTransfer: false,
|
|
1867
|
+
registeringPasskey: false,
|
|
1868
|
+
verifyingPasskeyPopup: false,
|
|
1869
|
+
passkeyPopupNeeded: config.passkeyPopupNeeded,
|
|
1870
|
+
activeCredentialId: config.activeCredentialId,
|
|
1871
|
+
knownCredentialIds: [],
|
|
1872
|
+
verificationTarget: null,
|
|
1873
|
+
oneTapLimit: 100,
|
|
1874
|
+
mobileFlow: false,
|
|
1875
|
+
deeplinkUri: null,
|
|
1876
|
+
increasingLimit: false
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
function paymentReducer(state, action) {
|
|
1880
|
+
switch (action.type) {
|
|
1881
|
+
// ── Auth ──────────────────────────────────────────────────────
|
|
1882
|
+
case "CODE_SENT":
|
|
1883
|
+
return {
|
|
1884
|
+
...state,
|
|
1885
|
+
verificationTarget: action.target,
|
|
1886
|
+
error: null,
|
|
1887
|
+
step: "otp-verify"
|
|
1888
|
+
};
|
|
1889
|
+
case "BACK_TO_LOGIN":
|
|
1890
|
+
return {
|
|
1891
|
+
...state,
|
|
1892
|
+
verificationTarget: null,
|
|
1893
|
+
error: null,
|
|
1894
|
+
step: "login"
|
|
1895
|
+
};
|
|
1896
|
+
// ── Passkey ──────────────────────────────────────────────────
|
|
1897
|
+
case "PASSKEY_CONFIG_LOADED":
|
|
1898
|
+
return {
|
|
1899
|
+
...state,
|
|
1900
|
+
knownCredentialIds: action.knownIds,
|
|
1901
|
+
oneTapLimit: action.oneTapLimit ?? state.oneTapLimit
|
|
1902
|
+
};
|
|
1903
|
+
case "PASSKEY_ACTIVATED":
|
|
1904
|
+
return {
|
|
1905
|
+
...state,
|
|
1906
|
+
activeCredentialId: action.credentialId,
|
|
1907
|
+
passkeyPopupNeeded: false
|
|
1908
|
+
};
|
|
1909
|
+
case "SET_PASSKEY_POPUP_NEEDED":
|
|
1910
|
+
return { ...state, passkeyPopupNeeded: action.needed };
|
|
1911
|
+
case "SET_REGISTERING_PASSKEY":
|
|
1912
|
+
return { ...state, registeringPasskey: action.value };
|
|
1913
|
+
case "SET_VERIFYING_PASSKEY":
|
|
1914
|
+
return { ...state, verifyingPasskeyPopup: action.value };
|
|
1915
|
+
// ── Data loading ─────────────────────────────────────────────
|
|
1916
|
+
case "DATA_LOAD_START":
|
|
1917
|
+
return { ...state, loadingData: true, error: null };
|
|
1918
|
+
case "DATA_LOADED": {
|
|
1919
|
+
const next = {
|
|
1920
|
+
...state,
|
|
1921
|
+
providers: action.providers,
|
|
1922
|
+
accounts: action.accounts,
|
|
1923
|
+
chains: action.chains
|
|
1924
|
+
};
|
|
1925
|
+
if (action.defaults) {
|
|
1926
|
+
next.selectedAccountId = action.defaults.accountId;
|
|
1927
|
+
next.selectedWalletId = action.defaults.walletId;
|
|
1928
|
+
} else if (action.fallbackProviderId && !state.connectingNewAccount) {
|
|
1929
|
+
next.selectedProviderId = action.fallbackProviderId;
|
|
1930
|
+
}
|
|
1931
|
+
if (action.clearMobileState) {
|
|
1932
|
+
next.mobileFlow = false;
|
|
1933
|
+
next.deeplinkUri = null;
|
|
1934
|
+
}
|
|
1935
|
+
if (action.resolvedStep !== void 0) {
|
|
1936
|
+
next.step = action.resolvedStep;
|
|
1937
|
+
}
|
|
1938
|
+
return next;
|
|
1939
|
+
}
|
|
1940
|
+
case "DATA_LOAD_END":
|
|
1941
|
+
return { ...state, loadingData: false };
|
|
1942
|
+
case "ACCOUNTS_RELOADED": {
|
|
1943
|
+
const next = {
|
|
1944
|
+
...state,
|
|
1945
|
+
accounts: action.accounts,
|
|
1946
|
+
providers: action.providers
|
|
1947
|
+
};
|
|
1948
|
+
if (action.defaults) {
|
|
1949
|
+
next.selectedAccountId = action.defaults.accountId;
|
|
1950
|
+
next.selectedWalletId = action.defaults.walletId;
|
|
1951
|
+
next.connectingNewAccount = false;
|
|
1952
|
+
}
|
|
1953
|
+
return next;
|
|
1954
|
+
}
|
|
1955
|
+
// ── Source selection ──────────────────────────────────────────
|
|
1956
|
+
case "SELECT_PROVIDER":
|
|
1957
|
+
return {
|
|
1958
|
+
...state,
|
|
1959
|
+
selectedProviderId: action.providerId,
|
|
1960
|
+
selectedAccountId: null,
|
|
1961
|
+
connectingNewAccount: true
|
|
1962
|
+
};
|
|
1963
|
+
case "SELECT_ACCOUNT":
|
|
1964
|
+
return {
|
|
1965
|
+
...state,
|
|
1966
|
+
selectedAccountId: action.accountId,
|
|
1967
|
+
selectedWalletId: action.walletId,
|
|
1968
|
+
connectingNewAccount: false,
|
|
1969
|
+
step: "deposit"
|
|
1970
|
+
};
|
|
1971
|
+
// ── Transfer lifecycle ───────────────────────────────────────
|
|
1972
|
+
case "PAY_STARTED":
|
|
1973
|
+
return {
|
|
1974
|
+
...state,
|
|
1975
|
+
step: action.isSetupRedirect ? "open-wallet" : "processing",
|
|
1976
|
+
error: null,
|
|
1977
|
+
creatingTransfer: true,
|
|
1978
|
+
deeplinkUri: null,
|
|
1979
|
+
mobileFlow: false
|
|
1980
|
+
};
|
|
1981
|
+
case "PAY_ENDED":
|
|
1982
|
+
return { ...state, creatingTransfer: false };
|
|
1983
|
+
case "PAY_ERROR":
|
|
1984
|
+
return {
|
|
1985
|
+
...state,
|
|
1986
|
+
error: action.error,
|
|
1987
|
+
step: action.fallbackStep
|
|
1988
|
+
};
|
|
1989
|
+
case "TRANSFER_CREATED":
|
|
1990
|
+
return { ...state, transfer: action.transfer };
|
|
1991
|
+
case "TRANSFER_SIGNED":
|
|
1992
|
+
return { ...state, transfer: action.transfer };
|
|
1993
|
+
case "TRANSFER_COMPLETED":
|
|
1994
|
+
return {
|
|
1995
|
+
...state,
|
|
1996
|
+
transfer: action.transfer,
|
|
1997
|
+
step: "success",
|
|
1998
|
+
mobileFlow: false,
|
|
1999
|
+
deeplinkUri: null
|
|
2000
|
+
};
|
|
2001
|
+
case "TRANSFER_FAILED":
|
|
2002
|
+
return {
|
|
2003
|
+
...state,
|
|
2004
|
+
transfer: action.transfer,
|
|
2005
|
+
error: action.error,
|
|
2006
|
+
step: "success",
|
|
2007
|
+
mobileFlow: false,
|
|
2008
|
+
deeplinkUri: null
|
|
2009
|
+
};
|
|
2010
|
+
case "PROCESSING_TIMEOUT":
|
|
2011
|
+
return { ...state, error: action.error, step: "deposit" };
|
|
2012
|
+
case "CONFIRM_SIGN_SUCCESS":
|
|
2013
|
+
return {
|
|
2014
|
+
...state,
|
|
2015
|
+
transfer: action.transfer,
|
|
2016
|
+
step: "processing",
|
|
2017
|
+
mobileFlow: false,
|
|
2018
|
+
deeplinkUri: null
|
|
2019
|
+
};
|
|
2020
|
+
// ── Mobile flow ──────────────────────────────────────────────
|
|
2021
|
+
case "MOBILE_DEEPLINK_READY":
|
|
2022
|
+
return {
|
|
2023
|
+
...state,
|
|
2024
|
+
mobileFlow: true,
|
|
2025
|
+
deeplinkUri: action.deeplinkUri,
|
|
2026
|
+
step: "open-wallet"
|
|
2027
|
+
};
|
|
2028
|
+
case "MOBILE_SETUP_COMPLETE":
|
|
2029
|
+
return {
|
|
2030
|
+
...state,
|
|
2031
|
+
transfer: action.transfer,
|
|
2032
|
+
error: null,
|
|
2033
|
+
mobileFlow: false,
|
|
2034
|
+
deeplinkUri: null,
|
|
2035
|
+
step: "deposit"
|
|
2036
|
+
};
|
|
2037
|
+
case "MOBILE_SIGN_READY":
|
|
2038
|
+
return {
|
|
2039
|
+
...state,
|
|
2040
|
+
transfer: action.transfer,
|
|
2041
|
+
error: null,
|
|
2042
|
+
mobileFlow: false,
|
|
2043
|
+
deeplinkUri: null,
|
|
2044
|
+
step: "confirm-sign"
|
|
2045
|
+
};
|
|
2046
|
+
case "CLEAR_MOBILE_STATE":
|
|
2047
|
+
return { ...state, mobileFlow: false, deeplinkUri: null };
|
|
2048
|
+
case "ENTER_MOBILE_FLOW":
|
|
2049
|
+
return {
|
|
2050
|
+
...state,
|
|
2051
|
+
mobileFlow: true,
|
|
2052
|
+
deeplinkUri: action.deeplinkUri,
|
|
2053
|
+
selectedProviderId: action.providerId,
|
|
2054
|
+
error: action.error ?? null,
|
|
2055
|
+
step: "open-wallet"
|
|
2056
|
+
};
|
|
2057
|
+
case "MOBILE_RESUME_SUCCESS":
|
|
2058
|
+
return {
|
|
2059
|
+
...state,
|
|
2060
|
+
transfer: action.transfer,
|
|
2061
|
+
error: null,
|
|
2062
|
+
mobileFlow: false,
|
|
2063
|
+
deeplinkUri: null,
|
|
2064
|
+
step: "success"
|
|
2065
|
+
};
|
|
2066
|
+
case "MOBILE_RESUME_FAILED":
|
|
2067
|
+
return {
|
|
2068
|
+
...state,
|
|
2069
|
+
transfer: action.transfer,
|
|
2070
|
+
error: "Transfer failed.",
|
|
2071
|
+
mobileFlow: false,
|
|
2072
|
+
deeplinkUri: null,
|
|
2073
|
+
step: "success"
|
|
2074
|
+
};
|
|
2075
|
+
case "MOBILE_RESUME_PROCESSING":
|
|
2076
|
+
return {
|
|
2077
|
+
...state,
|
|
2078
|
+
transfer: action.transfer,
|
|
2079
|
+
error: null,
|
|
2080
|
+
mobileFlow: false,
|
|
2081
|
+
deeplinkUri: null,
|
|
2082
|
+
step: "processing"
|
|
2083
|
+
};
|
|
2084
|
+
// ── Increase limit ───────────────────────────────────────────
|
|
2085
|
+
case "SET_INCREASING_LIMIT":
|
|
2086
|
+
return { ...state, increasingLimit: action.value };
|
|
2087
|
+
case "INCREASE_LIMIT_DEEPLINK":
|
|
2088
|
+
return {
|
|
2089
|
+
...state,
|
|
2090
|
+
transfer: action.transfer,
|
|
2091
|
+
mobileFlow: true,
|
|
2092
|
+
deeplinkUri: action.deeplinkUri
|
|
2093
|
+
};
|
|
2094
|
+
// ── Navigation & error ───────────────────────────────────────
|
|
2095
|
+
case "NAVIGATE":
|
|
2096
|
+
return { ...state, step: action.step };
|
|
2097
|
+
case "SET_ERROR":
|
|
2098
|
+
return { ...state, error: action.error };
|
|
2099
|
+
// ── Lifecycle ────────────────────────────────────────────────
|
|
2100
|
+
case "NEW_PAYMENT":
|
|
2101
|
+
return {
|
|
2102
|
+
...state,
|
|
2103
|
+
step: "deposit",
|
|
2104
|
+
transfer: null,
|
|
2105
|
+
error: null,
|
|
2106
|
+
amount: action.depositAmount != null ? action.depositAmount.toString() : "",
|
|
2107
|
+
mobileFlow: false,
|
|
2108
|
+
deeplinkUri: null,
|
|
2109
|
+
connectingNewAccount: false,
|
|
2110
|
+
selectedWalletId: null,
|
|
2111
|
+
selectedAccountId: action.firstAccountId
|
|
2112
|
+
};
|
|
2113
|
+
case "LOGOUT":
|
|
2114
|
+
return {
|
|
2115
|
+
...createInitialState({
|
|
2116
|
+
depositAmount: action.depositAmount,
|
|
2117
|
+
passkeyPopupNeeded: state.passkeyPopupNeeded,
|
|
2118
|
+
activeCredentialId: null
|
|
2119
|
+
})
|
|
2120
|
+
};
|
|
2121
|
+
case "SYNC_AMOUNT":
|
|
2122
|
+
return { ...state, amount: action.amount };
|
|
2123
|
+
default:
|
|
2124
|
+
return state;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
function Spinner({ size = 40, label }) {
|
|
2128
|
+
const { tokens } = useSwypeConfig();
|
|
2129
|
+
return /* @__PURE__ */ jsxs(
|
|
2130
|
+
"div",
|
|
2131
|
+
{
|
|
2132
|
+
style: {
|
|
2133
|
+
display: "flex",
|
|
2134
|
+
flexDirection: "column",
|
|
2135
|
+
alignItems: "center",
|
|
2136
|
+
gap: "12px"
|
|
2137
|
+
},
|
|
2138
|
+
children: [
|
|
2139
|
+
/* @__PURE__ */ jsx(
|
|
2140
|
+
"div",
|
|
2141
|
+
{
|
|
2142
|
+
style: {
|
|
2143
|
+
width: size,
|
|
2144
|
+
height: size,
|
|
2145
|
+
border: `4px solid ${tokens.bgInput}`,
|
|
2146
|
+
borderTopColor: tokens.accent,
|
|
2147
|
+
borderRightColor: tokens.accent + "66",
|
|
2148
|
+
borderRadius: "50%",
|
|
2149
|
+
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.1)",
|
|
2150
|
+
animation: "swype-spin 0.9s linear infinite"
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
),
|
|
2154
|
+
label && /* @__PURE__ */ jsx("p", { style: { color: tokens.textSecondary, fontSize: "0.875rem", margin: 0 }, children: label }),
|
|
2155
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
2156
|
+
@keyframes swype-spin {
|
|
2157
|
+
to { transform: rotate(360deg); }
|
|
2158
|
+
}
|
|
2159
|
+
` })
|
|
2160
|
+
]
|
|
2161
|
+
}
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
1781
2164
|
var FOOTER_CSS = `
|
|
1782
2165
|
.swype-screen-footer {
|
|
1783
2166
|
padding-bottom: max(24px, env(safe-area-inset-bottom, 24px));
|
|
@@ -4494,22 +4877,259 @@ var errorStyle2 = (color) => ({
|
|
|
4494
4877
|
color: "#ef4444",
|
|
4495
4878
|
margin: "8px 0 0"
|
|
4496
4879
|
});
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4880
|
+
function CenteredSpinner({ label }) {
|
|
4881
|
+
return /* @__PURE__ */ jsx(ScreenLayout, { children: /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: "48px 0", flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx(Spinner, { label }) }) });
|
|
4882
|
+
}
|
|
4883
|
+
function StepRenderer({
|
|
4884
|
+
state,
|
|
4885
|
+
ready,
|
|
4886
|
+
authenticated,
|
|
4887
|
+
activeOtpStatus,
|
|
4888
|
+
pollingTransfer,
|
|
4889
|
+
pollingError,
|
|
4890
|
+
authExecutorError,
|
|
4891
|
+
transferSigningSigning,
|
|
4892
|
+
transferSigningError,
|
|
4893
|
+
pendingConnections,
|
|
4894
|
+
sourceName,
|
|
4895
|
+
sourceAddress,
|
|
4896
|
+
sourceVerified,
|
|
4897
|
+
maxSourceBalance,
|
|
4898
|
+
tokenCount,
|
|
4899
|
+
selectedAccount,
|
|
4900
|
+
selectSourceChoices,
|
|
4901
|
+
selectSourceRecommended,
|
|
4902
|
+
authInput,
|
|
4903
|
+
otpCode,
|
|
4904
|
+
selectSourceChainName,
|
|
4905
|
+
selectSourceTokenSymbol,
|
|
4906
|
+
merchantName,
|
|
4907
|
+
onBack,
|
|
4908
|
+
onDismiss,
|
|
4909
|
+
autoCloseSeconds,
|
|
4910
|
+
depositAmount,
|
|
4911
|
+
handlers
|
|
4912
|
+
}) {
|
|
4913
|
+
const { step } = state;
|
|
4914
|
+
if (!ready) {
|
|
4915
|
+
return /* @__PURE__ */ jsx(CenteredSpinner, { label: "Initializing..." });
|
|
4507
4916
|
}
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4917
|
+
if (step === "login" && !authenticated) {
|
|
4918
|
+
return /* @__PURE__ */ jsx(
|
|
4919
|
+
LoginScreen,
|
|
4920
|
+
{
|
|
4921
|
+
authInput,
|
|
4922
|
+
onAuthInputChange: handlers.onSetAuthInput,
|
|
4923
|
+
onSubmit: handlers.onSendLoginCode,
|
|
4924
|
+
sending: activeOtpStatus === "sending-code",
|
|
4925
|
+
error: state.error,
|
|
4926
|
+
onBack,
|
|
4927
|
+
merchantInitials: merchantName ? merchantName.slice(0, 2).toUpperCase() : void 0
|
|
4928
|
+
}
|
|
4929
|
+
);
|
|
4930
|
+
}
|
|
4931
|
+
if (step === "otp-verify" && !authenticated) {
|
|
4932
|
+
return /* @__PURE__ */ jsx(
|
|
4933
|
+
OtpVerifyScreen,
|
|
4934
|
+
{
|
|
4935
|
+
maskedIdentifier: state.verificationTarget ? maskAuthIdentifier(state.verificationTarget) : "",
|
|
4936
|
+
otpCode,
|
|
4937
|
+
onOtpChange: (code) => {
|
|
4938
|
+
handlers.onSetOtpCode(code);
|
|
4939
|
+
},
|
|
4940
|
+
onVerify: handlers.onVerifyLoginCode,
|
|
4941
|
+
onResend: handlers.onResendLoginCode,
|
|
4942
|
+
onBack: handlers.onBackFromOtp,
|
|
4943
|
+
verifying: activeOtpStatus === "submitting-code",
|
|
4944
|
+
error: state.error
|
|
4945
|
+
}
|
|
4946
|
+
);
|
|
4947
|
+
}
|
|
4948
|
+
if ((step === "login" || step === "otp-verify") && authenticated) {
|
|
4949
|
+
return /* @__PURE__ */ jsx(CenteredSpinner, { label: "Verifying your passkey..." });
|
|
4950
|
+
}
|
|
4951
|
+
if (step === "verify-passkey") {
|
|
4952
|
+
return /* @__PURE__ */ jsx(
|
|
4953
|
+
VerifyPasskeyScreen,
|
|
4954
|
+
{
|
|
4955
|
+
onVerify: handlers.onVerifyPasskeyViaPopup,
|
|
4956
|
+
onBack: handlers.onLogout,
|
|
4957
|
+
verifying: state.verifyingPasskeyPopup,
|
|
4958
|
+
error: state.error
|
|
4959
|
+
}
|
|
4960
|
+
);
|
|
4961
|
+
}
|
|
4962
|
+
if (step === "create-passkey") {
|
|
4963
|
+
return /* @__PURE__ */ jsx(
|
|
4964
|
+
CreatePasskeyScreen,
|
|
4965
|
+
{
|
|
4966
|
+
onCreatePasskey: handlers.onRegisterPasskey,
|
|
4967
|
+
onBack: handlers.onLogout,
|
|
4968
|
+
creating: state.registeringPasskey,
|
|
4969
|
+
error: state.error,
|
|
4970
|
+
popupFallback: state.passkeyPopupNeeded,
|
|
4971
|
+
onCreatePasskeyViaPopup: handlers.onCreatePasskeyViaPopup
|
|
4972
|
+
}
|
|
4973
|
+
);
|
|
4974
|
+
}
|
|
4975
|
+
if (step === "wallet-picker") {
|
|
4976
|
+
return /* @__PURE__ */ jsx(
|
|
4977
|
+
WalletPickerScreen,
|
|
4978
|
+
{
|
|
4979
|
+
providers: state.providers,
|
|
4980
|
+
pendingConnections,
|
|
4981
|
+
loading: state.creatingTransfer,
|
|
4982
|
+
onSelectProvider: handlers.onSelectProvider,
|
|
4983
|
+
onContinueConnection: handlers.onContinueConnection,
|
|
4984
|
+
onBack: () => handlers.onNavigate(state.activeCredentialId ? "deposit" : "create-passkey")
|
|
4985
|
+
}
|
|
4986
|
+
);
|
|
4987
|
+
}
|
|
4988
|
+
if (step === "open-wallet") {
|
|
4989
|
+
const providerName = state.providers.find((p) => p.id === state.selectedProviderId)?.name ?? null;
|
|
4990
|
+
return /* @__PURE__ */ jsx(
|
|
4991
|
+
OpenWalletScreen,
|
|
4992
|
+
{
|
|
4993
|
+
walletName: providerName,
|
|
4994
|
+
deeplinkUri: state.deeplinkUri ?? "",
|
|
4995
|
+
loading: state.creatingTransfer || !state.deeplinkUri,
|
|
4996
|
+
error: state.error || pollingError,
|
|
4997
|
+
onRetryStatus: handlers.onRetryMobileStatus,
|
|
4998
|
+
onLogout: handlers.onLogout
|
|
4999
|
+
}
|
|
5000
|
+
);
|
|
5001
|
+
}
|
|
5002
|
+
if (step === "confirm-sign") {
|
|
5003
|
+
const providerName = state.providers.find((p) => p.id === state.selectedProviderId)?.name ?? null;
|
|
5004
|
+
return /* @__PURE__ */ jsx(
|
|
5005
|
+
ConfirmSignScreen,
|
|
5006
|
+
{
|
|
5007
|
+
walletName: providerName,
|
|
5008
|
+
signing: transferSigningSigning,
|
|
5009
|
+
error: state.error || transferSigningError,
|
|
5010
|
+
onSign: handlers.onConfirmSign,
|
|
5011
|
+
onLogout: handlers.onLogout
|
|
5012
|
+
}
|
|
5013
|
+
);
|
|
5014
|
+
}
|
|
5015
|
+
if (step === "deposit") {
|
|
5016
|
+
if (state.loadingData) {
|
|
5017
|
+
return /* @__PURE__ */ jsx(CenteredSpinner, { label: "Loading..." });
|
|
5018
|
+
}
|
|
5019
|
+
const parsedAmt = depositAmount != null ? depositAmount : 5;
|
|
5020
|
+
return /* @__PURE__ */ jsx(
|
|
5021
|
+
DepositScreen,
|
|
5022
|
+
{
|
|
5023
|
+
merchantName,
|
|
5024
|
+
sourceName,
|
|
5025
|
+
sourceAddress,
|
|
5026
|
+
sourceVerified,
|
|
5027
|
+
availableBalance: maxSourceBalance,
|
|
5028
|
+
remainingLimit: selectedAccount?.remainingAllowance ?? state.oneTapLimit,
|
|
5029
|
+
tokenCount,
|
|
5030
|
+
initialAmount: parsedAmt,
|
|
5031
|
+
processing: state.creatingTransfer,
|
|
5032
|
+
error: state.error,
|
|
5033
|
+
onDeposit: handlers.onPay,
|
|
5034
|
+
onChangeSource: () => handlers.onNavigate("wallet-picker"),
|
|
5035
|
+
onSwitchWallet: () => handlers.onNavigate("wallet-picker"),
|
|
5036
|
+
onBack: onBack ?? (() => handlers.onLogout()),
|
|
5037
|
+
onLogout: handlers.onLogout,
|
|
5038
|
+
onIncreaseLimit: handlers.onIncreaseLimit,
|
|
5039
|
+
increasingLimit: state.increasingLimit
|
|
5040
|
+
}
|
|
5041
|
+
);
|
|
5042
|
+
}
|
|
5043
|
+
if (step === "processing") {
|
|
5044
|
+
const polledStatus = pollingTransfer?.status;
|
|
5045
|
+
const transferPhase = state.creatingTransfer ? "creating" : polledStatus === "SENDING" || polledStatus === "SENT" ? "sent" : "verifying";
|
|
5046
|
+
return /* @__PURE__ */ jsx(
|
|
5047
|
+
TransferStatusScreen,
|
|
5048
|
+
{
|
|
5049
|
+
phase: transferPhase,
|
|
5050
|
+
error: state.error || authExecutorError || transferSigningError || pollingError,
|
|
5051
|
+
onLogout: handlers.onLogout
|
|
5052
|
+
}
|
|
5053
|
+
);
|
|
5054
|
+
}
|
|
5055
|
+
if (step === "select-source") {
|
|
5056
|
+
return /* @__PURE__ */ jsx(
|
|
5057
|
+
SelectSourceScreen,
|
|
5058
|
+
{
|
|
5059
|
+
choices: selectSourceChoices,
|
|
5060
|
+
selectedChainName: selectSourceChainName,
|
|
5061
|
+
selectedTokenSymbol: selectSourceTokenSymbol,
|
|
5062
|
+
recommended: selectSourceRecommended,
|
|
5063
|
+
onChainChange: handlers.onSelectSourceChainChange,
|
|
5064
|
+
onTokenChange: handlers.onSetSelectSourceTokenSymbol,
|
|
5065
|
+
onConfirm: handlers.onConfirmSelectSource,
|
|
5066
|
+
onLogout: handlers.onLogout
|
|
5067
|
+
}
|
|
5068
|
+
);
|
|
5069
|
+
}
|
|
5070
|
+
if (step === "success") {
|
|
5071
|
+
const succeeded = state.transfer?.status === "COMPLETED";
|
|
5072
|
+
const displayAmount = state.transfer?.amount?.amount ?? 0;
|
|
5073
|
+
const displayCurrency = state.transfer?.amount?.currency ?? "USD";
|
|
5074
|
+
return /* @__PURE__ */ jsx(
|
|
5075
|
+
SuccessScreen,
|
|
5076
|
+
{
|
|
5077
|
+
amount: displayAmount,
|
|
5078
|
+
currency: displayCurrency,
|
|
5079
|
+
succeeded,
|
|
5080
|
+
error: state.error,
|
|
5081
|
+
merchantName,
|
|
5082
|
+
sourceName,
|
|
5083
|
+
remainingLimit: succeeded ? (() => {
|
|
5084
|
+
const limit = selectedAccount?.remainingAllowance ?? state.oneTapLimit;
|
|
5085
|
+
return limit > displayAmount ? limit - displayAmount : 0;
|
|
5086
|
+
})() : void 0,
|
|
5087
|
+
onDone: onDismiss ?? handlers.onNewPayment,
|
|
5088
|
+
onLogout: handlers.onLogout,
|
|
5089
|
+
autoCloseSeconds
|
|
5090
|
+
}
|
|
5091
|
+
);
|
|
5092
|
+
}
|
|
5093
|
+
if (step === "low-balance") {
|
|
5094
|
+
return /* @__PURE__ */ jsx(
|
|
5095
|
+
DepositScreen,
|
|
5096
|
+
{
|
|
5097
|
+
merchantName,
|
|
5098
|
+
sourceName,
|
|
5099
|
+
sourceAddress,
|
|
5100
|
+
sourceVerified,
|
|
5101
|
+
availableBalance: 0,
|
|
5102
|
+
remainingLimit: selectedAccount?.remainingAllowance ?? state.oneTapLimit,
|
|
5103
|
+
tokenCount,
|
|
5104
|
+
initialAmount: depositAmount ?? 5,
|
|
5105
|
+
processing: false,
|
|
5106
|
+
error: state.error,
|
|
5107
|
+
onDeposit: handlers.onPay,
|
|
5108
|
+
onChangeSource: () => handlers.onNavigate("wallet-picker"),
|
|
5109
|
+
onSwitchWallet: () => handlers.onNavigate("wallet-picker"),
|
|
5110
|
+
onBack: onBack ?? (() => handlers.onLogout()),
|
|
5111
|
+
onLogout: handlers.onLogout
|
|
5112
|
+
}
|
|
5113
|
+
);
|
|
5114
|
+
}
|
|
5115
|
+
return null;
|
|
5116
|
+
}
|
|
5117
|
+
var PaymentErrorBoundary = class extends Component {
|
|
5118
|
+
constructor(props) {
|
|
5119
|
+
super(props);
|
|
5120
|
+
this.state = { hasError: false };
|
|
5121
|
+
}
|
|
5122
|
+
static getDerivedStateFromError() {
|
|
5123
|
+
return { hasError: true };
|
|
5124
|
+
}
|
|
5125
|
+
componentDidCatch(error, _info) {
|
|
5126
|
+
captureException(error);
|
|
5127
|
+
}
|
|
5128
|
+
handleReset = () => {
|
|
5129
|
+
this.setState({ hasError: false });
|
|
5130
|
+
this.props.onReset();
|
|
5131
|
+
};
|
|
5132
|
+
render() {
|
|
4513
5133
|
if (!this.state.hasError) {
|
|
4514
5134
|
return this.props.children;
|
|
4515
5135
|
}
|
|
@@ -4562,98 +5182,6 @@ var buttonStyle3 = {
|
|
|
4562
5182
|
fontFamily: "inherit",
|
|
4563
5183
|
cursor: "pointer"
|
|
4564
5184
|
};
|
|
4565
|
-
var ACTIVE_CREDENTIAL_STORAGE_KEY = "swype_active_credential_id";
|
|
4566
|
-
var MOBILE_FLOW_STORAGE_KEY = "swype_mobile_flow";
|
|
4567
|
-
var MIN_SEND_AMOUNT_USD = 0.25;
|
|
4568
|
-
function persistMobileFlowState(data) {
|
|
4569
|
-
try {
|
|
4570
|
-
sessionStorage.setItem(MOBILE_FLOW_STORAGE_KEY, JSON.stringify(data));
|
|
4571
|
-
} catch {
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
|
-
function loadMobileFlowState() {
|
|
4575
|
-
try {
|
|
4576
|
-
const raw = sessionStorage.getItem(MOBILE_FLOW_STORAGE_KEY);
|
|
4577
|
-
if (!raw) return null;
|
|
4578
|
-
return JSON.parse(raw);
|
|
4579
|
-
} catch {
|
|
4580
|
-
return null;
|
|
4581
|
-
}
|
|
4582
|
-
}
|
|
4583
|
-
function clearMobileFlowState() {
|
|
4584
|
-
try {
|
|
4585
|
-
sessionStorage.removeItem(MOBILE_FLOW_STORAGE_KEY);
|
|
4586
|
-
} catch {
|
|
4587
|
-
}
|
|
4588
|
-
}
|
|
4589
|
-
function computeSmartDefaults(accts, transferAmount) {
|
|
4590
|
-
if (accts.length === 0) return null;
|
|
4591
|
-
for (const acct of accts) {
|
|
4592
|
-
for (const wallet of acct.wallets) {
|
|
4593
|
-
if (wallet.status === "ACTIVE") {
|
|
4594
|
-
const bestSource = wallet.sources.find(
|
|
4595
|
-
(s) => s.balance.available.amount >= transferAmount
|
|
4596
|
-
);
|
|
4597
|
-
if (bestSource) {
|
|
4598
|
-
return { accountId: acct.id, walletId: wallet.id };
|
|
4599
|
-
}
|
|
4600
|
-
}
|
|
4601
|
-
}
|
|
4602
|
-
}
|
|
4603
|
-
let bestAccount = null;
|
|
4604
|
-
let bestWallet = null;
|
|
4605
|
-
let bestBalance = -1;
|
|
4606
|
-
let bestIsActive = false;
|
|
4607
|
-
for (const acct of accts) {
|
|
4608
|
-
for (const wallet of acct.wallets) {
|
|
4609
|
-
const walletBal = wallet.balance.available.amount;
|
|
4610
|
-
const isActive = wallet.status === "ACTIVE";
|
|
4611
|
-
if (walletBal > bestBalance || walletBal === bestBalance && isActive && !bestIsActive) {
|
|
4612
|
-
bestBalance = walletBal;
|
|
4613
|
-
bestAccount = acct;
|
|
4614
|
-
bestWallet = wallet;
|
|
4615
|
-
bestIsActive = isActive;
|
|
4616
|
-
}
|
|
4617
|
-
}
|
|
4618
|
-
}
|
|
4619
|
-
if (bestAccount) {
|
|
4620
|
-
return {
|
|
4621
|
-
accountId: bestAccount.id,
|
|
4622
|
-
walletId: bestWallet?.id ?? null
|
|
4623
|
-
};
|
|
4624
|
-
}
|
|
4625
|
-
return { accountId: accts[0].id, walletId: null };
|
|
4626
|
-
}
|
|
4627
|
-
function parseRawBalance(rawBalance, decimals) {
|
|
4628
|
-
const parsed = Number(rawBalance);
|
|
4629
|
-
if (!Number.isFinite(parsed)) return 0;
|
|
4630
|
-
return parsed / 10 ** decimals;
|
|
4631
|
-
}
|
|
4632
|
-
function buildSelectSourceChoices(options) {
|
|
4633
|
-
const chainChoices = [];
|
|
4634
|
-
const chainIndexByName = /* @__PURE__ */ new Map();
|
|
4635
|
-
for (const option of options) {
|
|
4636
|
-
const { chainName, tokenSymbol } = option;
|
|
4637
|
-
const balance = parseRawBalance(option.rawBalance, option.decimals);
|
|
4638
|
-
let chainChoice;
|
|
4639
|
-
const existingIdx = chainIndexByName.get(chainName);
|
|
4640
|
-
if (existingIdx === void 0) {
|
|
4641
|
-
chainChoice = { chainName, balance: 0, tokens: [] };
|
|
4642
|
-
chainIndexByName.set(chainName, chainChoices.length);
|
|
4643
|
-
chainChoices.push(chainChoice);
|
|
4644
|
-
} else {
|
|
4645
|
-
chainChoice = chainChoices[existingIdx];
|
|
4646
|
-
}
|
|
4647
|
-
chainChoice.balance += balance;
|
|
4648
|
-
const existing = chainChoice.tokens.find((t) => t.tokenSymbol === tokenSymbol);
|
|
4649
|
-
if (existing) {
|
|
4650
|
-
existing.balance += balance;
|
|
4651
|
-
} else {
|
|
4652
|
-
chainChoice.tokens.push({ tokenSymbol, balance });
|
|
4653
|
-
}
|
|
4654
|
-
}
|
|
4655
|
-
return chainChoices;
|
|
4656
|
-
}
|
|
4657
5185
|
function SwypePayment(props) {
|
|
4658
5186
|
const resetKey = useRef(0);
|
|
4659
5187
|
const handleBoundaryReset = useCallback(() => {
|
|
@@ -4665,7 +5193,7 @@ function SwypePaymentInner({
|
|
|
4665
5193
|
destination,
|
|
4666
5194
|
onComplete,
|
|
4667
5195
|
onError,
|
|
4668
|
-
useWalletConnector,
|
|
5196
|
+
useWalletConnector: useWalletConnectorProp,
|
|
4669
5197
|
idempotencyKey,
|
|
4670
5198
|
merchantAuthorization,
|
|
4671
5199
|
merchantName,
|
|
@@ -4673,7 +5201,7 @@ function SwypePaymentInner({
|
|
|
4673
5201
|
onDismiss,
|
|
4674
5202
|
autoCloseSeconds
|
|
4675
5203
|
}) {
|
|
4676
|
-
const { apiBaseUrl,
|
|
5204
|
+
const { apiBaseUrl, depositAmount } = useSwypeConfig();
|
|
4677
5205
|
const { ready, authenticated, user, logout, getAccessToken } = usePrivy();
|
|
4678
5206
|
const {
|
|
4679
5207
|
sendCode: sendEmailCode,
|
|
@@ -4685,161 +5213,74 @@ function SwypePaymentInner({
|
|
|
4685
5213
|
loginWithCode: loginWithSmsCode,
|
|
4686
5214
|
state: smsLoginState
|
|
4687
5215
|
} = useLoginWithSms();
|
|
4688
|
-
|
|
4689
|
-
const [
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
const [selectedProviderId, setSelectedProviderId] = useState(null);
|
|
4698
|
-
const [connectingNewAccount, setConnectingNewAccount] = useState(false);
|
|
4699
|
-
const [amount, setAmount] = useState(
|
|
4700
|
-
depositAmount != null ? depositAmount.toString() : ""
|
|
4701
|
-
);
|
|
4702
|
-
const [transfer, setTransfer] = useState(null);
|
|
4703
|
-
const [creatingTransfer, setCreatingTransfer] = useState(false);
|
|
4704
|
-
const [registeringPasskey, setRegisteringPasskey] = useState(false);
|
|
4705
|
-
const [verifyingPasskeyPopup, setVerifyingPasskeyPopup] = useState(false);
|
|
4706
|
-
const [passkeyPopupNeeded, setPasskeyPopupNeeded] = useState(
|
|
4707
|
-
() => isSafari() && isInCrossOriginIframe()
|
|
5216
|
+
useLoginWithOAuth();
|
|
5217
|
+
const [state, dispatch] = useReducer(
|
|
5218
|
+
paymentReducer,
|
|
5219
|
+
{
|
|
5220
|
+
depositAmount,
|
|
5221
|
+
passkeyPopupNeeded: isSafari() && isInCrossOriginIframe(),
|
|
5222
|
+
activeCredentialId: typeof window === "undefined" ? null : window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY)
|
|
5223
|
+
},
|
|
5224
|
+
createInitialState
|
|
4708
5225
|
);
|
|
4709
|
-
const [activeCredentialId, setActiveCredentialId] = useState(() => {
|
|
4710
|
-
if (typeof window === "undefined") return null;
|
|
4711
|
-
return window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
|
|
4712
|
-
});
|
|
4713
|
-
const [knownCredentialIds, setKnownCredentialIds] = useState([]);
|
|
4714
|
-
const [authInput, setAuthInput] = useState("");
|
|
4715
|
-
const [verificationTarget, setVerificationTarget] = useState(null);
|
|
4716
|
-
const [otpCode, setOtpCode] = useState("");
|
|
4717
|
-
const [oneTapLimit, setOneTapLimit] = useState(100);
|
|
4718
|
-
const [mobileFlow, setMobileFlow] = useState(false);
|
|
4719
|
-
const [deeplinkUri, setDeeplinkUri] = useState(null);
|
|
4720
5226
|
const loadingDataRef = useRef(false);
|
|
4721
5227
|
const pollingTransferIdRef = useRef(null);
|
|
4722
|
-
const mobileSigningTransferIdRef = useRef(null);
|
|
4723
5228
|
const mobileSetupFlowRef = useRef(false);
|
|
4724
5229
|
const handlingMobileReturnRef = useRef(false);
|
|
4725
5230
|
const processingStartedAtRef = useRef(null);
|
|
4726
|
-
const [selectSourceChainName, setSelectSourceChainName] = useState("");
|
|
4727
|
-
const [selectSourceTokenSymbol, setSelectSourceTokenSymbol] = useState("");
|
|
4728
5231
|
const initializedSelectSourceActionRef = useRef(null);
|
|
4729
5232
|
const preSelectSourceStepRef = useRef(null);
|
|
5233
|
+
const [authInput, setAuthInput] = useState("");
|
|
5234
|
+
const [otpCode, setOtpCode] = useState("");
|
|
5235
|
+
const [selectSourceChainName, setSelectSourceChainName] = useState("");
|
|
5236
|
+
const [selectSourceTokenSymbol, setSelectSourceTokenSymbol] = useState("");
|
|
4730
5237
|
const authExecutor = useAuthorizationExecutor();
|
|
4731
5238
|
const polling = useTransferPolling();
|
|
4732
5239
|
const transferSigning = useTransferSigning();
|
|
4733
|
-
const sourceType
|
|
4734
|
-
const
|
|
4735
|
-
const
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
const enterPersistedMobileFlow = useCallback((persisted, errorMessage) => {
|
|
4757
|
-
setMobileFlow(true);
|
|
4758
|
-
setDeeplinkUri(persisted.deeplinkUri);
|
|
4759
|
-
setSelectedProviderId(persisted.providerId);
|
|
4760
|
-
pollingTransferIdRef.current = persisted.transferId;
|
|
4761
|
-
mobileSetupFlowRef.current = persisted.isSetup;
|
|
4762
|
-
setError(errorMessage ?? null);
|
|
4763
|
-
setStep("open-wallet");
|
|
4764
|
-
polling.startPolling(persisted.transferId);
|
|
4765
|
-
}, [polling]);
|
|
4766
|
-
const handleAuthorizedMobileReturn = useCallback(async (authorizedTransfer, isSetup) => {
|
|
4767
|
-
if (handlingMobileReturnRef.current) return;
|
|
4768
|
-
handlingMobileReturnRef.current = true;
|
|
4769
|
-
polling.stopPolling();
|
|
4770
|
-
if (isSetup) {
|
|
4771
|
-
mobileSetupFlowRef.current = false;
|
|
4772
|
-
clearMobileFlowState();
|
|
4773
|
-
try {
|
|
4774
|
-
await reloadAccounts();
|
|
4775
|
-
resetDataLoadingState();
|
|
4776
|
-
setTransfer(authorizedTransfer);
|
|
4777
|
-
setError(null);
|
|
4778
|
-
setDeeplinkUri(null);
|
|
4779
|
-
setMobileFlow(false);
|
|
4780
|
-
setStep("deposit");
|
|
4781
|
-
} catch (err) {
|
|
4782
|
-
handlingMobileReturnRef.current = false;
|
|
4783
|
-
setError(
|
|
4784
|
-
err instanceof Error ? err.message : "Wallet authorized, but we could not refresh your account yet."
|
|
4785
|
-
);
|
|
4786
|
-
setStep("open-wallet");
|
|
5240
|
+
const { sourceType, sourceId } = deriveSourceTypeAndId(state);
|
|
5241
|
+
const selectedAccount = state.accounts.find((a) => a.id === state.selectedAccountId);
|
|
5242
|
+
const selectedWallet = selectedAccount?.wallets.find(
|
|
5243
|
+
(w) => w.id === state.selectedWalletId
|
|
5244
|
+
);
|
|
5245
|
+
const sourceName = selectedAccount?.name ?? selectedWallet?.chain.name ?? "Wallet";
|
|
5246
|
+
const sourceAddress = selectedWallet ? `${selectedWallet.name.slice(0, 6)}...${selectedWallet.name.slice(-4)}` : void 0;
|
|
5247
|
+
const sourceVerified = selectedWallet?.status === "ACTIVE";
|
|
5248
|
+
const pendingConnections = useMemo(
|
|
5249
|
+
() => state.accounts.filter(
|
|
5250
|
+
(a) => a.wallets.length > 0 && !a.wallets.some((w) => w.status === "ACTIVE")
|
|
5251
|
+
),
|
|
5252
|
+
[state.accounts]
|
|
5253
|
+
);
|
|
5254
|
+
const maxSourceBalance = useMemo(() => {
|
|
5255
|
+
let max = 0;
|
|
5256
|
+
for (const acct of state.accounts) {
|
|
5257
|
+
for (const wallet of acct.wallets) {
|
|
5258
|
+
for (const source of wallet.sources) {
|
|
5259
|
+
if (source.balance.available.amount > max) {
|
|
5260
|
+
max = source.balance.available.amount;
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
4787
5263
|
}
|
|
4788
|
-
return;
|
|
4789
|
-
}
|
|
4790
|
-
setTransfer(authorizedTransfer);
|
|
4791
|
-
mobileSetupFlowRef.current = false;
|
|
4792
|
-
clearMobileFlowState();
|
|
4793
|
-
setError(null);
|
|
4794
|
-
setDeeplinkUri(null);
|
|
4795
|
-
setMobileFlow(false);
|
|
4796
|
-
setStep("confirm-sign");
|
|
4797
|
-
}, [polling.stopPolling, reloadAccounts, resetDataLoadingState]);
|
|
4798
|
-
const handleRetryMobileStatus = useCallback(() => {
|
|
4799
|
-
setError(null);
|
|
4800
|
-
handlingMobileReturnRef.current = false;
|
|
4801
|
-
const currentTransfer = polling.transfer ?? transfer;
|
|
4802
|
-
if (currentTransfer?.status === "AUTHORIZED") {
|
|
4803
|
-
void handleAuthorizedMobileReturn(currentTransfer, mobileSetupFlowRef.current);
|
|
4804
|
-
return;
|
|
4805
|
-
}
|
|
4806
|
-
const transferIdToResume = pollingTransferIdRef.current ?? currentTransfer?.id;
|
|
4807
|
-
if (transferIdToResume) {
|
|
4808
|
-
polling.startPolling(transferIdToResume);
|
|
4809
|
-
}
|
|
4810
|
-
}, [handleAuthorizedMobileReturn, polling, transfer]);
|
|
4811
|
-
useEffect(() => {
|
|
4812
|
-
if (depositAmount != null) {
|
|
4813
|
-
setAmount(depositAmount.toString());
|
|
4814
5264
|
}
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
try {
|
|
4824
|
-
await initOAuth({ provider });
|
|
4825
|
-
} catch (err) {
|
|
4826
|
-
captureException(err);
|
|
4827
|
-
setError(err instanceof Error ? err.message : "Social login failed");
|
|
5265
|
+
return max;
|
|
5266
|
+
}, [state.accounts]);
|
|
5267
|
+
const tokenCount = useMemo(() => {
|
|
5268
|
+
let count = 0;
|
|
5269
|
+
for (const acct of state.accounts) {
|
|
5270
|
+
for (const wallet of acct.wallets) {
|
|
5271
|
+
count += wallet.sources.length;
|
|
5272
|
+
}
|
|
4828
5273
|
}
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
const
|
|
4832
|
-
|
|
4833
|
-
if (authenticated) return;
|
|
4834
|
-
if (activeOtpErrorMessage) setError(activeOtpErrorMessage);
|
|
4835
|
-
}, [activeOtpErrorMessage, authenticated]);
|
|
5274
|
+
return count;
|
|
5275
|
+
}, [state.accounts]);
|
|
5276
|
+
const activeOtpStatus = state.verificationTarget?.kind === "email" ? emailLoginState.status : state.verificationTarget?.kind === "phone" ? smsLoginState.status : "initial";
|
|
5277
|
+
const activeOtpErrorMessage = state.verificationTarget?.kind === "email" && emailLoginState.status === "error" ? emailLoginState.error?.message ?? "Failed to continue with email." : state.verificationTarget?.kind === "phone" && smsLoginState.status === "error" ? smsLoginState.error?.message ?? "Failed to continue with phone number." : null;
|
|
4836
5278
|
const handleSendLoginCode = useCallback(async () => {
|
|
4837
5279
|
const normalizedIdentifier = normalizeAuthIdentifier(authInput);
|
|
4838
5280
|
if (!normalizedIdentifier) {
|
|
4839
|
-
|
|
5281
|
+
dispatch({ type: "SET_ERROR", error: "Enter a valid email address or phone number." });
|
|
4840
5282
|
return;
|
|
4841
5283
|
}
|
|
4842
|
-
setError(null);
|
|
4843
5284
|
setOtpCode("");
|
|
4844
5285
|
try {
|
|
4845
5286
|
if (normalizedIdentifier.kind === "email") {
|
|
@@ -4847,468 +5288,214 @@ function SwypePaymentInner({
|
|
|
4847
5288
|
} else {
|
|
4848
5289
|
await sendSmsCode({ phoneNumber: normalizedIdentifier.value });
|
|
4849
5290
|
}
|
|
4850
|
-
|
|
4851
|
-
setStep("otp-verify");
|
|
5291
|
+
dispatch({ type: "CODE_SENT", target: normalizedIdentifier });
|
|
4852
5292
|
} catch (err) {
|
|
4853
5293
|
captureException(err);
|
|
4854
|
-
|
|
5294
|
+
dispatch({
|
|
5295
|
+
type: "SET_ERROR",
|
|
5296
|
+
error: err instanceof Error ? err.message : "Failed to send verification code"
|
|
5297
|
+
});
|
|
4855
5298
|
}
|
|
4856
5299
|
}, [authInput, sendEmailCode, sendSmsCode]);
|
|
4857
5300
|
const handleVerifyLoginCode = useCallback(async () => {
|
|
4858
|
-
if (!verificationTarget) return;
|
|
5301
|
+
if (!state.verificationTarget) return;
|
|
4859
5302
|
const trimmedCode = otpCode.trim();
|
|
4860
5303
|
if (!/^\d{6}$/.test(trimmedCode)) {
|
|
4861
|
-
|
|
5304
|
+
dispatch({ type: "SET_ERROR", error: "Enter the 6-digit verification code." });
|
|
4862
5305
|
return;
|
|
4863
5306
|
}
|
|
4864
|
-
|
|
5307
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
4865
5308
|
try {
|
|
4866
|
-
if (verificationTarget.kind === "email") {
|
|
5309
|
+
if (state.verificationTarget.kind === "email") {
|
|
4867
5310
|
await loginWithEmailCode({ code: trimmedCode });
|
|
4868
5311
|
} else {
|
|
4869
5312
|
await loginWithSmsCode({ code: trimmedCode });
|
|
4870
5313
|
}
|
|
4871
5314
|
} catch (err) {
|
|
4872
5315
|
captureException(err);
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
if (step === "otp-verify" && /^\d{6}$/.test(otpCode.trim()) && activeOtpStatus !== "submitting-code") {
|
|
4878
|
-
handleVerifyLoginCode();
|
|
5316
|
+
dispatch({
|
|
5317
|
+
type: "SET_ERROR",
|
|
5318
|
+
error: err instanceof Error ? err.message : "Failed to verify code"
|
|
5319
|
+
});
|
|
4879
5320
|
}
|
|
4880
|
-
}, [
|
|
5321
|
+
}, [state.verificationTarget, otpCode, loginWithEmailCode, loginWithSmsCode]);
|
|
4881
5322
|
const handleResendLoginCode = useCallback(async () => {
|
|
4882
|
-
if (!verificationTarget) return;
|
|
4883
|
-
|
|
5323
|
+
if (!state.verificationTarget) return;
|
|
5324
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
4884
5325
|
try {
|
|
4885
|
-
if (verificationTarget.kind === "email") {
|
|
4886
|
-
await sendEmailCode({ email: verificationTarget.value });
|
|
5326
|
+
if (state.verificationTarget.kind === "email") {
|
|
5327
|
+
await sendEmailCode({ email: state.verificationTarget.value });
|
|
4887
5328
|
} else {
|
|
4888
|
-
await sendSmsCode({ phoneNumber: verificationTarget.value });
|
|
5329
|
+
await sendSmsCode({ phoneNumber: state.verificationTarget.value });
|
|
4889
5330
|
}
|
|
4890
5331
|
} catch (err) {
|
|
4891
5332
|
captureException(err);
|
|
4892
|
-
|
|
5333
|
+
dispatch({
|
|
5334
|
+
type: "SET_ERROR",
|
|
5335
|
+
error: err instanceof Error ? err.message : "Failed to resend code"
|
|
5336
|
+
});
|
|
4893
5337
|
}
|
|
4894
|
-
}, [verificationTarget, sendEmailCode, sendSmsCode]);
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
if (
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
const
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
5338
|
+
}, [state.verificationTarget, sendEmailCode, sendSmsCode]);
|
|
5339
|
+
const completePasskeyRegistration = useCallback(async (credentialId, publicKey) => {
|
|
5340
|
+
const token = await getAccessToken();
|
|
5341
|
+
if (!token) throw new Error("Not authenticated");
|
|
5342
|
+
await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
|
|
5343
|
+
dispatch({ type: "PASSKEY_ACTIVATED", credentialId });
|
|
5344
|
+
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
|
|
5345
|
+
const resolved = resolvePostAuthStep({
|
|
5346
|
+
hasPasskey: true,
|
|
5347
|
+
accounts: state.accounts,
|
|
5348
|
+
persistedMobileFlow: loadMobileFlowState(),
|
|
5349
|
+
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5350
|
+
connectingNewAccount: state.connectingNewAccount
|
|
5351
|
+
});
|
|
5352
|
+
if (resolved.clearPersistedFlow) clearMobileFlowState();
|
|
5353
|
+
dispatch({ type: "NAVIGATE", step: resolved.step });
|
|
5354
|
+
}, [getAccessToken, apiBaseUrl, state.accounts, state.connectingNewAccount]);
|
|
5355
|
+
const handleRegisterPasskey = useCallback(async () => {
|
|
5356
|
+
dispatch({ type: "SET_REGISTERING_PASSKEY", value: true });
|
|
5357
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5358
|
+
try {
|
|
5359
|
+
const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
|
|
5360
|
+
const { credentialId, publicKey } = await createPasskeyCredential({
|
|
5361
|
+
userId: user?.id ?? "unknown",
|
|
5362
|
+
displayName: passkeyDisplayName
|
|
5363
|
+
});
|
|
5364
|
+
await completePasskeyRegistration(credentialId, publicKey);
|
|
5365
|
+
} catch (err) {
|
|
5366
|
+
if (err instanceof PasskeyIframeBlockedError) {
|
|
5367
|
+
dispatch({ type: "SET_PASSKEY_POPUP_NEEDED", needed: true });
|
|
5368
|
+
} else {
|
|
5369
|
+
captureException(err);
|
|
5370
|
+
dispatch({
|
|
5371
|
+
type: "SET_ERROR",
|
|
5372
|
+
error: err instanceof Error ? err.message : "Failed to register passkey"
|
|
5373
|
+
});
|
|
4908
5374
|
}
|
|
5375
|
+
} finally {
|
|
5376
|
+
dispatch({ type: "SET_REGISTERING_PASSKEY", value: false });
|
|
5377
|
+
}
|
|
5378
|
+
}, [user, completePasskeyRegistration]);
|
|
5379
|
+
const handleCreatePasskeyViaPopup = useCallback(async () => {
|
|
5380
|
+
dispatch({ type: "SET_REGISTERING_PASSKEY", value: true });
|
|
5381
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5382
|
+
try {
|
|
5383
|
+
const token = await getAccessToken();
|
|
5384
|
+
const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
|
|
5385
|
+
const popupOptions = buildPasskeyPopupOptions({
|
|
5386
|
+
userId: user?.id ?? "unknown",
|
|
5387
|
+
displayName: passkeyDisplayName,
|
|
5388
|
+
authToken: token ?? void 0,
|
|
5389
|
+
apiBaseUrl
|
|
5390
|
+
});
|
|
5391
|
+
const { credentialId } = await createPasskeyViaPopup(popupOptions);
|
|
5392
|
+
dispatch({ type: "PASSKEY_ACTIVATED", credentialId });
|
|
5393
|
+
localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
|
|
4909
5394
|
const resolved = resolvePostAuthStep({
|
|
4910
5395
|
hasPasskey: true,
|
|
4911
|
-
accounts:
|
|
4912
|
-
persistedMobileFlow:
|
|
4913
|
-
mobileSetupInProgress:
|
|
4914
|
-
connectingNewAccount:
|
|
5396
|
+
accounts: state.accounts,
|
|
5397
|
+
persistedMobileFlow: loadMobileFlowState(),
|
|
5398
|
+
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5399
|
+
connectingNewAccount: state.connectingNewAccount
|
|
4915
5400
|
});
|
|
4916
|
-
if (resolved.clearPersistedFlow)
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
await handleAuthorizedMobileReturn(existingTransfer, false);
|
|
4944
|
-
return;
|
|
4945
|
-
}
|
|
4946
|
-
if (mobileResolution.kind === "resume-success") {
|
|
4947
|
-
clearMobileFlowState();
|
|
4948
|
-
setMobileFlow(false);
|
|
4949
|
-
setDeeplinkUri(null);
|
|
4950
|
-
setTransfer(existingTransfer);
|
|
4951
|
-
setError(null);
|
|
4952
|
-
setStep("success");
|
|
4953
|
-
onComplete?.(existingTransfer);
|
|
4954
|
-
return;
|
|
4955
|
-
}
|
|
4956
|
-
if (mobileResolution.kind === "resume-failed") {
|
|
4957
|
-
clearMobileFlowState();
|
|
4958
|
-
setMobileFlow(false);
|
|
4959
|
-
setDeeplinkUri(null);
|
|
4960
|
-
setTransfer(existingTransfer);
|
|
4961
|
-
setError("Transfer failed.");
|
|
4962
|
-
setStep("success");
|
|
4963
|
-
return;
|
|
4964
|
-
}
|
|
4965
|
-
if (mobileResolution.kind === "resume-processing") {
|
|
4966
|
-
clearMobileFlowState();
|
|
4967
|
-
setMobileFlow(false);
|
|
4968
|
-
setDeeplinkUri(null);
|
|
4969
|
-
setTransfer(existingTransfer);
|
|
4970
|
-
setError(null);
|
|
4971
|
-
setStep("processing");
|
|
4972
|
-
polling.startPolling(existingTransfer.id);
|
|
4973
|
-
return;
|
|
4974
|
-
}
|
|
4975
|
-
if (mobileResolution.kind === "resume-stale-setup") {
|
|
4976
|
-
clearMobileFlowState();
|
|
4977
|
-
if (!cancelled) setStep("wallet-picker");
|
|
4978
|
-
return;
|
|
4979
|
-
}
|
|
4980
|
-
} catch (err) {
|
|
4981
|
-
if (cancelled) return;
|
|
4982
|
-
enterPersistedMobileFlow(
|
|
4983
|
-
persisted,
|
|
4984
|
-
err instanceof Error ? err.message : "Unable to refresh wallet authorization status."
|
|
4985
|
-
);
|
|
4986
|
-
return;
|
|
4987
|
-
}
|
|
4988
|
-
enterPersistedMobileFlow(persisted);
|
|
4989
|
-
return;
|
|
4990
|
-
}
|
|
4991
|
-
setStep(resolved.step);
|
|
4992
|
-
};
|
|
4993
|
-
const checkPasskey = async () => {
|
|
4994
|
-
try {
|
|
4995
|
-
const token = await getAccessToken();
|
|
4996
|
-
if (!token || cancelled) return;
|
|
4997
|
-
const { config } = await fetchUserConfig(apiBaseUrl, token);
|
|
4998
|
-
if (cancelled) return;
|
|
4999
|
-
if (config.defaultAllowance != null) {
|
|
5000
|
-
setOneTapLimit(config.defaultAllowance);
|
|
5001
|
-
}
|
|
5002
|
-
const allPasskeys = config.passkeys ?? (config.passkey ? [config.passkey] : []);
|
|
5003
|
-
setKnownCredentialIds(allPasskeys.map((p) => p.credentialId));
|
|
5004
|
-
if (allPasskeys.length === 0) {
|
|
5005
|
-
setStep("create-passkey");
|
|
5006
|
-
return;
|
|
5007
|
-
}
|
|
5008
|
-
if (activeCredentialId && allPasskeys.some((p) => p.credentialId === activeCredentialId)) {
|
|
5009
|
-
await restoreOrDeposit(activeCredentialId, token);
|
|
5010
|
-
return;
|
|
5011
|
-
}
|
|
5012
|
-
if (cancelled) return;
|
|
5013
|
-
const credentialIds = allPasskeys.map((p) => p.credentialId);
|
|
5014
|
-
if (isSafari() && isInCrossOriginIframe()) {
|
|
5015
|
-
setStep("verify-passkey");
|
|
5016
|
-
return;
|
|
5017
|
-
}
|
|
5018
|
-
const matched = await findDevicePasskey(credentialIds);
|
|
5019
|
-
if (cancelled) return;
|
|
5020
|
-
if (matched) {
|
|
5021
|
-
setActiveCredentialId(matched);
|
|
5022
|
-
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
|
|
5401
|
+
if (resolved.clearPersistedFlow) clearMobileFlowState();
|
|
5402
|
+
dispatch({ type: "NAVIGATE", step: resolved.step });
|
|
5403
|
+
} catch (err) {
|
|
5404
|
+
captureException(err);
|
|
5405
|
+
dispatch({
|
|
5406
|
+
type: "SET_ERROR",
|
|
5407
|
+
error: err instanceof Error ? err.message : "Failed to register passkey"
|
|
5408
|
+
});
|
|
5409
|
+
} finally {
|
|
5410
|
+
dispatch({ type: "SET_REGISTERING_PASSKEY", value: false });
|
|
5411
|
+
}
|
|
5412
|
+
}, [user, getAccessToken, apiBaseUrl, state.accounts, state.connectingNewAccount]);
|
|
5413
|
+
const handleVerifyPasskeyViaPopup = useCallback(async () => {
|
|
5414
|
+
dispatch({ type: "SET_VERIFYING_PASSKEY", value: true });
|
|
5415
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5416
|
+
try {
|
|
5417
|
+
const token = await getAccessToken();
|
|
5418
|
+
const matched = await findDevicePasskeyViaPopup({
|
|
5419
|
+
credentialIds: state.knownCredentialIds,
|
|
5420
|
+
rpId: resolvePasskeyRpId(),
|
|
5421
|
+
authToken: token ?? void 0,
|
|
5422
|
+
apiBaseUrl
|
|
5423
|
+
});
|
|
5424
|
+
if (matched) {
|
|
5425
|
+
dispatch({ type: "PASSKEY_ACTIVATED", credentialId: matched });
|
|
5426
|
+
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
|
|
5427
|
+
if (token) {
|
|
5023
5428
|
reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
|
|
5024
5429
|
});
|
|
5025
|
-
await restoreOrDeposit(matched, token);
|
|
5026
|
-
return;
|
|
5027
5430
|
}
|
|
5028
|
-
|
|
5029
|
-
}
|
|
5030
|
-
|
|
5431
|
+
dispatch({ type: "NAVIGATE", step: "login" });
|
|
5432
|
+
} else {
|
|
5433
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5031
5434
|
}
|
|
5032
|
-
}
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
};
|
|
5037
|
-
}, [
|
|
5038
|
-
ready,
|
|
5039
|
-
authenticated,
|
|
5040
|
-
step,
|
|
5041
|
-
apiBaseUrl,
|
|
5042
|
-
getAccessToken,
|
|
5043
|
-
activeCredentialId,
|
|
5044
|
-
resetHeadlessLogin,
|
|
5045
|
-
enterPersistedMobileFlow,
|
|
5046
|
-
handleAuthorizedMobileReturn,
|
|
5047
|
-
onComplete
|
|
5048
|
-
]);
|
|
5049
|
-
useEffect(() => {
|
|
5050
|
-
const loadAction = resolveDataLoadAction({
|
|
5051
|
-
authenticated,
|
|
5052
|
-
step,
|
|
5053
|
-
accountsCount: accounts.length,
|
|
5054
|
-
hasActiveCredential: !!activeCredentialId,
|
|
5055
|
-
loading: loadingDataRef.current
|
|
5056
|
-
});
|
|
5057
|
-
if (loadAction === "reset") {
|
|
5058
|
-
resetDataLoadingState();
|
|
5059
|
-
return;
|
|
5060
|
-
}
|
|
5061
|
-
if (loadAction === "wait") {
|
|
5062
|
-
return;
|
|
5063
|
-
}
|
|
5064
|
-
const credentialId = activeCredentialId;
|
|
5065
|
-
if (!credentialId) {
|
|
5066
|
-
resetDataLoadingState();
|
|
5067
|
-
return;
|
|
5435
|
+
} catch {
|
|
5436
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5437
|
+
} finally {
|
|
5438
|
+
dispatch({ type: "SET_VERIFYING_PASSKEY", value: false });
|
|
5068
5439
|
}
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
const
|
|
5072
|
-
|
|
5073
|
-
|
|
5440
|
+
}, [state.knownCredentialIds, getAccessToken, apiBaseUrl]);
|
|
5441
|
+
const reloadAccounts = useCallback(async () => {
|
|
5442
|
+
const token = await getAccessToken();
|
|
5443
|
+
if (!token || !state.activeCredentialId) return;
|
|
5444
|
+
const [accts, prov] = await Promise.all([
|
|
5445
|
+
fetchAccounts(apiBaseUrl, token, state.activeCredentialId),
|
|
5446
|
+
fetchProviders(apiBaseUrl, token)
|
|
5447
|
+
]);
|
|
5448
|
+
const parsedAmt = depositAmount != null ? depositAmount : 0;
|
|
5449
|
+
const defaults = computeSmartDefaults(accts, parsedAmt);
|
|
5450
|
+
dispatch({ type: "ACCOUNTS_RELOADED", accounts: accts, providers: prov, defaults });
|
|
5451
|
+
}, [getAccessToken, state.activeCredentialId, apiBaseUrl, depositAmount]);
|
|
5452
|
+
const handleAuthorizedMobileReturn = useCallback(async (authorizedTransfer, isSetup) => {
|
|
5453
|
+
if (handlingMobileReturnRef.current) return;
|
|
5454
|
+
handlingMobileReturnRef.current = true;
|
|
5455
|
+
polling.stopPolling();
|
|
5456
|
+
if (isSetup) {
|
|
5457
|
+
mobileSetupFlowRef.current = false;
|
|
5458
|
+
clearMobileFlowState();
|
|
5074
5459
|
try {
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
fetchProviders(apiBaseUrl, token),
|
|
5079
|
-
fetchAccounts(apiBaseUrl, token, credentialId),
|
|
5080
|
-
fetchChains(apiBaseUrl, token)
|
|
5081
|
-
]);
|
|
5082
|
-
if (cancelled) return;
|
|
5083
|
-
setProviders(prov);
|
|
5084
|
-
setAccounts(accts);
|
|
5085
|
-
setChains(chn);
|
|
5086
|
-
const parsedAmt = depositAmount != null ? depositAmount : 0;
|
|
5087
|
-
const defaults = computeSmartDefaults(accts, parsedAmt);
|
|
5088
|
-
if (defaults) {
|
|
5089
|
-
setSelectedAccountId(defaults.accountId);
|
|
5090
|
-
setSelectedWalletId(defaults.walletId);
|
|
5091
|
-
} else if (prov.length > 0 && !connectingNewAccount) {
|
|
5092
|
-
setSelectedProviderId(prov[0].id);
|
|
5093
|
-
}
|
|
5094
|
-
const persisted = loadMobileFlowState();
|
|
5095
|
-
const resolved = resolvePostAuthStep({
|
|
5096
|
-
hasPasskey: !!activeCredentialId,
|
|
5097
|
-
accounts: accts,
|
|
5098
|
-
persistedMobileFlow: persisted,
|
|
5099
|
-
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5100
|
-
connectingNewAccount
|
|
5101
|
-
});
|
|
5102
|
-
if (resolved.clearPersistedFlow) {
|
|
5103
|
-
clearMobileFlowState();
|
|
5104
|
-
setMobileFlow(false);
|
|
5105
|
-
setDeeplinkUri(null);
|
|
5106
|
-
}
|
|
5107
|
-
const correctableSteps = ["deposit", "wallet-picker", "open-wallet"];
|
|
5108
|
-
if (correctableSteps.includes(step)) {
|
|
5109
|
-
setStep(resolved.step);
|
|
5110
|
-
}
|
|
5460
|
+
await reloadAccounts();
|
|
5461
|
+
loadingDataRef.current = false;
|
|
5462
|
+
dispatch({ type: "MOBILE_SETUP_COMPLETE", transfer: authorizedTransfer });
|
|
5111
5463
|
} catch (err) {
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
resetDataLoadingState();
|
|
5119
|
-
}
|
|
5120
|
-
}
|
|
5121
|
-
};
|
|
5122
|
-
load();
|
|
5123
|
-
return () => {
|
|
5124
|
-
cancelled = true;
|
|
5125
|
-
loadingDataRef.current = false;
|
|
5126
|
-
};
|
|
5127
|
-
}, [authenticated, step, accounts.length, apiBaseUrl, getAccessToken, activeCredentialId, depositAmount, connectingNewAccount, resetDataLoadingState]);
|
|
5128
|
-
useEffect(() => {
|
|
5129
|
-
if (!polling.transfer) return;
|
|
5130
|
-
if (polling.transfer.status === "COMPLETED") {
|
|
5131
|
-
clearMobileFlowState();
|
|
5132
|
-
setStep("success");
|
|
5133
|
-
setTransfer(polling.transfer);
|
|
5134
|
-
onComplete?.(polling.transfer);
|
|
5135
|
-
} else if (polling.transfer.status === "FAILED") {
|
|
5136
|
-
clearMobileFlowState();
|
|
5137
|
-
setStep("success");
|
|
5138
|
-
setTransfer(polling.transfer);
|
|
5139
|
-
setError("Transfer failed.");
|
|
5140
|
-
}
|
|
5141
|
-
}, [polling.transfer, onComplete]);
|
|
5142
|
-
useEffect(() => {
|
|
5143
|
-
if (step !== "processing") {
|
|
5144
|
-
processingStartedAtRef.current = null;
|
|
5145
|
-
return;
|
|
5146
|
-
}
|
|
5147
|
-
if (!processingStartedAtRef.current) {
|
|
5148
|
-
processingStartedAtRef.current = Date.now();
|
|
5149
|
-
}
|
|
5150
|
-
const elapsedMs = Date.now() - processingStartedAtRef.current;
|
|
5151
|
-
const remainingMs = PROCESSING_TIMEOUT_MS - elapsedMs;
|
|
5152
|
-
const handleTimeout = () => {
|
|
5153
|
-
if (!hasProcessingTimedOut(processingStartedAtRef.current, Date.now())) return;
|
|
5154
|
-
const status = getTransferStatus(polling.transfer, transfer);
|
|
5155
|
-
const msg = buildProcessingTimeoutMessage(status);
|
|
5156
|
-
captureException(new Error(msg));
|
|
5157
|
-
polling.stopPolling();
|
|
5158
|
-
setStep("deposit");
|
|
5159
|
-
setError(msg);
|
|
5160
|
-
onError?.(msg);
|
|
5161
|
-
};
|
|
5162
|
-
if (remainingMs <= 0) {
|
|
5163
|
-
handleTimeout();
|
|
5164
|
-
return;
|
|
5165
|
-
}
|
|
5166
|
-
const timeoutId = window.setTimeout(handleTimeout, remainingMs);
|
|
5167
|
-
return () => window.clearTimeout(timeoutId);
|
|
5168
|
-
}, [step, polling.transfer, transfer, polling.stopPolling, onError]);
|
|
5169
|
-
useEffect(() => {
|
|
5170
|
-
if (!mobileFlow) {
|
|
5171
|
-
handlingMobileReturnRef.current = false;
|
|
5172
|
-
return;
|
|
5173
|
-
}
|
|
5174
|
-
if (handlingMobileReturnRef.current) return;
|
|
5175
|
-
const polledTransfer = polling.transfer;
|
|
5176
|
-
if (!polledTransfer || polledTransfer.status !== "AUTHORIZED") return;
|
|
5177
|
-
void handleAuthorizedMobileReturn(polledTransfer, mobileSetupFlowRef.current);
|
|
5178
|
-
}, [mobileFlow, polling.transfer, handleAuthorizedMobileReturn]);
|
|
5179
|
-
useEffect(() => {
|
|
5180
|
-
if (!mobileFlow) return;
|
|
5181
|
-
if (handlingMobileReturnRef.current) return;
|
|
5182
|
-
const transferIdToResume = pollingTransferIdRef.current ?? transfer?.id;
|
|
5183
|
-
if (!transferIdToResume) return;
|
|
5184
|
-
if (!polling.isPolling) polling.startPolling(transferIdToResume);
|
|
5185
|
-
const handleVisibility = () => {
|
|
5186
|
-
if (document.visibilityState === "visible" && !handlingMobileReturnRef.current) {
|
|
5187
|
-
polling.startPolling(transferIdToResume);
|
|
5464
|
+
handlingMobileReturnRef.current = false;
|
|
5465
|
+
dispatch({
|
|
5466
|
+
type: "SET_ERROR",
|
|
5467
|
+
error: err instanceof Error ? err.message : "Wallet authorized, but we could not refresh your account yet."
|
|
5468
|
+
});
|
|
5469
|
+
dispatch({ type: "NAVIGATE", step: "open-wallet" });
|
|
5188
5470
|
}
|
|
5189
|
-
};
|
|
5190
|
-
document.addEventListener("visibilitychange", handleVisibility);
|
|
5191
|
-
return () => document.removeEventListener("visibilitychange", handleVisibility);
|
|
5192
|
-
}, [mobileFlow, transfer?.id, polling.isPolling, polling.startPolling]);
|
|
5193
|
-
const pendingSelectSourceAction = authExecutor.pendingSelectSource;
|
|
5194
|
-
const selectSourceChoices = useMemo(() => {
|
|
5195
|
-
if (!pendingSelectSourceAction) return [];
|
|
5196
|
-
const options = pendingSelectSourceAction.metadata?.options ?? [];
|
|
5197
|
-
return buildSelectSourceChoices(options);
|
|
5198
|
-
}, [pendingSelectSourceAction]);
|
|
5199
|
-
const selectSourceRecommended = useMemo(() => {
|
|
5200
|
-
if (!pendingSelectSourceAction) return null;
|
|
5201
|
-
return pendingSelectSourceAction.metadata?.recommended ?? null;
|
|
5202
|
-
}, [pendingSelectSourceAction]);
|
|
5203
|
-
useEffect(() => {
|
|
5204
|
-
if (!pendingSelectSourceAction) {
|
|
5205
|
-
initializedSelectSourceActionRef.current = null;
|
|
5206
|
-
setSelectSourceChainName("");
|
|
5207
|
-
setSelectSourceTokenSymbol("");
|
|
5208
5471
|
return;
|
|
5209
5472
|
}
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
} else if (selectSourceChoices.length > 0 && selectSourceChoices[0].tokens.length > 0) {
|
|
5218
|
-
setSelectSourceChainName(selectSourceChoices[0].chainName);
|
|
5219
|
-
setSelectSourceTokenSymbol(selectSourceChoices[0].tokens[0].tokenSymbol);
|
|
5220
|
-
} else {
|
|
5221
|
-
setSelectSourceChainName("Base");
|
|
5222
|
-
setSelectSourceTokenSymbol("USDC");
|
|
5223
|
-
}
|
|
5224
|
-
initializedSelectSourceActionRef.current = pendingSelectSourceAction.id;
|
|
5225
|
-
}, [pendingSelectSourceAction, selectSourceChoices, selectSourceRecommended]);
|
|
5226
|
-
useEffect(() => {
|
|
5227
|
-
if (pendingSelectSourceAction && step === "processing") {
|
|
5228
|
-
preSelectSourceStepRef.current = step;
|
|
5229
|
-
setStep("select-source");
|
|
5230
|
-
} else if (!pendingSelectSourceAction && step === "select-source") {
|
|
5231
|
-
setStep(preSelectSourceStepRef.current ?? "processing");
|
|
5232
|
-
preSelectSourceStepRef.current = null;
|
|
5233
|
-
}
|
|
5234
|
-
}, [pendingSelectSourceAction, step]);
|
|
5235
|
-
const handleSelectSourceChainChange = useCallback(
|
|
5236
|
-
(chainName) => {
|
|
5237
|
-
setSelectSourceChainName(chainName);
|
|
5238
|
-
const chain = selectSourceChoices.find((c) => c.chainName === chainName);
|
|
5239
|
-
if (!chain || chain.tokens.length === 0) return;
|
|
5240
|
-
const recommendedToken = selectSourceRecommended?.chainName === chainName ? selectSourceRecommended.tokenSymbol : null;
|
|
5241
|
-
const hasRecommended = !!recommendedToken && chain.tokens.some((t) => t.tokenSymbol === recommendedToken);
|
|
5242
|
-
setSelectSourceTokenSymbol(
|
|
5243
|
-
hasRecommended ? recommendedToken : chain.tokens[0].tokenSymbol
|
|
5244
|
-
);
|
|
5245
|
-
},
|
|
5246
|
-
[selectSourceChoices, selectSourceRecommended]
|
|
5247
|
-
);
|
|
5248
|
-
const pendingConnections = useMemo(
|
|
5249
|
-
() => accounts.filter(
|
|
5250
|
-
(a) => a.wallets.length > 0 && !a.wallets.some((w) => w.status === "ACTIVE")
|
|
5251
|
-
),
|
|
5252
|
-
[accounts]
|
|
5253
|
-
);
|
|
5254
|
-
const selectedAccount = accounts.find((a) => a.id === selectedAccountId);
|
|
5255
|
-
const selectedWallet = selectedAccount?.wallets.find((w) => w.id === selectedWalletId);
|
|
5256
|
-
const sourceName = selectedAccount?.name ?? selectedWallet?.chain.name ?? "Wallet";
|
|
5257
|
-
const sourceAddress = selectedWallet ? `${selectedWallet.name.slice(0, 6)}...${selectedWallet.name.slice(-4)}` : void 0;
|
|
5258
|
-
const sourceVerified = selectedWallet?.status === "ACTIVE";
|
|
5259
|
-
const maxSourceBalance = useMemo(() => {
|
|
5260
|
-
let max = 0;
|
|
5261
|
-
for (const acct of accounts) {
|
|
5262
|
-
for (const wallet of acct.wallets) {
|
|
5263
|
-
for (const source of wallet.sources) {
|
|
5264
|
-
if (source.balance.available.amount > max) {
|
|
5265
|
-
max = source.balance.available.amount;
|
|
5266
|
-
}
|
|
5267
|
-
}
|
|
5268
|
-
}
|
|
5269
|
-
}
|
|
5270
|
-
return max;
|
|
5271
|
-
}, [accounts]);
|
|
5272
|
-
const tokenCount = useMemo(() => {
|
|
5273
|
-
let count = 0;
|
|
5274
|
-
for (const acct of accounts) {
|
|
5275
|
-
for (const wallet of acct.wallets) {
|
|
5276
|
-
count += wallet.sources.length;
|
|
5277
|
-
}
|
|
5278
|
-
}
|
|
5279
|
-
return count;
|
|
5280
|
-
}, [accounts]);
|
|
5281
|
-
const handlePay = useCallback(async (depositAmount2, sourceOverrides) => {
|
|
5282
|
-
const parsedAmount = depositAmount2;
|
|
5283
|
-
if (isNaN(parsedAmount) || parsedAmount < MIN_SEND_AMOUNT_USD) {
|
|
5284
|
-
setError(`Minimum amount is $${MIN_SEND_AMOUNT_USD.toFixed(2)}.`);
|
|
5473
|
+
mobileSetupFlowRef.current = false;
|
|
5474
|
+
clearMobileFlowState();
|
|
5475
|
+
dispatch({ type: "MOBILE_SIGN_READY", transfer: authorizedTransfer });
|
|
5476
|
+
}, [polling.stopPolling, reloadAccounts]);
|
|
5477
|
+
const handlePay = useCallback(async (payAmount, sourceOverrides) => {
|
|
5478
|
+
if (isNaN(payAmount) || payAmount < MIN_SEND_AMOUNT_USD) {
|
|
5479
|
+
dispatch({ type: "SET_ERROR", error: `Minimum amount is $${MIN_SEND_AMOUNT_USD.toFixed(2)}.` });
|
|
5285
5480
|
return;
|
|
5286
5481
|
}
|
|
5287
5482
|
if (!sourceOverrides?.sourceId && !sourceId) {
|
|
5288
|
-
|
|
5483
|
+
dispatch({ type: "SET_ERROR", error: "No account or provider selected." });
|
|
5289
5484
|
return;
|
|
5290
5485
|
}
|
|
5291
|
-
if (!activeCredentialId) {
|
|
5292
|
-
|
|
5293
|
-
|
|
5486
|
+
if (!state.activeCredentialId) {
|
|
5487
|
+
dispatch({ type: "SET_ERROR", error: "Create a passkey on this device before continuing." });
|
|
5488
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5294
5489
|
return;
|
|
5295
5490
|
}
|
|
5296
5491
|
const isSetupRedirect = mobileSetupFlowRef.current;
|
|
5297
|
-
|
|
5298
|
-
setStep("open-wallet");
|
|
5299
|
-
} else {
|
|
5300
|
-
setStep("processing");
|
|
5301
|
-
}
|
|
5492
|
+
dispatch({ type: "PAY_STARTED", isSetupRedirect });
|
|
5302
5493
|
processingStartedAtRef.current = Date.now();
|
|
5303
|
-
setError(null);
|
|
5304
|
-
setCreatingTransfer(true);
|
|
5305
|
-
setDeeplinkUri(null);
|
|
5306
|
-
setMobileFlow(false);
|
|
5307
5494
|
try {
|
|
5308
|
-
if (transfer?.status === "AUTHORIZED") {
|
|
5309
|
-
const signedTransfer2 = await transferSigning.signTransfer(transfer.id);
|
|
5310
|
-
|
|
5311
|
-
polling.startPolling(transfer.id);
|
|
5495
|
+
if (state.transfer?.status === "AUTHORIZED") {
|
|
5496
|
+
const signedTransfer2 = await transferSigning.signTransfer(state.transfer.id);
|
|
5497
|
+
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer2 });
|
|
5498
|
+
polling.startPolling(state.transfer.id);
|
|
5312
5499
|
return;
|
|
5313
5500
|
}
|
|
5314
5501
|
const token = await getAccessToken();
|
|
@@ -5316,21 +5503,21 @@ function SwypePaymentInner({
|
|
|
5316
5503
|
let effectiveSourceType = sourceOverrides?.sourceType ?? sourceType;
|
|
5317
5504
|
let effectiveSourceId = sourceOverrides?.sourceId ?? sourceId;
|
|
5318
5505
|
if (effectiveSourceType === "accountId") {
|
|
5319
|
-
const acct = accounts.find((a) => a.id === effectiveSourceId);
|
|
5506
|
+
const acct = state.accounts.find((a) => a.id === effectiveSourceId);
|
|
5320
5507
|
const activeWallet = acct?.wallets.find((w) => w.status === "ACTIVE");
|
|
5321
5508
|
if (activeWallet) {
|
|
5322
5509
|
effectiveSourceType = "walletId";
|
|
5323
5510
|
effectiveSourceId = activeWallet.id;
|
|
5324
5511
|
}
|
|
5325
5512
|
}
|
|
5326
|
-
const isActiveWallet = effectiveSourceType === "walletId" && accounts.some(
|
|
5513
|
+
const isActiveWallet = effectiveSourceType === "walletId" && state.accounts.some(
|
|
5327
5514
|
(a) => a.wallets.some((w) => w.id === effectiveSourceId && w.status === "ACTIVE")
|
|
5328
5515
|
);
|
|
5329
5516
|
if (!isActiveWallet && !isSetupRedirect) {
|
|
5330
5517
|
let found = false;
|
|
5331
|
-
for (const acct of accounts) {
|
|
5518
|
+
for (const acct of state.accounts) {
|
|
5332
5519
|
for (const wallet of acct.wallets) {
|
|
5333
|
-
if (wallet.status === "ACTIVE" && wallet.sources.some((s) => s.balance.available.amount >=
|
|
5520
|
+
if (wallet.status === "ACTIVE" && wallet.sources.some((s) => s.balance.available.amount >= payAmount)) {
|
|
5334
5521
|
effectiveSourceType = "walletId";
|
|
5335
5522
|
effectiveSourceId = wallet.id;
|
|
5336
5523
|
found = true;
|
|
@@ -5342,30 +5529,28 @@ function SwypePaymentInner({
|
|
|
5342
5529
|
}
|
|
5343
5530
|
const t = await createTransfer(apiBaseUrl, token, {
|
|
5344
5531
|
id: idempotencyKey,
|
|
5345
|
-
credentialId: activeCredentialId,
|
|
5532
|
+
credentialId: state.activeCredentialId,
|
|
5346
5533
|
merchantAuthorization,
|
|
5347
5534
|
sourceType: effectiveSourceType,
|
|
5348
5535
|
sourceId: effectiveSourceId,
|
|
5349
5536
|
destination,
|
|
5350
|
-
amount:
|
|
5537
|
+
amount: payAmount
|
|
5351
5538
|
});
|
|
5352
|
-
|
|
5539
|
+
dispatch({ type: "TRANSFER_CREATED", transfer: t });
|
|
5353
5540
|
if (t.authorizationSessions && t.authorizationSessions.length > 0) {
|
|
5354
|
-
const
|
|
5355
|
-
useWalletConnector,
|
|
5541
|
+
const useConnector = shouldUseWalletConnector({
|
|
5542
|
+
useWalletConnector: useWalletConnectorProp,
|
|
5356
5543
|
userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent
|
|
5357
5544
|
});
|
|
5358
|
-
if (!
|
|
5545
|
+
if (!useConnector) {
|
|
5359
5546
|
const uri = t.authorizationSessions[0].uri;
|
|
5360
|
-
setMobileFlow(true);
|
|
5361
5547
|
pollingTransferIdRef.current = t.id;
|
|
5362
5548
|
polling.startPolling(t.id);
|
|
5363
|
-
|
|
5364
|
-
setStep("open-wallet");
|
|
5549
|
+
dispatch({ type: "MOBILE_DEEPLINK_READY", deeplinkUri: uri });
|
|
5365
5550
|
persistMobileFlowState({
|
|
5366
5551
|
transferId: t.id,
|
|
5367
5552
|
deeplinkUri: uri,
|
|
5368
|
-
providerId: sourceOverrides?.sourceType === "providerId" ? sourceOverrides.sourceId : selectedProviderId,
|
|
5553
|
+
providerId: sourceOverrides?.sourceType === "providerId" ? sourceOverrides.sourceId : state.selectedProviderId,
|
|
5369
5554
|
isSetup: mobileSetupFlowRef.current
|
|
5370
5555
|
});
|
|
5371
5556
|
triggerDeeplink(uri);
|
|
@@ -5375,55 +5560,58 @@ function SwypePaymentInner({
|
|
|
5375
5560
|
}
|
|
5376
5561
|
}
|
|
5377
5562
|
const signedTransfer = await transferSigning.signTransfer(t.id);
|
|
5378
|
-
|
|
5563
|
+
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer });
|
|
5379
5564
|
polling.startPolling(t.id);
|
|
5380
5565
|
} catch (err) {
|
|
5381
5566
|
captureException(err);
|
|
5382
5567
|
const msg = err instanceof Error ? err.message : "Transfer failed";
|
|
5383
|
-
|
|
5568
|
+
dispatch({
|
|
5569
|
+
type: "PAY_ERROR",
|
|
5570
|
+
error: msg,
|
|
5571
|
+
fallbackStep: isSetupRedirect ? "wallet-picker" : "deposit"
|
|
5572
|
+
});
|
|
5384
5573
|
onError?.(msg);
|
|
5385
|
-
setStep(isSetupRedirect ? "wallet-picker" : "deposit");
|
|
5386
5574
|
} finally {
|
|
5387
|
-
|
|
5575
|
+
dispatch({ type: "PAY_ENDED" });
|
|
5388
5576
|
}
|
|
5389
5577
|
}, [
|
|
5390
5578
|
sourceId,
|
|
5391
5579
|
sourceType,
|
|
5392
|
-
activeCredentialId,
|
|
5580
|
+
state.activeCredentialId,
|
|
5581
|
+
state.transfer,
|
|
5582
|
+
state.accounts,
|
|
5583
|
+
state.selectedProviderId,
|
|
5393
5584
|
destination,
|
|
5394
5585
|
apiBaseUrl,
|
|
5395
5586
|
getAccessToken,
|
|
5396
|
-
accounts,
|
|
5397
5587
|
authExecutor,
|
|
5398
5588
|
transferSigning,
|
|
5399
5589
|
polling,
|
|
5400
5590
|
onError,
|
|
5401
|
-
|
|
5591
|
+
useWalletConnectorProp,
|
|
5402
5592
|
idempotencyKey,
|
|
5403
|
-
merchantAuthorization
|
|
5404
|
-
transfer
|
|
5593
|
+
merchantAuthorization
|
|
5405
5594
|
]);
|
|
5406
|
-
const [increasingLimit, setIncreasingLimit] = useState(false);
|
|
5407
5595
|
const handleIncreaseLimit = useCallback(async () => {
|
|
5408
5596
|
const parsedAmount = depositAmount ?? 5;
|
|
5409
5597
|
if (!sourceId) {
|
|
5410
|
-
|
|
5598
|
+
dispatch({ type: "SET_ERROR", error: "No account or provider selected." });
|
|
5411
5599
|
return;
|
|
5412
5600
|
}
|
|
5413
|
-
if (!activeCredentialId) {
|
|
5414
|
-
|
|
5415
|
-
|
|
5601
|
+
if (!state.activeCredentialId) {
|
|
5602
|
+
dispatch({ type: "SET_ERROR", error: "Create a passkey on this device before continuing." });
|
|
5603
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5416
5604
|
return;
|
|
5417
5605
|
}
|
|
5418
|
-
|
|
5419
|
-
|
|
5606
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5607
|
+
dispatch({ type: "SET_INCREASING_LIMIT", value: true });
|
|
5420
5608
|
try {
|
|
5421
5609
|
const token = await getAccessToken();
|
|
5422
5610
|
if (!token) throw new Error("Not authenticated");
|
|
5423
5611
|
let effectiveSourceType = sourceType;
|
|
5424
5612
|
let effectiveSourceId = sourceId;
|
|
5425
5613
|
if (effectiveSourceType === "accountId") {
|
|
5426
|
-
const acct = accounts.find((a) => a.id === effectiveSourceId);
|
|
5614
|
+
const acct = state.accounts.find((a) => a.id === effectiveSourceId);
|
|
5427
5615
|
const activeWallet = acct?.wallets.find((w) => w.status === "ACTIVE");
|
|
5428
5616
|
if (activeWallet) {
|
|
5429
5617
|
effectiveSourceType = "walletId";
|
|
@@ -5432,198 +5620,118 @@ function SwypePaymentInner({
|
|
|
5432
5620
|
}
|
|
5433
5621
|
const t = await createTransfer(apiBaseUrl, token, {
|
|
5434
5622
|
id: idempotencyKey,
|
|
5435
|
-
credentialId: activeCredentialId,
|
|
5623
|
+
credentialId: state.activeCredentialId,
|
|
5436
5624
|
merchantAuthorization,
|
|
5437
5625
|
sourceType: effectiveSourceType,
|
|
5438
5626
|
sourceId: effectiveSourceId,
|
|
5439
5627
|
destination,
|
|
5440
5628
|
amount: parsedAmount
|
|
5441
5629
|
});
|
|
5442
|
-
setTransfer(t);
|
|
5443
5630
|
if (t.authorizationSessions && t.authorizationSessions.length > 0) {
|
|
5444
5631
|
const uri = t.authorizationSessions[0].uri;
|
|
5445
|
-
setMobileFlow(true);
|
|
5446
5632
|
pollingTransferIdRef.current = t.id;
|
|
5447
5633
|
mobileSetupFlowRef.current = true;
|
|
5448
5634
|
handlingMobileReturnRef.current = false;
|
|
5449
5635
|
polling.startPolling(t.id);
|
|
5450
|
-
|
|
5636
|
+
dispatch({ type: "INCREASE_LIMIT_DEEPLINK", transfer: t, deeplinkUri: uri });
|
|
5451
5637
|
persistMobileFlowState({
|
|
5452
5638
|
transferId: t.id,
|
|
5453
5639
|
deeplinkUri: uri,
|
|
5454
|
-
providerId: selectedProviderId,
|
|
5640
|
+
providerId: state.selectedProviderId,
|
|
5455
5641
|
isSetup: true
|
|
5456
5642
|
});
|
|
5457
5643
|
triggerDeeplink(uri);
|
|
5644
|
+
} else {
|
|
5645
|
+
dispatch({ type: "TRANSFER_CREATED", transfer: t });
|
|
5458
5646
|
}
|
|
5459
5647
|
} catch (err) {
|
|
5460
5648
|
captureException(err);
|
|
5461
5649
|
const msg = err instanceof Error ? err.message : "Failed to increase limit";
|
|
5462
|
-
|
|
5650
|
+
dispatch({ type: "SET_ERROR", error: msg });
|
|
5463
5651
|
onError?.(msg);
|
|
5464
5652
|
} finally {
|
|
5465
|
-
|
|
5653
|
+
dispatch({ type: "SET_INCREASING_LIMIT", value: false });
|
|
5466
5654
|
}
|
|
5467
5655
|
}, [
|
|
5468
5656
|
depositAmount,
|
|
5469
5657
|
sourceId,
|
|
5470
5658
|
sourceType,
|
|
5471
|
-
activeCredentialId,
|
|
5659
|
+
state.activeCredentialId,
|
|
5660
|
+
state.accounts,
|
|
5661
|
+
state.selectedProviderId,
|
|
5472
5662
|
apiBaseUrl,
|
|
5473
5663
|
getAccessToken,
|
|
5474
|
-
accounts,
|
|
5475
5664
|
polling,
|
|
5476
5665
|
onError,
|
|
5477
5666
|
idempotencyKey,
|
|
5478
5667
|
merchantAuthorization,
|
|
5479
|
-
destination
|
|
5480
|
-
selectedProviderId
|
|
5668
|
+
destination
|
|
5481
5669
|
]);
|
|
5482
|
-
const
|
|
5483
|
-
const
|
|
5484
|
-
if (!
|
|
5485
|
-
await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
|
|
5486
|
-
setActiveCredentialId(credentialId);
|
|
5487
|
-
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
|
|
5488
|
-
setPasskeyPopupNeeded(false);
|
|
5489
|
-
const resolved = resolvePostAuthStep({
|
|
5490
|
-
hasPasskey: true,
|
|
5491
|
-
accounts,
|
|
5492
|
-
persistedMobileFlow: loadMobileFlowState(),
|
|
5493
|
-
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5494
|
-
connectingNewAccount
|
|
5495
|
-
});
|
|
5496
|
-
if (resolved.clearPersistedFlow) {
|
|
5497
|
-
clearMobileFlowState();
|
|
5498
|
-
}
|
|
5499
|
-
setStep(resolved.step);
|
|
5500
|
-
}, [getAccessToken, apiBaseUrl, accounts, connectingNewAccount]);
|
|
5501
|
-
const handleRegisterPasskey = useCallback(async () => {
|
|
5502
|
-
setRegisteringPasskey(true);
|
|
5503
|
-
setError(null);
|
|
5504
|
-
try {
|
|
5505
|
-
const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
|
|
5506
|
-
const { credentialId, publicKey } = await createPasskeyCredential({
|
|
5507
|
-
userId: user?.id ?? "unknown",
|
|
5508
|
-
displayName: passkeyDisplayName
|
|
5509
|
-
});
|
|
5510
|
-
await completePasskeyRegistration(credentialId, publicKey);
|
|
5511
|
-
} catch (err) {
|
|
5512
|
-
if (err instanceof PasskeyIframeBlockedError) {
|
|
5513
|
-
setPasskeyPopupNeeded(true);
|
|
5514
|
-
} else {
|
|
5515
|
-
captureException(err);
|
|
5516
|
-
setError(err instanceof Error ? err.message : "Failed to register passkey");
|
|
5517
|
-
}
|
|
5518
|
-
} finally {
|
|
5519
|
-
setRegisteringPasskey(false);
|
|
5520
|
-
}
|
|
5521
|
-
}, [user, completePasskeyRegistration]);
|
|
5522
|
-
const handleCreatePasskeyViaPopup = useCallback(async () => {
|
|
5523
|
-
setRegisteringPasskey(true);
|
|
5524
|
-
setError(null);
|
|
5670
|
+
const handleConfirmSign = useCallback(async () => {
|
|
5671
|
+
const t = state.transfer ?? polling.transfer;
|
|
5672
|
+
if (!t) return;
|
|
5525
5673
|
try {
|
|
5526
|
-
const
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
displayName: passkeyDisplayName,
|
|
5531
|
-
authToken: token ?? void 0,
|
|
5532
|
-
apiBaseUrl
|
|
5533
|
-
});
|
|
5534
|
-
const { credentialId } = await createPasskeyViaPopup(popupOptions);
|
|
5535
|
-
setActiveCredentialId(credentialId);
|
|
5536
|
-
localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
|
|
5537
|
-
setPasskeyPopupNeeded(false);
|
|
5538
|
-
const resolved = resolvePostAuthStep({
|
|
5539
|
-
hasPasskey: true,
|
|
5540
|
-
accounts,
|
|
5541
|
-
persistedMobileFlow: loadMobileFlowState(),
|
|
5542
|
-
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5543
|
-
connectingNewAccount
|
|
5544
|
-
});
|
|
5545
|
-
if (resolved.clearPersistedFlow) {
|
|
5546
|
-
clearMobileFlowState();
|
|
5547
|
-
}
|
|
5548
|
-
setStep(resolved.step);
|
|
5674
|
+
const signedTransfer = await transferSigning.signTransfer(t.id);
|
|
5675
|
+
clearMobileFlowState();
|
|
5676
|
+
dispatch({ type: "CONFIRM_SIGN_SUCCESS", transfer: signedTransfer });
|
|
5677
|
+
polling.startPolling(t.id);
|
|
5549
5678
|
} catch (err) {
|
|
5550
5679
|
captureException(err);
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5680
|
+
const msg = err instanceof Error ? err.message : "Failed to sign transfer";
|
|
5681
|
+
dispatch({ type: "SET_ERROR", error: msg });
|
|
5682
|
+
onError?.(msg);
|
|
5554
5683
|
}
|
|
5555
|
-
}, [
|
|
5556
|
-
const
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
if (matched) {
|
|
5568
|
-
setActiveCredentialId(matched);
|
|
5569
|
-
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
|
|
5570
|
-
if (token) {
|
|
5571
|
-
reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
|
|
5572
|
-
});
|
|
5573
|
-
}
|
|
5574
|
-
setStep("login");
|
|
5575
|
-
} else {
|
|
5576
|
-
setStep("create-passkey");
|
|
5577
|
-
}
|
|
5578
|
-
} catch {
|
|
5579
|
-
setStep("create-passkey");
|
|
5580
|
-
} finally {
|
|
5581
|
-
setVerifyingPasskeyPopup(false);
|
|
5684
|
+
}, [state.transfer, polling.transfer, polling.startPolling, transferSigning, onError]);
|
|
5685
|
+
const handleRetryMobileStatus = useCallback(() => {
|
|
5686
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5687
|
+
handlingMobileReturnRef.current = false;
|
|
5688
|
+
const currentTransfer = polling.transfer ?? state.transfer;
|
|
5689
|
+
if (currentTransfer?.status === "AUTHORIZED") {
|
|
5690
|
+
void handleAuthorizedMobileReturn(currentTransfer, mobileSetupFlowRef.current);
|
|
5691
|
+
return;
|
|
5692
|
+
}
|
|
5693
|
+
const transferIdToResume = pollingTransferIdRef.current ?? currentTransfer?.id;
|
|
5694
|
+
if (transferIdToResume) {
|
|
5695
|
+
polling.startPolling(transferIdToResume);
|
|
5582
5696
|
}
|
|
5583
|
-
}, [
|
|
5697
|
+
}, [handleAuthorizedMobileReturn, polling, state.transfer]);
|
|
5584
5698
|
const handleSelectProvider = useCallback((providerId) => {
|
|
5585
|
-
|
|
5586
|
-
setSelectedAccountId(null);
|
|
5587
|
-
setConnectingNewAccount(true);
|
|
5699
|
+
dispatch({ type: "SELECT_PROVIDER", providerId });
|
|
5588
5700
|
const isMobile = !shouldUseWalletConnector({
|
|
5589
|
-
useWalletConnector,
|
|
5701
|
+
useWalletConnector: useWalletConnectorProp,
|
|
5590
5702
|
userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent
|
|
5591
5703
|
});
|
|
5592
5704
|
if (isMobile) {
|
|
5593
5705
|
handlingMobileReturnRef.current = false;
|
|
5594
5706
|
mobileSetupFlowRef.current = true;
|
|
5595
|
-
const
|
|
5596
|
-
handlePay(
|
|
5707
|
+
const amount = depositAmount ?? 5;
|
|
5708
|
+
handlePay(amount, { sourceType: "providerId", sourceId: providerId });
|
|
5597
5709
|
} else {
|
|
5598
|
-
|
|
5710
|
+
dispatch({ type: "NAVIGATE", step: "deposit" });
|
|
5599
5711
|
}
|
|
5600
|
-
}, [
|
|
5712
|
+
}, [useWalletConnectorProp, depositAmount, handlePay]);
|
|
5601
5713
|
const handleContinueConnection = useCallback(
|
|
5602
5714
|
(accountId) => {
|
|
5603
|
-
const acct = accounts.find((a) => a.id === accountId);
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5715
|
+
const acct = state.accounts.find((a) => a.id === accountId);
|
|
5716
|
+
dispatch({
|
|
5717
|
+
type: "SELECT_ACCOUNT",
|
|
5718
|
+
accountId,
|
|
5719
|
+
walletId: acct?.wallets[0]?.id ?? null
|
|
5720
|
+
});
|
|
5608
5721
|
},
|
|
5609
|
-
[accounts]
|
|
5722
|
+
[state.accounts]
|
|
5610
5723
|
);
|
|
5611
5724
|
const handleNewPayment = useCallback(() => {
|
|
5612
5725
|
clearMobileFlowState();
|
|
5613
|
-
setStep("deposit");
|
|
5614
|
-
setTransfer(null);
|
|
5615
|
-
setError(null);
|
|
5616
|
-
setAmount(depositAmount != null ? depositAmount.toString() : "");
|
|
5617
|
-
setMobileFlow(false);
|
|
5618
|
-
setDeeplinkUri(null);
|
|
5619
5726
|
processingStartedAtRef.current = null;
|
|
5620
5727
|
pollingTransferIdRef.current = null;
|
|
5621
|
-
mobileSigningTransferIdRef.current = null;
|
|
5622
5728
|
preSelectSourceStepRef.current = null;
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5729
|
+
dispatch({
|
|
5730
|
+
type: "NEW_PAYMENT",
|
|
5731
|
+
depositAmount,
|
|
5732
|
+
firstAccountId: state.accounts.length > 0 ? state.accounts[0].id : null
|
|
5733
|
+
});
|
|
5734
|
+
}, [depositAmount, state.accounts]);
|
|
5627
5735
|
const handleLogout = useCallback(async () => {
|
|
5628
5736
|
try {
|
|
5629
5737
|
await logout();
|
|
@@ -5634,254 +5742,464 @@ function SwypePaymentInner({
|
|
|
5634
5742
|
window.localStorage.removeItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
|
|
5635
5743
|
}
|
|
5636
5744
|
polling.stopPolling();
|
|
5637
|
-
setActiveCredentialId(null);
|
|
5638
|
-
setStep("login");
|
|
5639
|
-
setError(null);
|
|
5640
|
-
setTransfer(null);
|
|
5641
|
-
setCreatingTransfer(false);
|
|
5642
|
-
setRegisteringPasskey(false);
|
|
5643
|
-
setProviders([]);
|
|
5644
|
-
setAccounts([]);
|
|
5645
|
-
setChains([]);
|
|
5646
|
-
setSelectedAccountId(null);
|
|
5647
|
-
setSelectedWalletId(null);
|
|
5648
|
-
setSelectedProviderId(null);
|
|
5649
|
-
setConnectingNewAccount(false);
|
|
5650
|
-
setAmount(depositAmount != null ? depositAmount.toString() : "");
|
|
5651
|
-
setMobileFlow(false);
|
|
5652
|
-
setDeeplinkUri(null);
|
|
5653
5745
|
preSelectSourceStepRef.current = null;
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5746
|
+
setAuthInput("");
|
|
5747
|
+
setOtpCode("");
|
|
5748
|
+
dispatch({ type: "LOGOUT", depositAmount });
|
|
5749
|
+
}, [logout, polling, depositAmount]);
|
|
5750
|
+
const pendingSelectSourceAction = authExecutor.pendingSelectSource;
|
|
5751
|
+
const selectSourceChoices = useMemo(() => {
|
|
5752
|
+
if (!pendingSelectSourceAction) return [];
|
|
5753
|
+
const options = pendingSelectSourceAction.metadata?.options ?? [];
|
|
5754
|
+
return buildSelectSourceChoices(options);
|
|
5755
|
+
}, [pendingSelectSourceAction]);
|
|
5756
|
+
const selectSourceRecommended = useMemo(() => {
|
|
5757
|
+
if (!pendingSelectSourceAction) return null;
|
|
5758
|
+
return pendingSelectSourceAction.metadata?.recommended ?? null;
|
|
5759
|
+
}, [pendingSelectSourceAction]);
|
|
5760
|
+
const handleSelectSourceChainChange = useCallback(
|
|
5761
|
+
(chainName) => {
|
|
5762
|
+
setSelectSourceChainName(chainName);
|
|
5763
|
+
const chain = selectSourceChoices.find((c) => c.chainName === chainName);
|
|
5764
|
+
if (!chain || chain.tokens.length === 0) return;
|
|
5765
|
+
const recommendedToken = selectSourceRecommended?.chainName === chainName ? selectSourceRecommended.tokenSymbol : null;
|
|
5766
|
+
const hasRecommended = !!recommendedToken && chain.tokens.some((t) => t.tokenSymbol === recommendedToken);
|
|
5767
|
+
setSelectSourceTokenSymbol(
|
|
5768
|
+
hasRecommended ? recommendedToken : chain.tokens[0].tokenSymbol
|
|
5769
|
+
);
|
|
5770
|
+
},
|
|
5771
|
+
[selectSourceChoices, selectSourceRecommended]
|
|
5772
|
+
);
|
|
5773
|
+
const handleConfirmSelectSource = useCallback(() => {
|
|
5774
|
+
authExecutor.resolveSelectSource({
|
|
5775
|
+
chainName: selectSourceChainName,
|
|
5776
|
+
tokenSymbol: selectSourceTokenSymbol
|
|
5777
|
+
});
|
|
5778
|
+
}, [authExecutor, selectSourceChainName, selectSourceTokenSymbol]);
|
|
5779
|
+
useEffect(() => {
|
|
5780
|
+
if (depositAmount != null) {
|
|
5781
|
+
dispatch({ type: "SYNC_AMOUNT", amount: depositAmount.toString() });
|
|
5670
5782
|
}
|
|
5671
|
-
}, [
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
);
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
onOtpChange: (code) => {
|
|
5696
|
-
setOtpCode(code);
|
|
5697
|
-
setError(null);
|
|
5698
|
-
},
|
|
5699
|
-
onVerify: handleVerifyLoginCode,
|
|
5700
|
-
onResend: handleResendLoginCode,
|
|
5701
|
-
onBack: () => {
|
|
5702
|
-
setVerificationTarget(null);
|
|
5703
|
-
setOtpCode("");
|
|
5704
|
-
setError(null);
|
|
5705
|
-
setStep("login");
|
|
5706
|
-
},
|
|
5707
|
-
verifying: activeOtpStatus === "submitting-code",
|
|
5708
|
-
error
|
|
5709
|
-
}
|
|
5710
|
-
);
|
|
5711
|
-
}
|
|
5712
|
-
if ((step === "login" || step === "otp-verify") && authenticated) {
|
|
5713
|
-
return /* @__PURE__ */ jsx(ScreenLayout, { children: /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: "48px 0", flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx(Spinner, { label: "Verifying your passkey..." }) }) });
|
|
5714
|
-
}
|
|
5715
|
-
if (step === "verify-passkey") {
|
|
5716
|
-
return /* @__PURE__ */ jsx(
|
|
5717
|
-
VerifyPasskeyScreen,
|
|
5718
|
-
{
|
|
5719
|
-
onVerify: handleVerifyPasskeyViaPopup,
|
|
5720
|
-
onBack: handleLogout,
|
|
5721
|
-
verifying: verifyingPasskeyPopup,
|
|
5722
|
-
error
|
|
5723
|
-
}
|
|
5724
|
-
);
|
|
5725
|
-
}
|
|
5726
|
-
if (step === "create-passkey") {
|
|
5727
|
-
return /* @__PURE__ */ jsx(
|
|
5728
|
-
CreatePasskeyScreen,
|
|
5729
|
-
{
|
|
5730
|
-
onCreatePasskey: handleRegisterPasskey,
|
|
5731
|
-
onBack: handleLogout,
|
|
5732
|
-
creating: registeringPasskey,
|
|
5733
|
-
error,
|
|
5734
|
-
popupFallback: passkeyPopupNeeded,
|
|
5735
|
-
onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup
|
|
5783
|
+
}, [depositAmount]);
|
|
5784
|
+
useEffect(() => {
|
|
5785
|
+
if (authenticated) return;
|
|
5786
|
+
if (activeOtpErrorMessage) dispatch({ type: "SET_ERROR", error: activeOtpErrorMessage });
|
|
5787
|
+
}, [activeOtpErrorMessage, authenticated]);
|
|
5788
|
+
useEffect(() => {
|
|
5789
|
+
if (state.step === "otp-verify" && /^\d{6}$/.test(otpCode.trim()) && activeOtpStatus !== "submitting-code") {
|
|
5790
|
+
handleVerifyLoginCode();
|
|
5791
|
+
}
|
|
5792
|
+
}, [otpCode, state.step, activeOtpStatus, handleVerifyLoginCode]);
|
|
5793
|
+
useEffect(() => {
|
|
5794
|
+
if (!ready || !authenticated) return;
|
|
5795
|
+
if (state.step !== "login" && state.step !== "otp-verify") return;
|
|
5796
|
+
let cancelled = false;
|
|
5797
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5798
|
+
setAuthInput("");
|
|
5799
|
+
setOtpCode("");
|
|
5800
|
+
const restoreOrDeposit = async (credId, token) => {
|
|
5801
|
+
const persisted = loadMobileFlowState();
|
|
5802
|
+
let accts = [];
|
|
5803
|
+
try {
|
|
5804
|
+
accts = await fetchAccounts(apiBaseUrl, token, credId);
|
|
5805
|
+
if (cancelled) return;
|
|
5806
|
+
} catch {
|
|
5736
5807
|
}
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5808
|
+
const resolved = resolvePostAuthStep({
|
|
5809
|
+
hasPasskey: true,
|
|
5810
|
+
accounts: accts,
|
|
5811
|
+
persistedMobileFlow: persisted,
|
|
5812
|
+
mobileSetupInProgress: false,
|
|
5813
|
+
connectingNewAccount: false
|
|
5814
|
+
});
|
|
5815
|
+
if (resolved.clearPersistedFlow) clearMobileFlowState();
|
|
5816
|
+
if (resolved.step === "deposit" && persisted && persisted.isSetup) {
|
|
5817
|
+
try {
|
|
5818
|
+
const existingTransfer = await fetchTransfer(apiBaseUrl, token, persisted.transferId);
|
|
5819
|
+
if (cancelled) return;
|
|
5820
|
+
if (existingTransfer.status === "AUTHORIZED") {
|
|
5821
|
+
await handleAuthorizedMobileReturn(existingTransfer, true);
|
|
5822
|
+
return;
|
|
5823
|
+
}
|
|
5824
|
+
} catch {
|
|
5825
|
+
}
|
|
5749
5826
|
}
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5827
|
+
if (resolved.step === "open-wallet" && persisted) {
|
|
5828
|
+
try {
|
|
5829
|
+
const existingTransfer = await fetchTransfer(apiBaseUrl, token, persisted.transferId);
|
|
5830
|
+
if (cancelled) return;
|
|
5831
|
+
const mobileResolution = resolveRestoredMobileFlow(
|
|
5832
|
+
existingTransfer.status,
|
|
5833
|
+
persisted.isSetup
|
|
5834
|
+
);
|
|
5835
|
+
if (mobileResolution.kind === "resume-setup-deposit") {
|
|
5836
|
+
await handleAuthorizedMobileReturn(existingTransfer, true);
|
|
5837
|
+
return;
|
|
5838
|
+
}
|
|
5839
|
+
if (mobileResolution.kind === "resume-confirm-sign") {
|
|
5840
|
+
await handleAuthorizedMobileReturn(existingTransfer, false);
|
|
5841
|
+
return;
|
|
5842
|
+
}
|
|
5843
|
+
if (mobileResolution.kind === "resume-success") {
|
|
5844
|
+
clearMobileFlowState();
|
|
5845
|
+
dispatch({ type: "MOBILE_RESUME_SUCCESS", transfer: existingTransfer });
|
|
5846
|
+
onComplete?.(existingTransfer);
|
|
5847
|
+
return;
|
|
5848
|
+
}
|
|
5849
|
+
if (mobileResolution.kind === "resume-failed") {
|
|
5850
|
+
clearMobileFlowState();
|
|
5851
|
+
dispatch({ type: "MOBILE_RESUME_FAILED", transfer: existingTransfer });
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
if (mobileResolution.kind === "resume-processing") {
|
|
5855
|
+
clearMobileFlowState();
|
|
5856
|
+
dispatch({ type: "MOBILE_RESUME_PROCESSING", transfer: existingTransfer });
|
|
5857
|
+
polling.startPolling(existingTransfer.id);
|
|
5858
|
+
return;
|
|
5859
|
+
}
|
|
5860
|
+
if (mobileResolution.kind === "resume-stale-setup") {
|
|
5861
|
+
clearMobileFlowState();
|
|
5862
|
+
if (!cancelled) dispatch({ type: "NAVIGATE", step: "wallet-picker" });
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
} catch (err) {
|
|
5866
|
+
if (cancelled) return;
|
|
5867
|
+
dispatch({
|
|
5868
|
+
type: "ENTER_MOBILE_FLOW",
|
|
5869
|
+
deeplinkUri: persisted.deeplinkUri,
|
|
5870
|
+
providerId: persisted.providerId,
|
|
5871
|
+
error: err instanceof Error ? err.message : "Unable to refresh wallet authorization status."
|
|
5872
|
+
});
|
|
5873
|
+
pollingTransferIdRef.current = persisted.transferId;
|
|
5874
|
+
mobileSetupFlowRef.current = persisted.isSetup;
|
|
5875
|
+
polling.startPolling(persisted.transferId);
|
|
5876
|
+
return;
|
|
5877
|
+
}
|
|
5878
|
+
dispatch({
|
|
5879
|
+
type: "ENTER_MOBILE_FLOW",
|
|
5880
|
+
deeplinkUri: persisted.deeplinkUri,
|
|
5881
|
+
providerId: persisted.providerId
|
|
5882
|
+
});
|
|
5883
|
+
pollingTransferIdRef.current = persisted.transferId;
|
|
5884
|
+
mobileSetupFlowRef.current = persisted.isSetup;
|
|
5885
|
+
polling.startPolling(persisted.transferId);
|
|
5886
|
+
return;
|
|
5763
5887
|
}
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5888
|
+
dispatch({ type: "NAVIGATE", step: resolved.step });
|
|
5889
|
+
};
|
|
5890
|
+
const checkPasskey = async () => {
|
|
5891
|
+
try {
|
|
5892
|
+
const token = await getAccessToken();
|
|
5893
|
+
if (!token || cancelled) return;
|
|
5894
|
+
const { config } = await fetchUserConfig(apiBaseUrl, token);
|
|
5895
|
+
if (cancelled) return;
|
|
5896
|
+
const allPasskeys = config.passkeys ?? (config.passkey ? [config.passkey] : []);
|
|
5897
|
+
dispatch({
|
|
5898
|
+
type: "PASSKEY_CONFIG_LOADED",
|
|
5899
|
+
knownIds: allPasskeys.map((p) => p.credentialId),
|
|
5900
|
+
oneTapLimit: config.defaultAllowance ?? void 0
|
|
5901
|
+
});
|
|
5902
|
+
if (allPasskeys.length === 0) {
|
|
5903
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5904
|
+
return;
|
|
5905
|
+
}
|
|
5906
|
+
if (state.activeCredentialId && allPasskeys.some((p) => p.credentialId === state.activeCredentialId)) {
|
|
5907
|
+
await restoreOrDeposit(state.activeCredentialId, token);
|
|
5908
|
+
return;
|
|
5909
|
+
}
|
|
5910
|
+
if (cancelled) return;
|
|
5911
|
+
if (isSafari() && isInCrossOriginIframe()) {
|
|
5912
|
+
dispatch({ type: "NAVIGATE", step: "verify-passkey" });
|
|
5913
|
+
return;
|
|
5914
|
+
}
|
|
5915
|
+
const credentialIds = allPasskeys.map((p) => p.credentialId);
|
|
5916
|
+
const matched = await findDevicePasskey(credentialIds);
|
|
5917
|
+
if (cancelled) return;
|
|
5918
|
+
if (matched) {
|
|
5919
|
+
dispatch({ type: "PASSKEY_ACTIVATED", credentialId: matched });
|
|
5920
|
+
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
|
|
5921
|
+
reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
|
|
5922
|
+
});
|
|
5923
|
+
await restoreOrDeposit(matched, token);
|
|
5924
|
+
return;
|
|
5925
|
+
}
|
|
5926
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5927
|
+
} catch {
|
|
5928
|
+
if (!cancelled) dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5776
5929
|
}
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5930
|
+
};
|
|
5931
|
+
checkPasskey();
|
|
5932
|
+
return () => {
|
|
5933
|
+
cancelled = true;
|
|
5934
|
+
};
|
|
5935
|
+
}, [
|
|
5936
|
+
ready,
|
|
5937
|
+
authenticated,
|
|
5938
|
+
state.step,
|
|
5939
|
+
apiBaseUrl,
|
|
5940
|
+
getAccessToken,
|
|
5941
|
+
state.activeCredentialId,
|
|
5942
|
+
handleAuthorizedMobileReturn,
|
|
5943
|
+
onComplete,
|
|
5944
|
+
polling
|
|
5945
|
+
]);
|
|
5946
|
+
useEffect(() => {
|
|
5947
|
+
const loadAction = resolveDataLoadAction({
|
|
5948
|
+
authenticated,
|
|
5949
|
+
step: state.step,
|
|
5950
|
+
accountsCount: state.accounts.length,
|
|
5951
|
+
hasActiveCredential: !!state.activeCredentialId,
|
|
5952
|
+
loading: loadingDataRef.current
|
|
5953
|
+
});
|
|
5954
|
+
if (loadAction === "reset") {
|
|
5955
|
+
loadingDataRef.current = false;
|
|
5956
|
+
dispatch({ type: "DATA_LOAD_END" });
|
|
5957
|
+
return;
|
|
5782
5958
|
}
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
chainName: selectSourceChainName,
|
|
5832
|
-
tokenSymbol: selectSourceTokenSymbol
|
|
5959
|
+
if (loadAction === "wait") return;
|
|
5960
|
+
const credentialId = state.activeCredentialId;
|
|
5961
|
+
if (!credentialId) {
|
|
5962
|
+
loadingDataRef.current = false;
|
|
5963
|
+
dispatch({ type: "DATA_LOAD_END" });
|
|
5964
|
+
return;
|
|
5965
|
+
}
|
|
5966
|
+
let cancelled = false;
|
|
5967
|
+
loadingDataRef.current = true;
|
|
5968
|
+
const load = async () => {
|
|
5969
|
+
dispatch({ type: "DATA_LOAD_START" });
|
|
5970
|
+
try {
|
|
5971
|
+
const token = await getAccessToken();
|
|
5972
|
+
if (!token) throw new Error("Not authenticated");
|
|
5973
|
+
const [prov, accts, chn] = await Promise.all([
|
|
5974
|
+
fetchProviders(apiBaseUrl, token),
|
|
5975
|
+
fetchAccounts(apiBaseUrl, token, credentialId),
|
|
5976
|
+
fetchChains(apiBaseUrl, token)
|
|
5977
|
+
]);
|
|
5978
|
+
if (cancelled) return;
|
|
5979
|
+
const parsedAmt = depositAmount != null ? depositAmount : 0;
|
|
5980
|
+
const defaults = computeSmartDefaults(accts, parsedAmt);
|
|
5981
|
+
const persisted = loadMobileFlowState();
|
|
5982
|
+
const resolved = resolvePostAuthStep({
|
|
5983
|
+
hasPasskey: !!state.activeCredentialId,
|
|
5984
|
+
accounts: accts,
|
|
5985
|
+
persistedMobileFlow: persisted,
|
|
5986
|
+
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5987
|
+
connectingNewAccount: state.connectingNewAccount
|
|
5988
|
+
});
|
|
5989
|
+
const correctableSteps = ["deposit", "wallet-picker", "open-wallet"];
|
|
5990
|
+
dispatch({
|
|
5991
|
+
type: "DATA_LOADED",
|
|
5992
|
+
providers: prov,
|
|
5993
|
+
accounts: accts,
|
|
5994
|
+
chains: chn,
|
|
5995
|
+
defaults,
|
|
5996
|
+
fallbackProviderId: !defaults && prov.length > 0 ? prov[0].id : null,
|
|
5997
|
+
resolvedStep: correctableSteps.includes(state.step) ? resolved.step : void 0,
|
|
5998
|
+
clearMobileState: resolved.clearPersistedFlow
|
|
5999
|
+
});
|
|
6000
|
+
if (resolved.clearPersistedFlow) clearMobileFlowState();
|
|
6001
|
+
} catch (err) {
|
|
6002
|
+
if (!cancelled) {
|
|
6003
|
+
captureException(err);
|
|
6004
|
+
dispatch({
|
|
6005
|
+
type: "SET_ERROR",
|
|
6006
|
+
error: err instanceof Error ? err.message : "Failed to load data"
|
|
5833
6007
|
});
|
|
5834
|
-
}
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
const succeeded = transfer?.status === "COMPLETED";
|
|
5841
|
-
const displayAmount = transfer?.amount?.amount ?? 0;
|
|
5842
|
-
const displayCurrency = transfer?.amount?.currency ?? "USD";
|
|
5843
|
-
return /* @__PURE__ */ jsx(
|
|
5844
|
-
SuccessScreen,
|
|
5845
|
-
{
|
|
5846
|
-
amount: displayAmount,
|
|
5847
|
-
currency: displayCurrency,
|
|
5848
|
-
succeeded,
|
|
5849
|
-
error,
|
|
5850
|
-
merchantName,
|
|
5851
|
-
sourceName,
|
|
5852
|
-
remainingLimit: succeeded ? (() => {
|
|
5853
|
-
const limit = selectedAccount?.remainingAllowance ?? oneTapLimit;
|
|
5854
|
-
return limit > displayAmount ? limit - displayAmount : 0;
|
|
5855
|
-
})() : void 0,
|
|
5856
|
-
onDone: onDismiss ?? handleNewPayment,
|
|
5857
|
-
onLogout: handleLogout,
|
|
5858
|
-
autoCloseSeconds
|
|
6008
|
+
}
|
|
6009
|
+
} finally {
|
|
6010
|
+
if (!cancelled) {
|
|
6011
|
+
loadingDataRef.current = false;
|
|
6012
|
+
dispatch({ type: "DATA_LOAD_END" });
|
|
6013
|
+
}
|
|
5859
6014
|
}
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
6015
|
+
};
|
|
6016
|
+
load();
|
|
6017
|
+
return () => {
|
|
6018
|
+
cancelled = true;
|
|
6019
|
+
loadingDataRef.current = false;
|
|
6020
|
+
};
|
|
6021
|
+
}, [
|
|
6022
|
+
authenticated,
|
|
6023
|
+
state.step,
|
|
6024
|
+
state.accounts.length,
|
|
6025
|
+
apiBaseUrl,
|
|
6026
|
+
getAccessToken,
|
|
6027
|
+
state.activeCredentialId,
|
|
6028
|
+
depositAmount,
|
|
6029
|
+
state.connectingNewAccount
|
|
6030
|
+
]);
|
|
6031
|
+
useEffect(() => {
|
|
6032
|
+
if (!polling.transfer) return;
|
|
6033
|
+
if (polling.transfer.status === "COMPLETED") {
|
|
6034
|
+
clearMobileFlowState();
|
|
6035
|
+
dispatch({ type: "TRANSFER_COMPLETED", transfer: polling.transfer });
|
|
6036
|
+
onComplete?.(polling.transfer);
|
|
6037
|
+
} else if (polling.transfer.status === "FAILED") {
|
|
6038
|
+
clearMobileFlowState();
|
|
6039
|
+
dispatch({ type: "TRANSFER_FAILED", transfer: polling.transfer, error: "Transfer failed." });
|
|
6040
|
+
}
|
|
6041
|
+
}, [polling.transfer, onComplete]);
|
|
6042
|
+
useEffect(() => {
|
|
6043
|
+
if (state.step !== "processing") {
|
|
6044
|
+
processingStartedAtRef.current = null;
|
|
6045
|
+
return;
|
|
6046
|
+
}
|
|
6047
|
+
if (!processingStartedAtRef.current) {
|
|
6048
|
+
processingStartedAtRef.current = Date.now();
|
|
6049
|
+
}
|
|
6050
|
+
const elapsedMs = Date.now() - processingStartedAtRef.current;
|
|
6051
|
+
const remainingMs = PROCESSING_TIMEOUT_MS - elapsedMs;
|
|
6052
|
+
const handleTimeout = () => {
|
|
6053
|
+
if (!hasProcessingTimedOut(processingStartedAtRef.current, Date.now())) return;
|
|
6054
|
+
const status = getTransferStatus(polling.transfer, state.transfer);
|
|
6055
|
+
const msg = buildProcessingTimeoutMessage(status);
|
|
6056
|
+
captureException(new Error(msg));
|
|
6057
|
+
polling.stopPolling();
|
|
6058
|
+
dispatch({ type: "PROCESSING_TIMEOUT", error: msg });
|
|
6059
|
+
onError?.(msg);
|
|
6060
|
+
};
|
|
6061
|
+
if (remainingMs <= 0) {
|
|
6062
|
+
handleTimeout();
|
|
6063
|
+
return;
|
|
6064
|
+
}
|
|
6065
|
+
const timeoutId = window.setTimeout(handleTimeout, remainingMs);
|
|
6066
|
+
return () => window.clearTimeout(timeoutId);
|
|
6067
|
+
}, [state.step, polling.transfer, state.transfer, polling.stopPolling, onError]);
|
|
6068
|
+
useEffect(() => {
|
|
6069
|
+
if (!state.mobileFlow) {
|
|
6070
|
+
handlingMobileReturnRef.current = false;
|
|
6071
|
+
return;
|
|
6072
|
+
}
|
|
6073
|
+
if (handlingMobileReturnRef.current) return;
|
|
6074
|
+
const polledTransfer = polling.transfer;
|
|
6075
|
+
if (!polledTransfer || polledTransfer.status !== "AUTHORIZED") return;
|
|
6076
|
+
void handleAuthorizedMobileReturn(polledTransfer, mobileSetupFlowRef.current);
|
|
6077
|
+
}, [state.mobileFlow, polling.transfer, handleAuthorizedMobileReturn]);
|
|
6078
|
+
useEffect(() => {
|
|
6079
|
+
if (!state.mobileFlow) return;
|
|
6080
|
+
if (handlingMobileReturnRef.current) return;
|
|
6081
|
+
const transferIdToResume = pollingTransferIdRef.current ?? state.transfer?.id;
|
|
6082
|
+
if (!transferIdToResume) return;
|
|
6083
|
+
if (!polling.isPolling) polling.startPolling(transferIdToResume);
|
|
6084
|
+
const handleVisibility = () => {
|
|
6085
|
+
if (document.visibilityState === "visible" && !handlingMobileReturnRef.current) {
|
|
6086
|
+
polling.startPolling(transferIdToResume);
|
|
5881
6087
|
}
|
|
6088
|
+
};
|
|
6089
|
+
document.addEventListener("visibilitychange", handleVisibility);
|
|
6090
|
+
return () => document.removeEventListener("visibilitychange", handleVisibility);
|
|
6091
|
+
}, [state.mobileFlow, state.transfer?.id, polling.isPolling, polling.startPolling]);
|
|
6092
|
+
useEffect(() => {
|
|
6093
|
+
if (!pendingSelectSourceAction) {
|
|
6094
|
+
initializedSelectSourceActionRef.current = null;
|
|
6095
|
+
setSelectSourceChainName("");
|
|
6096
|
+
setSelectSourceTokenSymbol("");
|
|
6097
|
+
return;
|
|
6098
|
+
}
|
|
6099
|
+
if (initializedSelectSourceActionRef.current === pendingSelectSourceAction.id) return;
|
|
6100
|
+
const hasRecommended = !!selectSourceRecommended && selectSourceChoices.some(
|
|
6101
|
+
(chain) => chain.chainName === selectSourceRecommended.chainName && chain.tokens.some((t) => t.tokenSymbol === selectSourceRecommended.tokenSymbol)
|
|
5882
6102
|
);
|
|
5883
|
-
|
|
5884
|
-
|
|
6103
|
+
if (hasRecommended && selectSourceRecommended) {
|
|
6104
|
+
setSelectSourceChainName(selectSourceRecommended.chainName);
|
|
6105
|
+
setSelectSourceTokenSymbol(selectSourceRecommended.tokenSymbol);
|
|
6106
|
+
} else if (selectSourceChoices.length > 0 && selectSourceChoices[0].tokens.length > 0) {
|
|
6107
|
+
setSelectSourceChainName(selectSourceChoices[0].chainName);
|
|
6108
|
+
setSelectSourceTokenSymbol(selectSourceChoices[0].tokens[0].tokenSymbol);
|
|
6109
|
+
} else {
|
|
6110
|
+
setSelectSourceChainName("Base");
|
|
6111
|
+
setSelectSourceTokenSymbol("USDC");
|
|
6112
|
+
}
|
|
6113
|
+
initializedSelectSourceActionRef.current = pendingSelectSourceAction.id;
|
|
6114
|
+
}, [pendingSelectSourceAction, selectSourceChoices, selectSourceRecommended]);
|
|
6115
|
+
useEffect(() => {
|
|
6116
|
+
if (pendingSelectSourceAction && state.step === "processing") {
|
|
6117
|
+
preSelectSourceStepRef.current = state.step;
|
|
6118
|
+
dispatch({ type: "NAVIGATE", step: "select-source" });
|
|
6119
|
+
} else if (!pendingSelectSourceAction && state.step === "select-source") {
|
|
6120
|
+
dispatch({ type: "NAVIGATE", step: preSelectSourceStepRef.current ?? "processing" });
|
|
6121
|
+
preSelectSourceStepRef.current = null;
|
|
6122
|
+
}
|
|
6123
|
+
}, [pendingSelectSourceAction, state.step]);
|
|
6124
|
+
const handlers = useMemo(() => ({
|
|
6125
|
+
onSendLoginCode: handleSendLoginCode,
|
|
6126
|
+
onVerifyLoginCode: handleVerifyLoginCode,
|
|
6127
|
+
onResendLoginCode: handleResendLoginCode,
|
|
6128
|
+
onBackFromOtp: () => {
|
|
6129
|
+
setOtpCode("");
|
|
6130
|
+
dispatch({ type: "BACK_TO_LOGIN" });
|
|
6131
|
+
},
|
|
6132
|
+
onRegisterPasskey: handleRegisterPasskey,
|
|
6133
|
+
onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup,
|
|
6134
|
+
onVerifyPasskeyViaPopup: handleVerifyPasskeyViaPopup,
|
|
6135
|
+
onSelectProvider: handleSelectProvider,
|
|
6136
|
+
onContinueConnection: handleContinueConnection,
|
|
6137
|
+
onPay: handlePay,
|
|
6138
|
+
onIncreaseLimit: handleIncreaseLimit,
|
|
6139
|
+
onConfirmSign: handleConfirmSign,
|
|
6140
|
+
onRetryMobileStatus: handleRetryMobileStatus,
|
|
6141
|
+
onLogout: handleLogout,
|
|
6142
|
+
onNewPayment: handleNewPayment,
|
|
6143
|
+
onNavigate: (step) => dispatch({ type: "NAVIGATE", step }),
|
|
6144
|
+
onSetAuthInput: setAuthInput,
|
|
6145
|
+
onSetOtpCode: (code) => {
|
|
6146
|
+
setOtpCode(code);
|
|
6147
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
6148
|
+
},
|
|
6149
|
+
onSelectSourceChainChange: handleSelectSourceChainChange,
|
|
6150
|
+
onSetSelectSourceTokenSymbol: setSelectSourceTokenSymbol,
|
|
6151
|
+
onConfirmSelectSource: handleConfirmSelectSource
|
|
6152
|
+
}), [
|
|
6153
|
+
handleSendLoginCode,
|
|
6154
|
+
handleVerifyLoginCode,
|
|
6155
|
+
handleResendLoginCode,
|
|
6156
|
+
handleRegisterPasskey,
|
|
6157
|
+
handleCreatePasskeyViaPopup,
|
|
6158
|
+
handleVerifyPasskeyViaPopup,
|
|
6159
|
+
handleSelectProvider,
|
|
6160
|
+
handleContinueConnection,
|
|
6161
|
+
handlePay,
|
|
6162
|
+
handleIncreaseLimit,
|
|
6163
|
+
handleConfirmSign,
|
|
6164
|
+
handleRetryMobileStatus,
|
|
6165
|
+
handleLogout,
|
|
6166
|
+
handleNewPayment,
|
|
6167
|
+
handleSelectSourceChainChange,
|
|
6168
|
+
handleConfirmSelectSource
|
|
6169
|
+
]);
|
|
6170
|
+
return /* @__PURE__ */ jsx(
|
|
6171
|
+
StepRenderer,
|
|
6172
|
+
{
|
|
6173
|
+
state,
|
|
6174
|
+
ready,
|
|
6175
|
+
authenticated,
|
|
6176
|
+
activeOtpStatus,
|
|
6177
|
+
pollingTransfer: polling.transfer,
|
|
6178
|
+
pollingError: polling.error,
|
|
6179
|
+
authExecutorError: authExecutor.error,
|
|
6180
|
+
transferSigningSigning: transferSigning.signing,
|
|
6181
|
+
transferSigningError: transferSigning.error,
|
|
6182
|
+
pendingConnections,
|
|
6183
|
+
sourceName,
|
|
6184
|
+
sourceAddress,
|
|
6185
|
+
sourceVerified,
|
|
6186
|
+
maxSourceBalance,
|
|
6187
|
+
tokenCount,
|
|
6188
|
+
selectedAccount,
|
|
6189
|
+
selectSourceChoices,
|
|
6190
|
+
selectSourceRecommended,
|
|
6191
|
+
authInput,
|
|
6192
|
+
otpCode,
|
|
6193
|
+
selectSourceChainName,
|
|
6194
|
+
selectSourceTokenSymbol,
|
|
6195
|
+
merchantName,
|
|
6196
|
+
onBack,
|
|
6197
|
+
onDismiss,
|
|
6198
|
+
autoCloseSeconds,
|
|
6199
|
+
depositAmount,
|
|
6200
|
+
handlers
|
|
6201
|
+
}
|
|
6202
|
+
);
|
|
5885
6203
|
}
|
|
5886
6204
|
|
|
5887
6205
|
export { AdvancedSourceScreen, CreatePasskeyScreen, IconCircle, InfoBanner, OutlineButton, PasskeyIframeBlockedError, PoweredByFooter, PrimaryButton, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, Spinner, StepList, SwypePayment, SwypeProvider, buildPasskeyPopupOptions, createPasskeyCredential, createPasskeyViaPopup, darkTheme, deviceHasPasskey, findDevicePasskey, findDevicePasskeyViaPopup, getTheme, lightTheme, resolvePasskeyRpId, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
|