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