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