@swype-org/react-sdk 0.1.87 → 0.1.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1421 -1158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -28
- package/dist/index.d.ts +12 -28
- package/dist/index.js +1422 -1159
- 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';
|
|
@@ -725,10 +725,10 @@ function isSafari() {
|
|
|
725
725
|
var POPUP_RESULT_TIMEOUT_MS = 12e4;
|
|
726
726
|
var POPUP_CLOSED_POLL_MS = 500;
|
|
727
727
|
var POPUP_CLOSED_GRACE_MS = 1e3;
|
|
728
|
-
function createPasskeyViaPopup(options
|
|
728
|
+
function createPasskeyViaPopup(options) {
|
|
729
729
|
return new Promise((resolve, reject) => {
|
|
730
|
-
const
|
|
731
|
-
const payload = { ...options,
|
|
730
|
+
const verificationToken = crypto.randomUUID();
|
|
731
|
+
const payload = { ...options, verificationToken };
|
|
732
732
|
const encoded = btoa(JSON.stringify(payload));
|
|
733
733
|
const popupUrl = `${window.location.origin}/passkey-register#${encoded}`;
|
|
734
734
|
const popup = window.open(popupUrl, "swype-passkey");
|
|
@@ -737,22 +737,21 @@ function createPasskeyViaPopup(options, existingCredentialIds = []) {
|
|
|
737
737
|
return;
|
|
738
738
|
}
|
|
739
739
|
let settled = false;
|
|
740
|
-
const channel = typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(channelId) : null;
|
|
741
740
|
const timer = setTimeout(() => {
|
|
742
741
|
cleanup();
|
|
743
742
|
reject(new Error("Passkey creation timed out. Please try again."));
|
|
744
743
|
}, POPUP_RESULT_TIMEOUT_MS);
|
|
745
|
-
let closedGraceTimer = null;
|
|
746
744
|
const closedPoll = setInterval(() => {
|
|
747
745
|
if (popup.closed) {
|
|
748
746
|
clearInterval(closedPoll);
|
|
749
|
-
|
|
747
|
+
setTimeout(() => {
|
|
750
748
|
if (!settled) {
|
|
749
|
+
settled = true;
|
|
751
750
|
cleanup();
|
|
752
|
-
|
|
751
|
+
checkServerForPasskeyByToken(
|
|
753
752
|
options.authToken,
|
|
754
753
|
options.apiBaseUrl,
|
|
755
|
-
|
|
754
|
+
verificationToken
|
|
756
755
|
).then((result) => {
|
|
757
756
|
if (result) {
|
|
758
757
|
resolve(result);
|
|
@@ -766,45 +765,18 @@ function createPasskeyViaPopup(options, existingCredentialIds = []) {
|
|
|
766
765
|
}, POPUP_CLOSED_GRACE_MS);
|
|
767
766
|
}
|
|
768
767
|
}, POPUP_CLOSED_POLL_MS);
|
|
769
|
-
function handleResult(data) {
|
|
770
|
-
if (settled) return;
|
|
771
|
-
if (!data || typeof data !== "object") return;
|
|
772
|
-
if (data.type !== "swype:passkey-popup-result") return;
|
|
773
|
-
settled = true;
|
|
774
|
-
cleanup();
|
|
775
|
-
if (data.error) {
|
|
776
|
-
reject(new Error(data.error));
|
|
777
|
-
} else if (data.result) {
|
|
778
|
-
resolve(data.result);
|
|
779
|
-
} else {
|
|
780
|
-
reject(new Error("Invalid passkey popup response."));
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
if (channel) {
|
|
784
|
-
channel.onmessage = (event) => handleResult(event.data);
|
|
785
|
-
}
|
|
786
|
-
const postMessageHandler = (event) => {
|
|
787
|
-
if (event.source !== popup) return;
|
|
788
|
-
handleResult(event.data);
|
|
789
|
-
};
|
|
790
|
-
window.addEventListener("message", postMessageHandler);
|
|
791
768
|
function cleanup() {
|
|
792
769
|
clearTimeout(timer);
|
|
793
770
|
clearInterval(closedPoll);
|
|
794
|
-
if (closedGraceTimer) clearTimeout(closedGraceTimer);
|
|
795
|
-
window.removeEventListener("message", postMessageHandler);
|
|
796
|
-
channel?.close();
|
|
797
771
|
}
|
|
798
772
|
});
|
|
799
773
|
}
|
|
800
774
|
var VERIFY_POPUP_TIMEOUT_MS = 6e4;
|
|
801
775
|
function findDevicePasskeyViaPopup(options) {
|
|
802
776
|
return new Promise((resolve, reject) => {
|
|
803
|
-
const channelId = `swype-pv-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
804
777
|
const verificationToken = crypto.randomUUID();
|
|
805
778
|
const payload = {
|
|
806
779
|
...options,
|
|
807
|
-
channelId,
|
|
808
780
|
verificationToken
|
|
809
781
|
};
|
|
810
782
|
const encoded = btoa(JSON.stringify(payload));
|
|
@@ -815,7 +787,6 @@ function findDevicePasskeyViaPopup(options) {
|
|
|
815
787
|
return;
|
|
816
788
|
}
|
|
817
789
|
let settled = false;
|
|
818
|
-
const channel = typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(channelId) : null;
|
|
819
790
|
const timer = setTimeout(() => {
|
|
820
791
|
cleanup();
|
|
821
792
|
resolve(null);
|
|
@@ -825,13 +796,14 @@ function findDevicePasskeyViaPopup(options) {
|
|
|
825
796
|
clearInterval(closedPoll);
|
|
826
797
|
setTimeout(() => {
|
|
827
798
|
if (!settled) {
|
|
799
|
+
settled = true;
|
|
828
800
|
cleanup();
|
|
829
|
-
|
|
801
|
+
checkServerForPasskeyByToken(
|
|
830
802
|
options.authToken,
|
|
831
803
|
options.apiBaseUrl,
|
|
832
804
|
verificationToken
|
|
833
|
-
).then((
|
|
834
|
-
resolve(credentialId);
|
|
805
|
+
).then((result) => {
|
|
806
|
+
resolve(result?.credentialId ?? null);
|
|
835
807
|
}).catch(() => {
|
|
836
808
|
resolve(null);
|
|
837
809
|
});
|
|
@@ -839,38 +811,13 @@ function findDevicePasskeyViaPopup(options) {
|
|
|
839
811
|
}, POPUP_CLOSED_GRACE_MS);
|
|
840
812
|
}
|
|
841
813
|
}, POPUP_CLOSED_POLL_MS);
|
|
842
|
-
function handleResult(data) {
|
|
843
|
-
if (settled) return;
|
|
844
|
-
if (!data || typeof data !== "object") return;
|
|
845
|
-
if (data.type !== "swype:passkey-verify-result") return;
|
|
846
|
-
settled = true;
|
|
847
|
-
cleanup();
|
|
848
|
-
if (data.error) {
|
|
849
|
-
resolve(null);
|
|
850
|
-
} else if (data.result && typeof data.result === "object") {
|
|
851
|
-
const result = data.result;
|
|
852
|
-
resolve(result.credentialId ?? null);
|
|
853
|
-
} else {
|
|
854
|
-
resolve(null);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
if (channel) {
|
|
858
|
-
channel.onmessage = (event) => handleResult(event.data);
|
|
859
|
-
}
|
|
860
|
-
const postMessageHandler = (event) => {
|
|
861
|
-
if (event.source !== popup) return;
|
|
862
|
-
handleResult(event.data);
|
|
863
|
-
};
|
|
864
|
-
window.addEventListener("message", postMessageHandler);
|
|
865
814
|
function cleanup() {
|
|
866
815
|
clearTimeout(timer);
|
|
867
816
|
clearInterval(closedPoll);
|
|
868
|
-
window.removeEventListener("message", postMessageHandler);
|
|
869
|
-
channel?.close();
|
|
870
817
|
}
|
|
871
818
|
});
|
|
872
819
|
}
|
|
873
|
-
async function
|
|
820
|
+
async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
|
|
874
821
|
if (!authToken || !apiBaseUrl) return null;
|
|
875
822
|
const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
|
|
876
823
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
@@ -879,19 +826,7 @@ async function checkServerForVerifiedPasskey(authToken, apiBaseUrl, verification
|
|
|
879
826
|
const body = await res.json();
|
|
880
827
|
const passkeys = body.config.passkeys ?? [];
|
|
881
828
|
const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
|
|
882
|
-
return matched
|
|
883
|
-
}
|
|
884
|
-
async function checkServerForNewPasskey(authToken, apiBaseUrl, existingCredentialIds) {
|
|
885
|
-
if (!authToken || !apiBaseUrl) return null;
|
|
886
|
-
const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
|
|
887
|
-
headers: { Authorization: `Bearer ${authToken}` }
|
|
888
|
-
});
|
|
889
|
-
if (!res.ok) return null;
|
|
890
|
-
const body = await res.json();
|
|
891
|
-
const passkeys = body.config.passkeys ?? [];
|
|
892
|
-
const existingSet = new Set(existingCredentialIds);
|
|
893
|
-
const newPasskey = passkeys.find((p) => !existingSet.has(p.credentialId));
|
|
894
|
-
return newPasskey ?? null;
|
|
829
|
+
return matched ? { credentialId: matched.credentialId, publicKey: matched.publicKey } : null;
|
|
895
830
|
}
|
|
896
831
|
|
|
897
832
|
// src/hooks.ts
|
|
@@ -1645,43 +1580,6 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
1645
1580
|
);
|
|
1646
1581
|
return { signing, signPayload, error, signTransfer: signTransfer2 };
|
|
1647
1582
|
}
|
|
1648
|
-
function Spinner({ size = 40, label }) {
|
|
1649
|
-
const { tokens } = useSwypeConfig();
|
|
1650
|
-
return /* @__PURE__ */ jsxs(
|
|
1651
|
-
"div",
|
|
1652
|
-
{
|
|
1653
|
-
style: {
|
|
1654
|
-
display: "flex",
|
|
1655
|
-
flexDirection: "column",
|
|
1656
|
-
alignItems: "center",
|
|
1657
|
-
gap: "12px"
|
|
1658
|
-
},
|
|
1659
|
-
children: [
|
|
1660
|
-
/* @__PURE__ */ jsx(
|
|
1661
|
-
"div",
|
|
1662
|
-
{
|
|
1663
|
-
style: {
|
|
1664
|
-
width: size,
|
|
1665
|
-
height: size,
|
|
1666
|
-
border: `4px solid ${tokens.bgInput}`,
|
|
1667
|
-
borderTopColor: tokens.accent,
|
|
1668
|
-
borderRightColor: tokens.accent + "66",
|
|
1669
|
-
borderRadius: "50%",
|
|
1670
|
-
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.1)",
|
|
1671
|
-
animation: "swype-spin 0.9s linear infinite"
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
),
|
|
1675
|
-
label && /* @__PURE__ */ jsx("p", { style: { color: tokens.textSecondary, fontSize: "0.875rem", margin: 0 }, children: label }),
|
|
1676
|
-
/* @__PURE__ */ jsx("style", { children: `
|
|
1677
|
-
@keyframes swype-spin {
|
|
1678
|
-
to { transform: rotate(360deg); }
|
|
1679
|
-
}
|
|
1680
|
-
` })
|
|
1681
|
-
]
|
|
1682
|
-
}
|
|
1683
|
-
);
|
|
1684
|
-
}
|
|
1685
1583
|
|
|
1686
1584
|
// src/auth.ts
|
|
1687
1585
|
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -1843,6 +1741,426 @@ function resolveDataLoadAction({
|
|
|
1843
1741
|
}
|
|
1844
1742
|
return "load";
|
|
1845
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
|
+
}
|
|
1846
2164
|
var FOOTER_CSS = `
|
|
1847
2165
|
.swype-screen-footer {
|
|
1848
2166
|
padding-bottom: max(24px, env(safe-area-inset-bottom, 24px));
|
|
@@ -4559,11 +4877,248 @@ var errorStyle2 = (color) => ({
|
|
|
4559
4877
|
color: "#ef4444",
|
|
4560
4878
|
margin: "8px 0 0"
|
|
4561
4879
|
});
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
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..." });
|
|
4916
|
+
}
|
|
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
|
+
}
|
|
4567
5122
|
static getDerivedStateFromError() {
|
|
4568
5123
|
return { hasError: true };
|
|
4569
5124
|
}
|
|
@@ -4627,98 +5182,6 @@ var buttonStyle3 = {
|
|
|
4627
5182
|
fontFamily: "inherit",
|
|
4628
5183
|
cursor: "pointer"
|
|
4629
5184
|
};
|
|
4630
|
-
var ACTIVE_CREDENTIAL_STORAGE_KEY = "swype_active_credential_id";
|
|
4631
|
-
var MOBILE_FLOW_STORAGE_KEY = "swype_mobile_flow";
|
|
4632
|
-
var MIN_SEND_AMOUNT_USD = 0.25;
|
|
4633
|
-
function persistMobileFlowState(data) {
|
|
4634
|
-
try {
|
|
4635
|
-
sessionStorage.setItem(MOBILE_FLOW_STORAGE_KEY, JSON.stringify(data));
|
|
4636
|
-
} catch {
|
|
4637
|
-
}
|
|
4638
|
-
}
|
|
4639
|
-
function loadMobileFlowState() {
|
|
4640
|
-
try {
|
|
4641
|
-
const raw = sessionStorage.getItem(MOBILE_FLOW_STORAGE_KEY);
|
|
4642
|
-
if (!raw) return null;
|
|
4643
|
-
return JSON.parse(raw);
|
|
4644
|
-
} catch {
|
|
4645
|
-
return null;
|
|
4646
|
-
}
|
|
4647
|
-
}
|
|
4648
|
-
function clearMobileFlowState() {
|
|
4649
|
-
try {
|
|
4650
|
-
sessionStorage.removeItem(MOBILE_FLOW_STORAGE_KEY);
|
|
4651
|
-
} catch {
|
|
4652
|
-
}
|
|
4653
|
-
}
|
|
4654
|
-
function computeSmartDefaults(accts, transferAmount) {
|
|
4655
|
-
if (accts.length === 0) return null;
|
|
4656
|
-
for (const acct of accts) {
|
|
4657
|
-
for (const wallet of acct.wallets) {
|
|
4658
|
-
if (wallet.status === "ACTIVE") {
|
|
4659
|
-
const bestSource = wallet.sources.find(
|
|
4660
|
-
(s) => s.balance.available.amount >= transferAmount
|
|
4661
|
-
);
|
|
4662
|
-
if (bestSource) {
|
|
4663
|
-
return { accountId: acct.id, walletId: wallet.id };
|
|
4664
|
-
}
|
|
4665
|
-
}
|
|
4666
|
-
}
|
|
4667
|
-
}
|
|
4668
|
-
let bestAccount = null;
|
|
4669
|
-
let bestWallet = null;
|
|
4670
|
-
let bestBalance = -1;
|
|
4671
|
-
let bestIsActive = false;
|
|
4672
|
-
for (const acct of accts) {
|
|
4673
|
-
for (const wallet of acct.wallets) {
|
|
4674
|
-
const walletBal = wallet.balance.available.amount;
|
|
4675
|
-
const isActive = wallet.status === "ACTIVE";
|
|
4676
|
-
if (walletBal > bestBalance || walletBal === bestBalance && isActive && !bestIsActive) {
|
|
4677
|
-
bestBalance = walletBal;
|
|
4678
|
-
bestAccount = acct;
|
|
4679
|
-
bestWallet = wallet;
|
|
4680
|
-
bestIsActive = isActive;
|
|
4681
|
-
}
|
|
4682
|
-
}
|
|
4683
|
-
}
|
|
4684
|
-
if (bestAccount) {
|
|
4685
|
-
return {
|
|
4686
|
-
accountId: bestAccount.id,
|
|
4687
|
-
walletId: bestWallet?.id ?? null
|
|
4688
|
-
};
|
|
4689
|
-
}
|
|
4690
|
-
return { accountId: accts[0].id, walletId: null };
|
|
4691
|
-
}
|
|
4692
|
-
function parseRawBalance(rawBalance, decimals) {
|
|
4693
|
-
const parsed = Number(rawBalance);
|
|
4694
|
-
if (!Number.isFinite(parsed)) return 0;
|
|
4695
|
-
return parsed / 10 ** decimals;
|
|
4696
|
-
}
|
|
4697
|
-
function buildSelectSourceChoices(options) {
|
|
4698
|
-
const chainChoices = [];
|
|
4699
|
-
const chainIndexByName = /* @__PURE__ */ new Map();
|
|
4700
|
-
for (const option of options) {
|
|
4701
|
-
const { chainName, tokenSymbol } = option;
|
|
4702
|
-
const balance = parseRawBalance(option.rawBalance, option.decimals);
|
|
4703
|
-
let chainChoice;
|
|
4704
|
-
const existingIdx = chainIndexByName.get(chainName);
|
|
4705
|
-
if (existingIdx === void 0) {
|
|
4706
|
-
chainChoice = { chainName, balance: 0, tokens: [] };
|
|
4707
|
-
chainIndexByName.set(chainName, chainChoices.length);
|
|
4708
|
-
chainChoices.push(chainChoice);
|
|
4709
|
-
} else {
|
|
4710
|
-
chainChoice = chainChoices[existingIdx];
|
|
4711
|
-
}
|
|
4712
|
-
chainChoice.balance += balance;
|
|
4713
|
-
const existing = chainChoice.tokens.find((t) => t.tokenSymbol === tokenSymbol);
|
|
4714
|
-
if (existing) {
|
|
4715
|
-
existing.balance += balance;
|
|
4716
|
-
} else {
|
|
4717
|
-
chainChoice.tokens.push({ tokenSymbol, balance });
|
|
4718
|
-
}
|
|
4719
|
-
}
|
|
4720
|
-
return chainChoices;
|
|
4721
|
-
}
|
|
4722
5185
|
function SwypePayment(props) {
|
|
4723
5186
|
const resetKey = useRef(0);
|
|
4724
5187
|
const handleBoundaryReset = useCallback(() => {
|
|
@@ -4730,7 +5193,7 @@ function SwypePaymentInner({
|
|
|
4730
5193
|
destination,
|
|
4731
5194
|
onComplete,
|
|
4732
5195
|
onError,
|
|
4733
|
-
useWalletConnector,
|
|
5196
|
+
useWalletConnector: useWalletConnectorProp,
|
|
4734
5197
|
idempotencyKey,
|
|
4735
5198
|
merchantAuthorization,
|
|
4736
5199
|
merchantName,
|
|
@@ -4738,7 +5201,7 @@ function SwypePaymentInner({
|
|
|
4738
5201
|
onDismiss,
|
|
4739
5202
|
autoCloseSeconds
|
|
4740
5203
|
}) {
|
|
4741
|
-
const { apiBaseUrl,
|
|
5204
|
+
const { apiBaseUrl, depositAmount } = useSwypeConfig();
|
|
4742
5205
|
const { ready, authenticated, user, logout, getAccessToken } = usePrivy();
|
|
4743
5206
|
const {
|
|
4744
5207
|
sendCode: sendEmailCode,
|
|
@@ -4750,161 +5213,74 @@ function SwypePaymentInner({
|
|
|
4750
5213
|
loginWithCode: loginWithSmsCode,
|
|
4751
5214
|
state: smsLoginState
|
|
4752
5215
|
} = useLoginWithSms();
|
|
4753
|
-
|
|
4754
|
-
const [
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
const [selectedProviderId, setSelectedProviderId] = useState(null);
|
|
4763
|
-
const [connectingNewAccount, setConnectingNewAccount] = useState(false);
|
|
4764
|
-
const [amount, setAmount] = useState(
|
|
4765
|
-
depositAmount != null ? depositAmount.toString() : ""
|
|
4766
|
-
);
|
|
4767
|
-
const [transfer, setTransfer] = useState(null);
|
|
4768
|
-
const [creatingTransfer, setCreatingTransfer] = useState(false);
|
|
4769
|
-
const [registeringPasskey, setRegisteringPasskey] = useState(false);
|
|
4770
|
-
const [verifyingPasskeyPopup, setVerifyingPasskeyPopup] = useState(false);
|
|
4771
|
-
const [passkeyPopupNeeded, setPasskeyPopupNeeded] = useState(
|
|
4772
|
-
() => 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
|
|
4773
5225
|
);
|
|
4774
|
-
const [activeCredentialId, setActiveCredentialId] = useState(() => {
|
|
4775
|
-
if (typeof window === "undefined") return null;
|
|
4776
|
-
return window.localStorage.getItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
|
|
4777
|
-
});
|
|
4778
|
-
const [knownCredentialIds, setKnownCredentialIds] = useState([]);
|
|
4779
|
-
const [authInput, setAuthInput] = useState("");
|
|
4780
|
-
const [verificationTarget, setVerificationTarget] = useState(null);
|
|
4781
|
-
const [otpCode, setOtpCode] = useState("");
|
|
4782
|
-
const [oneTapLimit, setOneTapLimit] = useState(100);
|
|
4783
|
-
const [mobileFlow, setMobileFlow] = useState(false);
|
|
4784
|
-
const [deeplinkUri, setDeeplinkUri] = useState(null);
|
|
4785
5226
|
const loadingDataRef = useRef(false);
|
|
4786
5227
|
const pollingTransferIdRef = useRef(null);
|
|
4787
|
-
const mobileSigningTransferIdRef = useRef(null);
|
|
4788
5228
|
const mobileSetupFlowRef = useRef(false);
|
|
4789
5229
|
const handlingMobileReturnRef = useRef(false);
|
|
4790
5230
|
const processingStartedAtRef = useRef(null);
|
|
4791
|
-
const [selectSourceChainName, setSelectSourceChainName] = useState("");
|
|
4792
|
-
const [selectSourceTokenSymbol, setSelectSourceTokenSymbol] = useState("");
|
|
4793
5231
|
const initializedSelectSourceActionRef = useRef(null);
|
|
4794
5232
|
const preSelectSourceStepRef = useRef(null);
|
|
4795
|
-
const
|
|
4796
|
-
const
|
|
4797
|
-
const
|
|
4798
|
-
const
|
|
4799
|
-
const
|
|
4800
|
-
const
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
pollingTransferIdRef.current = persisted.transferId;
|
|
4826
|
-
mobileSetupFlowRef.current = persisted.isSetup;
|
|
4827
|
-
setError(errorMessage ?? null);
|
|
4828
|
-
setStep("open-wallet");
|
|
4829
|
-
polling.startPolling(persisted.transferId);
|
|
4830
|
-
}, [polling]);
|
|
4831
|
-
const handleAuthorizedMobileReturn = useCallback(async (authorizedTransfer, isSetup) => {
|
|
4832
|
-
if (handlingMobileReturnRef.current) return;
|
|
4833
|
-
handlingMobileReturnRef.current = true;
|
|
4834
|
-
polling.stopPolling();
|
|
4835
|
-
if (isSetup) {
|
|
4836
|
-
mobileSetupFlowRef.current = false;
|
|
4837
|
-
clearMobileFlowState();
|
|
4838
|
-
try {
|
|
4839
|
-
await reloadAccounts();
|
|
4840
|
-
resetDataLoadingState();
|
|
4841
|
-
setTransfer(authorizedTransfer);
|
|
4842
|
-
setError(null);
|
|
4843
|
-
setDeeplinkUri(null);
|
|
4844
|
-
setMobileFlow(false);
|
|
4845
|
-
setStep("deposit");
|
|
4846
|
-
} catch (err) {
|
|
4847
|
-
handlingMobileReturnRef.current = false;
|
|
4848
|
-
setError(
|
|
4849
|
-
err instanceof Error ? err.message : "Wallet authorized, but we could not refresh your account yet."
|
|
4850
|
-
);
|
|
4851
|
-
setStep("open-wallet");
|
|
5233
|
+
const [authInput, setAuthInput] = useState("");
|
|
5234
|
+
const [otpCode, setOtpCode] = useState("");
|
|
5235
|
+
const [selectSourceChainName, setSelectSourceChainName] = useState("");
|
|
5236
|
+
const [selectSourceTokenSymbol, setSelectSourceTokenSymbol] = useState("");
|
|
5237
|
+
const authExecutor = useAuthorizationExecutor();
|
|
5238
|
+
const polling = useTransferPolling();
|
|
5239
|
+
const transferSigning = useTransferSigning();
|
|
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
|
+
}
|
|
4852
5263
|
}
|
|
4853
|
-
return;
|
|
4854
|
-
}
|
|
4855
|
-
setTransfer(authorizedTransfer);
|
|
4856
|
-
mobileSetupFlowRef.current = false;
|
|
4857
|
-
clearMobileFlowState();
|
|
4858
|
-
setError(null);
|
|
4859
|
-
setDeeplinkUri(null);
|
|
4860
|
-
setMobileFlow(false);
|
|
4861
|
-
setStep("confirm-sign");
|
|
4862
|
-
}, [polling.stopPolling, reloadAccounts, resetDataLoadingState]);
|
|
4863
|
-
const handleRetryMobileStatus = useCallback(() => {
|
|
4864
|
-
setError(null);
|
|
4865
|
-
handlingMobileReturnRef.current = false;
|
|
4866
|
-
const currentTransfer = polling.transfer ?? transfer;
|
|
4867
|
-
if (currentTransfer?.status === "AUTHORIZED") {
|
|
4868
|
-
void handleAuthorizedMobileReturn(currentTransfer, mobileSetupFlowRef.current);
|
|
4869
|
-
return;
|
|
4870
|
-
}
|
|
4871
|
-
const transferIdToResume = pollingTransferIdRef.current ?? currentTransfer?.id;
|
|
4872
|
-
if (transferIdToResume) {
|
|
4873
|
-
polling.startPolling(transferIdToResume);
|
|
4874
|
-
}
|
|
4875
|
-
}, [handleAuthorizedMobileReturn, polling, transfer]);
|
|
4876
|
-
useEffect(() => {
|
|
4877
|
-
if (depositAmount != null) {
|
|
4878
|
-
setAmount(depositAmount.toString());
|
|
4879
5264
|
}
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
try {
|
|
4889
|
-
await initOAuth({ provider });
|
|
4890
|
-
} catch (err) {
|
|
4891
|
-
captureException(err);
|
|
4892
|
-
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
|
+
}
|
|
4893
5273
|
}
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
const
|
|
4897
|
-
|
|
4898
|
-
if (authenticated) return;
|
|
4899
|
-
if (activeOtpErrorMessage) setError(activeOtpErrorMessage);
|
|
4900
|
-
}, [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;
|
|
4901
5278
|
const handleSendLoginCode = useCallback(async () => {
|
|
4902
5279
|
const normalizedIdentifier = normalizeAuthIdentifier(authInput);
|
|
4903
5280
|
if (!normalizedIdentifier) {
|
|
4904
|
-
|
|
5281
|
+
dispatch({ type: "SET_ERROR", error: "Enter a valid email address or phone number." });
|
|
4905
5282
|
return;
|
|
4906
5283
|
}
|
|
4907
|
-
setError(null);
|
|
4908
5284
|
setOtpCode("");
|
|
4909
5285
|
try {
|
|
4910
5286
|
if (normalizedIdentifier.kind === "email") {
|
|
@@ -4912,468 +5288,214 @@ function SwypePaymentInner({
|
|
|
4912
5288
|
} else {
|
|
4913
5289
|
await sendSmsCode({ phoneNumber: normalizedIdentifier.value });
|
|
4914
5290
|
}
|
|
4915
|
-
|
|
4916
|
-
setStep("otp-verify");
|
|
5291
|
+
dispatch({ type: "CODE_SENT", target: normalizedIdentifier });
|
|
4917
5292
|
} catch (err) {
|
|
4918
5293
|
captureException(err);
|
|
4919
|
-
|
|
5294
|
+
dispatch({
|
|
5295
|
+
type: "SET_ERROR",
|
|
5296
|
+
error: err instanceof Error ? err.message : "Failed to send verification code"
|
|
5297
|
+
});
|
|
4920
5298
|
}
|
|
4921
5299
|
}, [authInput, sendEmailCode, sendSmsCode]);
|
|
4922
5300
|
const handleVerifyLoginCode = useCallback(async () => {
|
|
4923
|
-
if (!verificationTarget) return;
|
|
5301
|
+
if (!state.verificationTarget) return;
|
|
4924
5302
|
const trimmedCode = otpCode.trim();
|
|
4925
5303
|
if (!/^\d{6}$/.test(trimmedCode)) {
|
|
4926
|
-
|
|
5304
|
+
dispatch({ type: "SET_ERROR", error: "Enter the 6-digit verification code." });
|
|
4927
5305
|
return;
|
|
4928
5306
|
}
|
|
4929
|
-
|
|
5307
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
4930
5308
|
try {
|
|
4931
|
-
if (verificationTarget.kind === "email") {
|
|
5309
|
+
if (state.verificationTarget.kind === "email") {
|
|
4932
5310
|
await loginWithEmailCode({ code: trimmedCode });
|
|
4933
5311
|
} else {
|
|
4934
5312
|
await loginWithSmsCode({ code: trimmedCode });
|
|
4935
5313
|
}
|
|
4936
5314
|
} catch (err) {
|
|
4937
5315
|
captureException(err);
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
if (step === "otp-verify" && /^\d{6}$/.test(otpCode.trim()) && activeOtpStatus !== "submitting-code") {
|
|
4943
|
-
handleVerifyLoginCode();
|
|
5316
|
+
dispatch({
|
|
5317
|
+
type: "SET_ERROR",
|
|
5318
|
+
error: err instanceof Error ? err.message : "Failed to verify code"
|
|
5319
|
+
});
|
|
4944
5320
|
}
|
|
4945
|
-
}, [
|
|
5321
|
+
}, [state.verificationTarget, otpCode, loginWithEmailCode, loginWithSmsCode]);
|
|
4946
5322
|
const handleResendLoginCode = useCallback(async () => {
|
|
4947
|
-
if (!verificationTarget) return;
|
|
4948
|
-
|
|
5323
|
+
if (!state.verificationTarget) return;
|
|
5324
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
4949
5325
|
try {
|
|
4950
|
-
if (verificationTarget.kind === "email") {
|
|
4951
|
-
await sendEmailCode({ email: verificationTarget.value });
|
|
5326
|
+
if (state.verificationTarget.kind === "email") {
|
|
5327
|
+
await sendEmailCode({ email: state.verificationTarget.value });
|
|
4952
5328
|
} else {
|
|
4953
|
-
await sendSmsCode({ phoneNumber: verificationTarget.value });
|
|
5329
|
+
await sendSmsCode({ phoneNumber: state.verificationTarget.value });
|
|
4954
5330
|
}
|
|
4955
5331
|
} catch (err) {
|
|
4956
5332
|
captureException(err);
|
|
4957
|
-
|
|
5333
|
+
dispatch({
|
|
5334
|
+
type: "SET_ERROR",
|
|
5335
|
+
error: err instanceof Error ? err.message : "Failed to resend code"
|
|
5336
|
+
});
|
|
4958
5337
|
}
|
|
4959
|
-
}, [verificationTarget, sendEmailCode, sendSmsCode]);
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
if (
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
const
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
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
|
+
});
|
|
4973
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);
|
|
4974
5394
|
const resolved = resolvePostAuthStep({
|
|
4975
5395
|
hasPasskey: true,
|
|
4976
|
-
accounts:
|
|
4977
|
-
persistedMobileFlow:
|
|
4978
|
-
mobileSetupInProgress:
|
|
4979
|
-
connectingNewAccount:
|
|
5396
|
+
accounts: state.accounts,
|
|
5397
|
+
persistedMobileFlow: loadMobileFlowState(),
|
|
5398
|
+
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5399
|
+
connectingNewAccount: state.connectingNewAccount
|
|
4980
5400
|
});
|
|
4981
|
-
if (resolved.clearPersistedFlow)
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
await handleAuthorizedMobileReturn(existingTransfer, false);
|
|
5009
|
-
return;
|
|
5010
|
-
}
|
|
5011
|
-
if (mobileResolution.kind === "resume-success") {
|
|
5012
|
-
clearMobileFlowState();
|
|
5013
|
-
setMobileFlow(false);
|
|
5014
|
-
setDeeplinkUri(null);
|
|
5015
|
-
setTransfer(existingTransfer);
|
|
5016
|
-
setError(null);
|
|
5017
|
-
setStep("success");
|
|
5018
|
-
onComplete?.(existingTransfer);
|
|
5019
|
-
return;
|
|
5020
|
-
}
|
|
5021
|
-
if (mobileResolution.kind === "resume-failed") {
|
|
5022
|
-
clearMobileFlowState();
|
|
5023
|
-
setMobileFlow(false);
|
|
5024
|
-
setDeeplinkUri(null);
|
|
5025
|
-
setTransfer(existingTransfer);
|
|
5026
|
-
setError("Transfer failed.");
|
|
5027
|
-
setStep("success");
|
|
5028
|
-
return;
|
|
5029
|
-
}
|
|
5030
|
-
if (mobileResolution.kind === "resume-processing") {
|
|
5031
|
-
clearMobileFlowState();
|
|
5032
|
-
setMobileFlow(false);
|
|
5033
|
-
setDeeplinkUri(null);
|
|
5034
|
-
setTransfer(existingTransfer);
|
|
5035
|
-
setError(null);
|
|
5036
|
-
setStep("processing");
|
|
5037
|
-
polling.startPolling(existingTransfer.id);
|
|
5038
|
-
return;
|
|
5039
|
-
}
|
|
5040
|
-
if (mobileResolution.kind === "resume-stale-setup") {
|
|
5041
|
-
clearMobileFlowState();
|
|
5042
|
-
if (!cancelled) setStep("wallet-picker");
|
|
5043
|
-
return;
|
|
5044
|
-
}
|
|
5045
|
-
} catch (err) {
|
|
5046
|
-
if (cancelled) return;
|
|
5047
|
-
enterPersistedMobileFlow(
|
|
5048
|
-
persisted,
|
|
5049
|
-
err instanceof Error ? err.message : "Unable to refresh wallet authorization status."
|
|
5050
|
-
);
|
|
5051
|
-
return;
|
|
5052
|
-
}
|
|
5053
|
-
enterPersistedMobileFlow(persisted);
|
|
5054
|
-
return;
|
|
5055
|
-
}
|
|
5056
|
-
setStep(resolved.step);
|
|
5057
|
-
};
|
|
5058
|
-
const checkPasskey = async () => {
|
|
5059
|
-
try {
|
|
5060
|
-
const token = await getAccessToken();
|
|
5061
|
-
if (!token || cancelled) return;
|
|
5062
|
-
const { config } = await fetchUserConfig(apiBaseUrl, token);
|
|
5063
|
-
if (cancelled) return;
|
|
5064
|
-
if (config.defaultAllowance != null) {
|
|
5065
|
-
setOneTapLimit(config.defaultAllowance);
|
|
5066
|
-
}
|
|
5067
|
-
const allPasskeys = config.passkeys ?? (config.passkey ? [config.passkey] : []);
|
|
5068
|
-
setKnownCredentialIds(allPasskeys.map((p) => p.credentialId));
|
|
5069
|
-
if (allPasskeys.length === 0) {
|
|
5070
|
-
setStep("create-passkey");
|
|
5071
|
-
return;
|
|
5072
|
-
}
|
|
5073
|
-
if (activeCredentialId && allPasskeys.some((p) => p.credentialId === activeCredentialId)) {
|
|
5074
|
-
await restoreOrDeposit(activeCredentialId, token);
|
|
5075
|
-
return;
|
|
5076
|
-
}
|
|
5077
|
-
if (cancelled) return;
|
|
5078
|
-
const credentialIds = allPasskeys.map((p) => p.credentialId);
|
|
5079
|
-
if (isSafari() && isInCrossOriginIframe()) {
|
|
5080
|
-
setStep("verify-passkey");
|
|
5081
|
-
return;
|
|
5082
|
-
}
|
|
5083
|
-
const matched = await findDevicePasskey(credentialIds);
|
|
5084
|
-
if (cancelled) return;
|
|
5085
|
-
if (matched) {
|
|
5086
|
-
setActiveCredentialId(matched);
|
|
5087
|
-
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) {
|
|
5088
5428
|
reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
|
|
5089
5429
|
});
|
|
5090
|
-
await restoreOrDeposit(matched, token);
|
|
5091
|
-
return;
|
|
5092
5430
|
}
|
|
5093
|
-
|
|
5094
|
-
}
|
|
5095
|
-
|
|
5431
|
+
dispatch({ type: "NAVIGATE", step: "login" });
|
|
5432
|
+
} else {
|
|
5433
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5096
5434
|
}
|
|
5097
|
-
}
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
};
|
|
5102
|
-
}, [
|
|
5103
|
-
ready,
|
|
5104
|
-
authenticated,
|
|
5105
|
-
step,
|
|
5106
|
-
apiBaseUrl,
|
|
5107
|
-
getAccessToken,
|
|
5108
|
-
activeCredentialId,
|
|
5109
|
-
resetHeadlessLogin,
|
|
5110
|
-
enterPersistedMobileFlow,
|
|
5111
|
-
handleAuthorizedMobileReturn,
|
|
5112
|
-
onComplete
|
|
5113
|
-
]);
|
|
5114
|
-
useEffect(() => {
|
|
5115
|
-
const loadAction = resolveDataLoadAction({
|
|
5116
|
-
authenticated,
|
|
5117
|
-
step,
|
|
5118
|
-
accountsCount: accounts.length,
|
|
5119
|
-
hasActiveCredential: !!activeCredentialId,
|
|
5120
|
-
loading: loadingDataRef.current
|
|
5121
|
-
});
|
|
5122
|
-
if (loadAction === "reset") {
|
|
5123
|
-
resetDataLoadingState();
|
|
5124
|
-
return;
|
|
5125
|
-
}
|
|
5126
|
-
if (loadAction === "wait") {
|
|
5127
|
-
return;
|
|
5128
|
-
}
|
|
5129
|
-
const credentialId = activeCredentialId;
|
|
5130
|
-
if (!credentialId) {
|
|
5131
|
-
resetDataLoadingState();
|
|
5132
|
-
return;
|
|
5435
|
+
} catch {
|
|
5436
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5437
|
+
} finally {
|
|
5438
|
+
dispatch({ type: "SET_VERIFYING_PASSKEY", value: false });
|
|
5133
5439
|
}
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
const
|
|
5137
|
-
|
|
5138
|
-
|
|
5440
|
+
}, [state.knownCredentialIds, getAccessToken, apiBaseUrl]);
|
|
5441
|
+
const reloadAccounts = useCallback(async () => {
|
|
5442
|
+
const token = await getAccessToken();
|
|
5443
|
+
if (!token || !state.activeCredentialId) return;
|
|
5444
|
+
const [accts, prov] = await Promise.all([
|
|
5445
|
+
fetchAccounts(apiBaseUrl, token, state.activeCredentialId),
|
|
5446
|
+
fetchProviders(apiBaseUrl, token)
|
|
5447
|
+
]);
|
|
5448
|
+
const parsedAmt = depositAmount != null ? depositAmount : 0;
|
|
5449
|
+
const defaults = computeSmartDefaults(accts, parsedAmt);
|
|
5450
|
+
dispatch({ type: "ACCOUNTS_RELOADED", accounts: accts, providers: prov, defaults });
|
|
5451
|
+
}, [getAccessToken, state.activeCredentialId, apiBaseUrl, depositAmount]);
|
|
5452
|
+
const handleAuthorizedMobileReturn = useCallback(async (authorizedTransfer, isSetup) => {
|
|
5453
|
+
if (handlingMobileReturnRef.current) return;
|
|
5454
|
+
handlingMobileReturnRef.current = true;
|
|
5455
|
+
polling.stopPolling();
|
|
5456
|
+
if (isSetup) {
|
|
5457
|
+
mobileSetupFlowRef.current = false;
|
|
5458
|
+
clearMobileFlowState();
|
|
5139
5459
|
try {
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
fetchProviders(apiBaseUrl, token),
|
|
5144
|
-
fetchAccounts(apiBaseUrl, token, credentialId),
|
|
5145
|
-
fetchChains(apiBaseUrl, token)
|
|
5146
|
-
]);
|
|
5147
|
-
if (cancelled) return;
|
|
5148
|
-
setProviders(prov);
|
|
5149
|
-
setAccounts(accts);
|
|
5150
|
-
setChains(chn);
|
|
5151
|
-
const parsedAmt = depositAmount != null ? depositAmount : 0;
|
|
5152
|
-
const defaults = computeSmartDefaults(accts, parsedAmt);
|
|
5153
|
-
if (defaults) {
|
|
5154
|
-
setSelectedAccountId(defaults.accountId);
|
|
5155
|
-
setSelectedWalletId(defaults.walletId);
|
|
5156
|
-
} else if (prov.length > 0 && !connectingNewAccount) {
|
|
5157
|
-
setSelectedProviderId(prov[0].id);
|
|
5158
|
-
}
|
|
5159
|
-
const persisted = loadMobileFlowState();
|
|
5160
|
-
const resolved = resolvePostAuthStep({
|
|
5161
|
-
hasPasskey: !!activeCredentialId,
|
|
5162
|
-
accounts: accts,
|
|
5163
|
-
persistedMobileFlow: persisted,
|
|
5164
|
-
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5165
|
-
connectingNewAccount
|
|
5166
|
-
});
|
|
5167
|
-
if (resolved.clearPersistedFlow) {
|
|
5168
|
-
clearMobileFlowState();
|
|
5169
|
-
setMobileFlow(false);
|
|
5170
|
-
setDeeplinkUri(null);
|
|
5171
|
-
}
|
|
5172
|
-
const correctableSteps = ["deposit", "wallet-picker", "open-wallet"];
|
|
5173
|
-
if (correctableSteps.includes(step)) {
|
|
5174
|
-
setStep(resolved.step);
|
|
5175
|
-
}
|
|
5460
|
+
await reloadAccounts();
|
|
5461
|
+
loadingDataRef.current = false;
|
|
5462
|
+
dispatch({ type: "MOBILE_SETUP_COMPLETE", transfer: authorizedTransfer });
|
|
5176
5463
|
} catch (err) {
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
resetDataLoadingState();
|
|
5184
|
-
}
|
|
5185
|
-
}
|
|
5186
|
-
};
|
|
5187
|
-
load();
|
|
5188
|
-
return () => {
|
|
5189
|
-
cancelled = true;
|
|
5190
|
-
loadingDataRef.current = false;
|
|
5191
|
-
};
|
|
5192
|
-
}, [authenticated, step, accounts.length, apiBaseUrl, getAccessToken, activeCredentialId, depositAmount, connectingNewAccount, resetDataLoadingState]);
|
|
5193
|
-
useEffect(() => {
|
|
5194
|
-
if (!polling.transfer) return;
|
|
5195
|
-
if (polling.transfer.status === "COMPLETED") {
|
|
5196
|
-
clearMobileFlowState();
|
|
5197
|
-
setStep("success");
|
|
5198
|
-
setTransfer(polling.transfer);
|
|
5199
|
-
onComplete?.(polling.transfer);
|
|
5200
|
-
} else if (polling.transfer.status === "FAILED") {
|
|
5201
|
-
clearMobileFlowState();
|
|
5202
|
-
setStep("success");
|
|
5203
|
-
setTransfer(polling.transfer);
|
|
5204
|
-
setError("Transfer failed.");
|
|
5205
|
-
}
|
|
5206
|
-
}, [polling.transfer, onComplete]);
|
|
5207
|
-
useEffect(() => {
|
|
5208
|
-
if (step !== "processing") {
|
|
5209
|
-
processingStartedAtRef.current = null;
|
|
5210
|
-
return;
|
|
5211
|
-
}
|
|
5212
|
-
if (!processingStartedAtRef.current) {
|
|
5213
|
-
processingStartedAtRef.current = Date.now();
|
|
5214
|
-
}
|
|
5215
|
-
const elapsedMs = Date.now() - processingStartedAtRef.current;
|
|
5216
|
-
const remainingMs = PROCESSING_TIMEOUT_MS - elapsedMs;
|
|
5217
|
-
const handleTimeout = () => {
|
|
5218
|
-
if (!hasProcessingTimedOut(processingStartedAtRef.current, Date.now())) return;
|
|
5219
|
-
const status = getTransferStatus(polling.transfer, transfer);
|
|
5220
|
-
const msg = buildProcessingTimeoutMessage(status);
|
|
5221
|
-
captureException(new Error(msg));
|
|
5222
|
-
polling.stopPolling();
|
|
5223
|
-
setStep("deposit");
|
|
5224
|
-
setError(msg);
|
|
5225
|
-
onError?.(msg);
|
|
5226
|
-
};
|
|
5227
|
-
if (remainingMs <= 0) {
|
|
5228
|
-
handleTimeout();
|
|
5229
|
-
return;
|
|
5230
|
-
}
|
|
5231
|
-
const timeoutId = window.setTimeout(handleTimeout, remainingMs);
|
|
5232
|
-
return () => window.clearTimeout(timeoutId);
|
|
5233
|
-
}, [step, polling.transfer, transfer, polling.stopPolling, onError]);
|
|
5234
|
-
useEffect(() => {
|
|
5235
|
-
if (!mobileFlow) {
|
|
5236
|
-
handlingMobileReturnRef.current = false;
|
|
5237
|
-
return;
|
|
5238
|
-
}
|
|
5239
|
-
if (handlingMobileReturnRef.current) return;
|
|
5240
|
-
const polledTransfer = polling.transfer;
|
|
5241
|
-
if (!polledTransfer || polledTransfer.status !== "AUTHORIZED") return;
|
|
5242
|
-
void handleAuthorizedMobileReturn(polledTransfer, mobileSetupFlowRef.current);
|
|
5243
|
-
}, [mobileFlow, polling.transfer, handleAuthorizedMobileReturn]);
|
|
5244
|
-
useEffect(() => {
|
|
5245
|
-
if (!mobileFlow) return;
|
|
5246
|
-
if (handlingMobileReturnRef.current) return;
|
|
5247
|
-
const transferIdToResume = pollingTransferIdRef.current ?? transfer?.id;
|
|
5248
|
-
if (!transferIdToResume) return;
|
|
5249
|
-
if (!polling.isPolling) polling.startPolling(transferIdToResume);
|
|
5250
|
-
const handleVisibility = () => {
|
|
5251
|
-
if (document.visibilityState === "visible" && !handlingMobileReturnRef.current) {
|
|
5252
|
-
polling.startPolling(transferIdToResume);
|
|
5464
|
+
handlingMobileReturnRef.current = false;
|
|
5465
|
+
dispatch({
|
|
5466
|
+
type: "SET_ERROR",
|
|
5467
|
+
error: err instanceof Error ? err.message : "Wallet authorized, but we could not refresh your account yet."
|
|
5468
|
+
});
|
|
5469
|
+
dispatch({ type: "NAVIGATE", step: "open-wallet" });
|
|
5253
5470
|
}
|
|
5254
|
-
};
|
|
5255
|
-
document.addEventListener("visibilitychange", handleVisibility);
|
|
5256
|
-
return () => document.removeEventListener("visibilitychange", handleVisibility);
|
|
5257
|
-
}, [mobileFlow, transfer?.id, polling.isPolling, polling.startPolling]);
|
|
5258
|
-
const pendingSelectSourceAction = authExecutor.pendingSelectSource;
|
|
5259
|
-
const selectSourceChoices = useMemo(() => {
|
|
5260
|
-
if (!pendingSelectSourceAction) return [];
|
|
5261
|
-
const options = pendingSelectSourceAction.metadata?.options ?? [];
|
|
5262
|
-
return buildSelectSourceChoices(options);
|
|
5263
|
-
}, [pendingSelectSourceAction]);
|
|
5264
|
-
const selectSourceRecommended = useMemo(() => {
|
|
5265
|
-
if (!pendingSelectSourceAction) return null;
|
|
5266
|
-
return pendingSelectSourceAction.metadata?.recommended ?? null;
|
|
5267
|
-
}, [pendingSelectSourceAction]);
|
|
5268
|
-
useEffect(() => {
|
|
5269
|
-
if (!pendingSelectSourceAction) {
|
|
5270
|
-
initializedSelectSourceActionRef.current = null;
|
|
5271
|
-
setSelectSourceChainName("");
|
|
5272
|
-
setSelectSourceTokenSymbol("");
|
|
5273
5471
|
return;
|
|
5274
5472
|
}
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
} else if (selectSourceChoices.length > 0 && selectSourceChoices[0].tokens.length > 0) {
|
|
5283
|
-
setSelectSourceChainName(selectSourceChoices[0].chainName);
|
|
5284
|
-
setSelectSourceTokenSymbol(selectSourceChoices[0].tokens[0].tokenSymbol);
|
|
5285
|
-
} else {
|
|
5286
|
-
setSelectSourceChainName("Base");
|
|
5287
|
-
setSelectSourceTokenSymbol("USDC");
|
|
5288
|
-
}
|
|
5289
|
-
initializedSelectSourceActionRef.current = pendingSelectSourceAction.id;
|
|
5290
|
-
}, [pendingSelectSourceAction, selectSourceChoices, selectSourceRecommended]);
|
|
5291
|
-
useEffect(() => {
|
|
5292
|
-
if (pendingSelectSourceAction && step === "processing") {
|
|
5293
|
-
preSelectSourceStepRef.current = step;
|
|
5294
|
-
setStep("select-source");
|
|
5295
|
-
} else if (!pendingSelectSourceAction && step === "select-source") {
|
|
5296
|
-
setStep(preSelectSourceStepRef.current ?? "processing");
|
|
5297
|
-
preSelectSourceStepRef.current = null;
|
|
5298
|
-
}
|
|
5299
|
-
}, [pendingSelectSourceAction, step]);
|
|
5300
|
-
const handleSelectSourceChainChange = useCallback(
|
|
5301
|
-
(chainName) => {
|
|
5302
|
-
setSelectSourceChainName(chainName);
|
|
5303
|
-
const chain = selectSourceChoices.find((c) => c.chainName === chainName);
|
|
5304
|
-
if (!chain || chain.tokens.length === 0) return;
|
|
5305
|
-
const recommendedToken = selectSourceRecommended?.chainName === chainName ? selectSourceRecommended.tokenSymbol : null;
|
|
5306
|
-
const hasRecommended = !!recommendedToken && chain.tokens.some((t) => t.tokenSymbol === recommendedToken);
|
|
5307
|
-
setSelectSourceTokenSymbol(
|
|
5308
|
-
hasRecommended ? recommendedToken : chain.tokens[0].tokenSymbol
|
|
5309
|
-
);
|
|
5310
|
-
},
|
|
5311
|
-
[selectSourceChoices, selectSourceRecommended]
|
|
5312
|
-
);
|
|
5313
|
-
const pendingConnections = useMemo(
|
|
5314
|
-
() => accounts.filter(
|
|
5315
|
-
(a) => a.wallets.length > 0 && !a.wallets.some((w) => w.status === "ACTIVE")
|
|
5316
|
-
),
|
|
5317
|
-
[accounts]
|
|
5318
|
-
);
|
|
5319
|
-
const selectedAccount = accounts.find((a) => a.id === selectedAccountId);
|
|
5320
|
-
const selectedWallet = selectedAccount?.wallets.find((w) => w.id === selectedWalletId);
|
|
5321
|
-
const sourceName = selectedAccount?.name ?? selectedWallet?.chain.name ?? "Wallet";
|
|
5322
|
-
const sourceAddress = selectedWallet ? `${selectedWallet.name.slice(0, 6)}...${selectedWallet.name.slice(-4)}` : void 0;
|
|
5323
|
-
const sourceVerified = selectedWallet?.status === "ACTIVE";
|
|
5324
|
-
const maxSourceBalance = useMemo(() => {
|
|
5325
|
-
let max = 0;
|
|
5326
|
-
for (const acct of accounts) {
|
|
5327
|
-
for (const wallet of acct.wallets) {
|
|
5328
|
-
for (const source of wallet.sources) {
|
|
5329
|
-
if (source.balance.available.amount > max) {
|
|
5330
|
-
max = source.balance.available.amount;
|
|
5331
|
-
}
|
|
5332
|
-
}
|
|
5333
|
-
}
|
|
5334
|
-
}
|
|
5335
|
-
return max;
|
|
5336
|
-
}, [accounts]);
|
|
5337
|
-
const tokenCount = useMemo(() => {
|
|
5338
|
-
let count = 0;
|
|
5339
|
-
for (const acct of accounts) {
|
|
5340
|
-
for (const wallet of acct.wallets) {
|
|
5341
|
-
count += wallet.sources.length;
|
|
5342
|
-
}
|
|
5343
|
-
}
|
|
5344
|
-
return count;
|
|
5345
|
-
}, [accounts]);
|
|
5346
|
-
const handlePay = useCallback(async (depositAmount2, sourceOverrides) => {
|
|
5347
|
-
const parsedAmount = depositAmount2;
|
|
5348
|
-
if (isNaN(parsedAmount) || parsedAmount < MIN_SEND_AMOUNT_USD) {
|
|
5349
|
-
setError(`Minimum amount is $${MIN_SEND_AMOUNT_USD.toFixed(2)}.`);
|
|
5473
|
+
mobileSetupFlowRef.current = false;
|
|
5474
|
+
clearMobileFlowState();
|
|
5475
|
+
dispatch({ type: "MOBILE_SIGN_READY", transfer: authorizedTransfer });
|
|
5476
|
+
}, [polling.stopPolling, reloadAccounts]);
|
|
5477
|
+
const handlePay = useCallback(async (payAmount, sourceOverrides) => {
|
|
5478
|
+
if (isNaN(payAmount) || payAmount < MIN_SEND_AMOUNT_USD) {
|
|
5479
|
+
dispatch({ type: "SET_ERROR", error: `Minimum amount is $${MIN_SEND_AMOUNT_USD.toFixed(2)}.` });
|
|
5350
5480
|
return;
|
|
5351
5481
|
}
|
|
5352
5482
|
if (!sourceOverrides?.sourceId && !sourceId) {
|
|
5353
|
-
|
|
5483
|
+
dispatch({ type: "SET_ERROR", error: "No account or provider selected." });
|
|
5354
5484
|
return;
|
|
5355
5485
|
}
|
|
5356
|
-
if (!activeCredentialId) {
|
|
5357
|
-
|
|
5358
|
-
|
|
5486
|
+
if (!state.activeCredentialId) {
|
|
5487
|
+
dispatch({ type: "SET_ERROR", error: "Create a passkey on this device before continuing." });
|
|
5488
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5359
5489
|
return;
|
|
5360
5490
|
}
|
|
5361
5491
|
const isSetupRedirect = mobileSetupFlowRef.current;
|
|
5362
|
-
|
|
5363
|
-
setStep("open-wallet");
|
|
5364
|
-
} else {
|
|
5365
|
-
setStep("processing");
|
|
5366
|
-
}
|
|
5492
|
+
dispatch({ type: "PAY_STARTED", isSetupRedirect });
|
|
5367
5493
|
processingStartedAtRef.current = Date.now();
|
|
5368
|
-
setError(null);
|
|
5369
|
-
setCreatingTransfer(true);
|
|
5370
|
-
setDeeplinkUri(null);
|
|
5371
|
-
setMobileFlow(false);
|
|
5372
5494
|
try {
|
|
5373
|
-
if (transfer?.status === "AUTHORIZED") {
|
|
5374
|
-
const signedTransfer2 = await transferSigning.signTransfer(transfer.id);
|
|
5375
|
-
|
|
5376
|
-
polling.startPolling(transfer.id);
|
|
5495
|
+
if (state.transfer?.status === "AUTHORIZED") {
|
|
5496
|
+
const signedTransfer2 = await transferSigning.signTransfer(state.transfer.id);
|
|
5497
|
+
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer2 });
|
|
5498
|
+
polling.startPolling(state.transfer.id);
|
|
5377
5499
|
return;
|
|
5378
5500
|
}
|
|
5379
5501
|
const token = await getAccessToken();
|
|
@@ -5381,21 +5503,21 @@ function SwypePaymentInner({
|
|
|
5381
5503
|
let effectiveSourceType = sourceOverrides?.sourceType ?? sourceType;
|
|
5382
5504
|
let effectiveSourceId = sourceOverrides?.sourceId ?? sourceId;
|
|
5383
5505
|
if (effectiveSourceType === "accountId") {
|
|
5384
|
-
const acct = accounts.find((a) => a.id === effectiveSourceId);
|
|
5506
|
+
const acct = state.accounts.find((a) => a.id === effectiveSourceId);
|
|
5385
5507
|
const activeWallet = acct?.wallets.find((w) => w.status === "ACTIVE");
|
|
5386
5508
|
if (activeWallet) {
|
|
5387
5509
|
effectiveSourceType = "walletId";
|
|
5388
5510
|
effectiveSourceId = activeWallet.id;
|
|
5389
5511
|
}
|
|
5390
5512
|
}
|
|
5391
|
-
const isActiveWallet = effectiveSourceType === "walletId" && accounts.some(
|
|
5513
|
+
const isActiveWallet = effectiveSourceType === "walletId" && state.accounts.some(
|
|
5392
5514
|
(a) => a.wallets.some((w) => w.id === effectiveSourceId && w.status === "ACTIVE")
|
|
5393
5515
|
);
|
|
5394
5516
|
if (!isActiveWallet && !isSetupRedirect) {
|
|
5395
5517
|
let found = false;
|
|
5396
|
-
for (const acct of accounts) {
|
|
5518
|
+
for (const acct of state.accounts) {
|
|
5397
5519
|
for (const wallet of acct.wallets) {
|
|
5398
|
-
if (wallet.status === "ACTIVE" && wallet.sources.some((s) => s.balance.available.amount >=
|
|
5520
|
+
if (wallet.status === "ACTIVE" && wallet.sources.some((s) => s.balance.available.amount >= payAmount)) {
|
|
5399
5521
|
effectiveSourceType = "walletId";
|
|
5400
5522
|
effectiveSourceId = wallet.id;
|
|
5401
5523
|
found = true;
|
|
@@ -5407,30 +5529,28 @@ function SwypePaymentInner({
|
|
|
5407
5529
|
}
|
|
5408
5530
|
const t = await createTransfer(apiBaseUrl, token, {
|
|
5409
5531
|
id: idempotencyKey,
|
|
5410
|
-
credentialId: activeCredentialId,
|
|
5532
|
+
credentialId: state.activeCredentialId,
|
|
5411
5533
|
merchantAuthorization,
|
|
5412
5534
|
sourceType: effectiveSourceType,
|
|
5413
5535
|
sourceId: effectiveSourceId,
|
|
5414
5536
|
destination,
|
|
5415
|
-
amount:
|
|
5537
|
+
amount: payAmount
|
|
5416
5538
|
});
|
|
5417
|
-
|
|
5539
|
+
dispatch({ type: "TRANSFER_CREATED", transfer: t });
|
|
5418
5540
|
if (t.authorizationSessions && t.authorizationSessions.length > 0) {
|
|
5419
|
-
const
|
|
5420
|
-
useWalletConnector,
|
|
5541
|
+
const useConnector = shouldUseWalletConnector({
|
|
5542
|
+
useWalletConnector: useWalletConnectorProp,
|
|
5421
5543
|
userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent
|
|
5422
5544
|
});
|
|
5423
|
-
if (!
|
|
5545
|
+
if (!useConnector) {
|
|
5424
5546
|
const uri = t.authorizationSessions[0].uri;
|
|
5425
|
-
setMobileFlow(true);
|
|
5426
5547
|
pollingTransferIdRef.current = t.id;
|
|
5427
5548
|
polling.startPolling(t.id);
|
|
5428
|
-
|
|
5429
|
-
setStep("open-wallet");
|
|
5549
|
+
dispatch({ type: "MOBILE_DEEPLINK_READY", deeplinkUri: uri });
|
|
5430
5550
|
persistMobileFlowState({
|
|
5431
5551
|
transferId: t.id,
|
|
5432
5552
|
deeplinkUri: uri,
|
|
5433
|
-
providerId: sourceOverrides?.sourceType === "providerId" ? sourceOverrides.sourceId : selectedProviderId,
|
|
5553
|
+
providerId: sourceOverrides?.sourceType === "providerId" ? sourceOverrides.sourceId : state.selectedProviderId,
|
|
5434
5554
|
isSetup: mobileSetupFlowRef.current
|
|
5435
5555
|
});
|
|
5436
5556
|
triggerDeeplink(uri);
|
|
@@ -5440,55 +5560,58 @@ function SwypePaymentInner({
|
|
|
5440
5560
|
}
|
|
5441
5561
|
}
|
|
5442
5562
|
const signedTransfer = await transferSigning.signTransfer(t.id);
|
|
5443
|
-
|
|
5563
|
+
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer });
|
|
5444
5564
|
polling.startPolling(t.id);
|
|
5445
5565
|
} catch (err) {
|
|
5446
5566
|
captureException(err);
|
|
5447
5567
|
const msg = err instanceof Error ? err.message : "Transfer failed";
|
|
5448
|
-
|
|
5568
|
+
dispatch({
|
|
5569
|
+
type: "PAY_ERROR",
|
|
5570
|
+
error: msg,
|
|
5571
|
+
fallbackStep: isSetupRedirect ? "wallet-picker" : "deposit"
|
|
5572
|
+
});
|
|
5449
5573
|
onError?.(msg);
|
|
5450
|
-
setStep(isSetupRedirect ? "wallet-picker" : "deposit");
|
|
5451
5574
|
} finally {
|
|
5452
|
-
|
|
5575
|
+
dispatch({ type: "PAY_ENDED" });
|
|
5453
5576
|
}
|
|
5454
5577
|
}, [
|
|
5455
5578
|
sourceId,
|
|
5456
5579
|
sourceType,
|
|
5457
|
-
activeCredentialId,
|
|
5580
|
+
state.activeCredentialId,
|
|
5581
|
+
state.transfer,
|
|
5582
|
+
state.accounts,
|
|
5583
|
+
state.selectedProviderId,
|
|
5458
5584
|
destination,
|
|
5459
5585
|
apiBaseUrl,
|
|
5460
5586
|
getAccessToken,
|
|
5461
|
-
accounts,
|
|
5462
5587
|
authExecutor,
|
|
5463
5588
|
transferSigning,
|
|
5464
5589
|
polling,
|
|
5465
5590
|
onError,
|
|
5466
|
-
|
|
5591
|
+
useWalletConnectorProp,
|
|
5467
5592
|
idempotencyKey,
|
|
5468
|
-
merchantAuthorization
|
|
5469
|
-
transfer
|
|
5593
|
+
merchantAuthorization
|
|
5470
5594
|
]);
|
|
5471
|
-
const [increasingLimit, setIncreasingLimit] = useState(false);
|
|
5472
5595
|
const handleIncreaseLimit = useCallback(async () => {
|
|
5473
5596
|
const parsedAmount = depositAmount ?? 5;
|
|
5474
5597
|
if (!sourceId) {
|
|
5475
|
-
|
|
5598
|
+
dispatch({ type: "SET_ERROR", error: "No account or provider selected." });
|
|
5476
5599
|
return;
|
|
5477
5600
|
}
|
|
5478
|
-
if (!activeCredentialId) {
|
|
5479
|
-
|
|
5480
|
-
|
|
5601
|
+
if (!state.activeCredentialId) {
|
|
5602
|
+
dispatch({ type: "SET_ERROR", error: "Create a passkey on this device before continuing." });
|
|
5603
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5481
5604
|
return;
|
|
5482
5605
|
}
|
|
5483
|
-
|
|
5484
|
-
|
|
5606
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5607
|
+
dispatch({ type: "SET_INCREASING_LIMIT", value: true });
|
|
5485
5608
|
try {
|
|
5486
5609
|
const token = await getAccessToken();
|
|
5487
5610
|
if (!token) throw new Error("Not authenticated");
|
|
5488
5611
|
let effectiveSourceType = sourceType;
|
|
5489
5612
|
let effectiveSourceId = sourceId;
|
|
5490
5613
|
if (effectiveSourceType === "accountId") {
|
|
5491
|
-
const acct = accounts.find((a) => a.id === effectiveSourceId);
|
|
5614
|
+
const acct = state.accounts.find((a) => a.id === effectiveSourceId);
|
|
5492
5615
|
const activeWallet = acct?.wallets.find((w) => w.status === "ACTIVE");
|
|
5493
5616
|
if (activeWallet) {
|
|
5494
5617
|
effectiveSourceType = "walletId";
|
|
@@ -5497,188 +5620,118 @@ function SwypePaymentInner({
|
|
|
5497
5620
|
}
|
|
5498
5621
|
const t = await createTransfer(apiBaseUrl, token, {
|
|
5499
5622
|
id: idempotencyKey,
|
|
5500
|
-
credentialId: activeCredentialId,
|
|
5623
|
+
credentialId: state.activeCredentialId,
|
|
5501
5624
|
merchantAuthorization,
|
|
5502
5625
|
sourceType: effectiveSourceType,
|
|
5503
5626
|
sourceId: effectiveSourceId,
|
|
5504
5627
|
destination,
|
|
5505
5628
|
amount: parsedAmount
|
|
5506
5629
|
});
|
|
5507
|
-
setTransfer(t);
|
|
5508
5630
|
if (t.authorizationSessions && t.authorizationSessions.length > 0) {
|
|
5509
5631
|
const uri = t.authorizationSessions[0].uri;
|
|
5510
|
-
setMobileFlow(true);
|
|
5511
5632
|
pollingTransferIdRef.current = t.id;
|
|
5512
5633
|
mobileSetupFlowRef.current = true;
|
|
5513
5634
|
handlingMobileReturnRef.current = false;
|
|
5514
5635
|
polling.startPolling(t.id);
|
|
5515
|
-
|
|
5636
|
+
dispatch({ type: "INCREASE_LIMIT_DEEPLINK", transfer: t, deeplinkUri: uri });
|
|
5516
5637
|
persistMobileFlowState({
|
|
5517
5638
|
transferId: t.id,
|
|
5518
5639
|
deeplinkUri: uri,
|
|
5519
|
-
providerId: selectedProviderId,
|
|
5640
|
+
providerId: state.selectedProviderId,
|
|
5520
5641
|
isSetup: true
|
|
5521
5642
|
});
|
|
5522
5643
|
triggerDeeplink(uri);
|
|
5644
|
+
} else {
|
|
5645
|
+
dispatch({ type: "TRANSFER_CREATED", transfer: t });
|
|
5523
5646
|
}
|
|
5524
5647
|
} catch (err) {
|
|
5525
5648
|
captureException(err);
|
|
5526
5649
|
const msg = err instanceof Error ? err.message : "Failed to increase limit";
|
|
5527
|
-
|
|
5650
|
+
dispatch({ type: "SET_ERROR", error: msg });
|
|
5528
5651
|
onError?.(msg);
|
|
5529
5652
|
} finally {
|
|
5530
|
-
|
|
5653
|
+
dispatch({ type: "SET_INCREASING_LIMIT", value: false });
|
|
5531
5654
|
}
|
|
5532
5655
|
}, [
|
|
5533
5656
|
depositAmount,
|
|
5534
5657
|
sourceId,
|
|
5535
5658
|
sourceType,
|
|
5536
|
-
activeCredentialId,
|
|
5659
|
+
state.activeCredentialId,
|
|
5660
|
+
state.accounts,
|
|
5661
|
+
state.selectedProviderId,
|
|
5537
5662
|
apiBaseUrl,
|
|
5538
5663
|
getAccessToken,
|
|
5539
|
-
accounts,
|
|
5540
5664
|
polling,
|
|
5541
5665
|
onError,
|
|
5542
5666
|
idempotencyKey,
|
|
5543
5667
|
merchantAuthorization,
|
|
5544
|
-
destination
|
|
5545
|
-
selectedProviderId
|
|
5668
|
+
destination
|
|
5546
5669
|
]);
|
|
5547
|
-
const
|
|
5548
|
-
const
|
|
5549
|
-
if (!
|
|
5550
|
-
await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
|
|
5551
|
-
setActiveCredentialId(credentialId);
|
|
5552
|
-
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, credentialId);
|
|
5553
|
-
setPasskeyPopupNeeded(false);
|
|
5554
|
-
const resolved = resolvePostAuthStep({
|
|
5555
|
-
hasPasskey: true,
|
|
5556
|
-
accounts,
|
|
5557
|
-
persistedMobileFlow: loadMobileFlowState(),
|
|
5558
|
-
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5559
|
-
connectingNewAccount
|
|
5560
|
-
});
|
|
5561
|
-
if (resolved.clearPersistedFlow) {
|
|
5562
|
-
clearMobileFlowState();
|
|
5563
|
-
}
|
|
5564
|
-
setStep(resolved.step);
|
|
5565
|
-
}, [getAccessToken, apiBaseUrl, accounts, connectingNewAccount]);
|
|
5566
|
-
const handleRegisterPasskey = useCallback(async () => {
|
|
5567
|
-
setRegisteringPasskey(true);
|
|
5568
|
-
setError(null);
|
|
5569
|
-
try {
|
|
5570
|
-
const passkeyDisplayName = user?.email?.address ?? user?.google?.name ?? user?.id ?? "Swype User";
|
|
5571
|
-
const { credentialId, publicKey } = await createPasskeyCredential({
|
|
5572
|
-
userId: user?.id ?? "unknown",
|
|
5573
|
-
displayName: passkeyDisplayName
|
|
5574
|
-
});
|
|
5575
|
-
await completePasskeyRegistration(credentialId, publicKey);
|
|
5576
|
-
} catch (err) {
|
|
5577
|
-
if (err instanceof PasskeyIframeBlockedError) {
|
|
5578
|
-
setPasskeyPopupNeeded(true);
|
|
5579
|
-
} else {
|
|
5580
|
-
captureException(err);
|
|
5581
|
-
setError(err instanceof Error ? err.message : "Failed to register passkey");
|
|
5582
|
-
}
|
|
5583
|
-
} finally {
|
|
5584
|
-
setRegisteringPasskey(false);
|
|
5585
|
-
}
|
|
5586
|
-
}, [user, completePasskeyRegistration]);
|
|
5587
|
-
const handleCreatePasskeyViaPopup = useCallback(async () => {
|
|
5588
|
-
setRegisteringPasskey(true);
|
|
5589
|
-
setError(null);
|
|
5670
|
+
const handleConfirmSign = useCallback(async () => {
|
|
5671
|
+
const t = state.transfer ?? polling.transfer;
|
|
5672
|
+
if (!t) return;
|
|
5590
5673
|
try {
|
|
5591
|
-
const
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
displayName: passkeyDisplayName,
|
|
5596
|
-
authToken: token ?? void 0,
|
|
5597
|
-
apiBaseUrl
|
|
5598
|
-
});
|
|
5599
|
-
const { credentialId, publicKey } = await createPasskeyViaPopup(
|
|
5600
|
-
popupOptions,
|
|
5601
|
-
knownCredentialIds
|
|
5602
|
-
);
|
|
5603
|
-
await completePasskeyRegistration(credentialId, publicKey);
|
|
5674
|
+
const signedTransfer = await transferSigning.signTransfer(t.id);
|
|
5675
|
+
clearMobileFlowState();
|
|
5676
|
+
dispatch({ type: "CONFIRM_SIGN_SUCCESS", transfer: signedTransfer });
|
|
5677
|
+
polling.startPolling(t.id);
|
|
5604
5678
|
} catch (err) {
|
|
5605
5679
|
captureException(err);
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5680
|
+
const msg = err instanceof Error ? err.message : "Failed to sign transfer";
|
|
5681
|
+
dispatch({ type: "SET_ERROR", error: msg });
|
|
5682
|
+
onError?.(msg);
|
|
5609
5683
|
}
|
|
5610
|
-
}, [
|
|
5611
|
-
const
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
if (matched) {
|
|
5623
|
-
setActiveCredentialId(matched);
|
|
5624
|
-
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
|
|
5625
|
-
if (token) {
|
|
5626
|
-
reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
|
|
5627
|
-
});
|
|
5628
|
-
}
|
|
5629
|
-
setStep("login");
|
|
5630
|
-
} else {
|
|
5631
|
-
setStep("create-passkey");
|
|
5632
|
-
}
|
|
5633
|
-
} catch {
|
|
5634
|
-
setStep("create-passkey");
|
|
5635
|
-
} finally {
|
|
5636
|
-
setVerifyingPasskeyPopup(false);
|
|
5684
|
+
}, [state.transfer, polling.transfer, polling.startPolling, transferSigning, onError]);
|
|
5685
|
+
const handleRetryMobileStatus = useCallback(() => {
|
|
5686
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5687
|
+
handlingMobileReturnRef.current = false;
|
|
5688
|
+
const currentTransfer = polling.transfer ?? state.transfer;
|
|
5689
|
+
if (currentTransfer?.status === "AUTHORIZED") {
|
|
5690
|
+
void handleAuthorizedMobileReturn(currentTransfer, mobileSetupFlowRef.current);
|
|
5691
|
+
return;
|
|
5692
|
+
}
|
|
5693
|
+
const transferIdToResume = pollingTransferIdRef.current ?? currentTransfer?.id;
|
|
5694
|
+
if (transferIdToResume) {
|
|
5695
|
+
polling.startPolling(transferIdToResume);
|
|
5637
5696
|
}
|
|
5638
|
-
}, [
|
|
5697
|
+
}, [handleAuthorizedMobileReturn, polling, state.transfer]);
|
|
5639
5698
|
const handleSelectProvider = useCallback((providerId) => {
|
|
5640
|
-
|
|
5641
|
-
setSelectedAccountId(null);
|
|
5642
|
-
setConnectingNewAccount(true);
|
|
5699
|
+
dispatch({ type: "SELECT_PROVIDER", providerId });
|
|
5643
5700
|
const isMobile = !shouldUseWalletConnector({
|
|
5644
|
-
useWalletConnector,
|
|
5701
|
+
useWalletConnector: useWalletConnectorProp,
|
|
5645
5702
|
userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent
|
|
5646
5703
|
});
|
|
5647
5704
|
if (isMobile) {
|
|
5648
5705
|
handlingMobileReturnRef.current = false;
|
|
5649
5706
|
mobileSetupFlowRef.current = true;
|
|
5650
|
-
const
|
|
5651
|
-
handlePay(
|
|
5707
|
+
const amount = depositAmount ?? 5;
|
|
5708
|
+
handlePay(amount, { sourceType: "providerId", sourceId: providerId });
|
|
5652
5709
|
} else {
|
|
5653
|
-
|
|
5710
|
+
dispatch({ type: "NAVIGATE", step: "deposit" });
|
|
5654
5711
|
}
|
|
5655
|
-
}, [
|
|
5712
|
+
}, [useWalletConnectorProp, depositAmount, handlePay]);
|
|
5656
5713
|
const handleContinueConnection = useCallback(
|
|
5657
5714
|
(accountId) => {
|
|
5658
|
-
const acct = accounts.find((a) => a.id === accountId);
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5715
|
+
const acct = state.accounts.find((a) => a.id === accountId);
|
|
5716
|
+
dispatch({
|
|
5717
|
+
type: "SELECT_ACCOUNT",
|
|
5718
|
+
accountId,
|
|
5719
|
+
walletId: acct?.wallets[0]?.id ?? null
|
|
5720
|
+
});
|
|
5663
5721
|
},
|
|
5664
|
-
[accounts]
|
|
5722
|
+
[state.accounts]
|
|
5665
5723
|
);
|
|
5666
5724
|
const handleNewPayment = useCallback(() => {
|
|
5667
5725
|
clearMobileFlowState();
|
|
5668
|
-
setStep("deposit");
|
|
5669
|
-
setTransfer(null);
|
|
5670
|
-
setError(null);
|
|
5671
|
-
setAmount(depositAmount != null ? depositAmount.toString() : "");
|
|
5672
|
-
setMobileFlow(false);
|
|
5673
|
-
setDeeplinkUri(null);
|
|
5674
5726
|
processingStartedAtRef.current = null;
|
|
5675
5727
|
pollingTransferIdRef.current = null;
|
|
5676
|
-
mobileSigningTransferIdRef.current = null;
|
|
5677
5728
|
preSelectSourceStepRef.current = null;
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5729
|
+
dispatch({
|
|
5730
|
+
type: "NEW_PAYMENT",
|
|
5731
|
+
depositAmount,
|
|
5732
|
+
firstAccountId: state.accounts.length > 0 ? state.accounts[0].id : null
|
|
5733
|
+
});
|
|
5734
|
+
}, [depositAmount, state.accounts]);
|
|
5682
5735
|
const handleLogout = useCallback(async () => {
|
|
5683
5736
|
try {
|
|
5684
5737
|
await logout();
|
|
@@ -5689,254 +5742,464 @@ function SwypePaymentInner({
|
|
|
5689
5742
|
window.localStorage.removeItem(ACTIVE_CREDENTIAL_STORAGE_KEY);
|
|
5690
5743
|
}
|
|
5691
5744
|
polling.stopPolling();
|
|
5692
|
-
setActiveCredentialId(null);
|
|
5693
|
-
setStep("login");
|
|
5694
|
-
setError(null);
|
|
5695
|
-
setTransfer(null);
|
|
5696
|
-
setCreatingTransfer(false);
|
|
5697
|
-
setRegisteringPasskey(false);
|
|
5698
|
-
setProviders([]);
|
|
5699
|
-
setAccounts([]);
|
|
5700
|
-
setChains([]);
|
|
5701
|
-
setSelectedAccountId(null);
|
|
5702
|
-
setSelectedWalletId(null);
|
|
5703
|
-
setSelectedProviderId(null);
|
|
5704
|
-
setConnectingNewAccount(false);
|
|
5705
|
-
setAmount(depositAmount != null ? depositAmount.toString() : "");
|
|
5706
|
-
setMobileFlow(false);
|
|
5707
|
-
setDeeplinkUri(null);
|
|
5708
5745
|
preSelectSourceStepRef.current = null;
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5746
|
+
setAuthInput("");
|
|
5747
|
+
setOtpCode("");
|
|
5748
|
+
dispatch({ type: "LOGOUT", depositAmount });
|
|
5749
|
+
}, [logout, polling, depositAmount]);
|
|
5750
|
+
const pendingSelectSourceAction = authExecutor.pendingSelectSource;
|
|
5751
|
+
const selectSourceChoices = useMemo(() => {
|
|
5752
|
+
if (!pendingSelectSourceAction) return [];
|
|
5753
|
+
const options = pendingSelectSourceAction.metadata?.options ?? [];
|
|
5754
|
+
return buildSelectSourceChoices(options);
|
|
5755
|
+
}, [pendingSelectSourceAction]);
|
|
5756
|
+
const selectSourceRecommended = useMemo(() => {
|
|
5757
|
+
if (!pendingSelectSourceAction) return null;
|
|
5758
|
+
return pendingSelectSourceAction.metadata?.recommended ?? null;
|
|
5759
|
+
}, [pendingSelectSourceAction]);
|
|
5760
|
+
const handleSelectSourceChainChange = useCallback(
|
|
5761
|
+
(chainName) => {
|
|
5762
|
+
setSelectSourceChainName(chainName);
|
|
5763
|
+
const chain = selectSourceChoices.find((c) => c.chainName === chainName);
|
|
5764
|
+
if (!chain || chain.tokens.length === 0) return;
|
|
5765
|
+
const recommendedToken = selectSourceRecommended?.chainName === chainName ? selectSourceRecommended.tokenSymbol : null;
|
|
5766
|
+
const hasRecommended = !!recommendedToken && chain.tokens.some((t) => t.tokenSymbol === recommendedToken);
|
|
5767
|
+
setSelectSourceTokenSymbol(
|
|
5768
|
+
hasRecommended ? recommendedToken : chain.tokens[0].tokenSymbol
|
|
5769
|
+
);
|
|
5770
|
+
},
|
|
5771
|
+
[selectSourceChoices, selectSourceRecommended]
|
|
5772
|
+
);
|
|
5773
|
+
const handleConfirmSelectSource = useCallback(() => {
|
|
5774
|
+
authExecutor.resolveSelectSource({
|
|
5775
|
+
chainName: selectSourceChainName,
|
|
5776
|
+
tokenSymbol: selectSourceTokenSymbol
|
|
5777
|
+
});
|
|
5778
|
+
}, [authExecutor, selectSourceChainName, selectSourceTokenSymbol]);
|
|
5779
|
+
useEffect(() => {
|
|
5780
|
+
if (depositAmount != null) {
|
|
5781
|
+
dispatch({ type: "SYNC_AMOUNT", amount: depositAmount.toString() });
|
|
5725
5782
|
}
|
|
5726
|
-
}, [
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
);
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
onOtpChange: (code) => {
|
|
5751
|
-
setOtpCode(code);
|
|
5752
|
-
setError(null);
|
|
5753
|
-
},
|
|
5754
|
-
onVerify: handleVerifyLoginCode,
|
|
5755
|
-
onResend: handleResendLoginCode,
|
|
5756
|
-
onBack: () => {
|
|
5757
|
-
setVerificationTarget(null);
|
|
5758
|
-
setOtpCode("");
|
|
5759
|
-
setError(null);
|
|
5760
|
-
setStep("login");
|
|
5761
|
-
},
|
|
5762
|
-
verifying: activeOtpStatus === "submitting-code",
|
|
5763
|
-
error
|
|
5764
|
-
}
|
|
5765
|
-
);
|
|
5766
|
-
}
|
|
5767
|
-
if ((step === "login" || step === "otp-verify") && authenticated) {
|
|
5768
|
-
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..." }) }) });
|
|
5769
|
-
}
|
|
5770
|
-
if (step === "verify-passkey") {
|
|
5771
|
-
return /* @__PURE__ */ jsx(
|
|
5772
|
-
VerifyPasskeyScreen,
|
|
5773
|
-
{
|
|
5774
|
-
onVerify: handleVerifyPasskeyViaPopup,
|
|
5775
|
-
onBack: handleLogout,
|
|
5776
|
-
verifying: verifyingPasskeyPopup,
|
|
5777
|
-
error
|
|
5778
|
-
}
|
|
5779
|
-
);
|
|
5780
|
-
}
|
|
5781
|
-
if (step === "create-passkey") {
|
|
5782
|
-
return /* @__PURE__ */ jsx(
|
|
5783
|
-
CreatePasskeyScreen,
|
|
5784
|
-
{
|
|
5785
|
-
onCreatePasskey: handleRegisterPasskey,
|
|
5786
|
-
onBack: handleLogout,
|
|
5787
|
-
creating: registeringPasskey,
|
|
5788
|
-
error,
|
|
5789
|
-
popupFallback: passkeyPopupNeeded,
|
|
5790
|
-
onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup
|
|
5783
|
+
}, [depositAmount]);
|
|
5784
|
+
useEffect(() => {
|
|
5785
|
+
if (authenticated) return;
|
|
5786
|
+
if (activeOtpErrorMessage) dispatch({ type: "SET_ERROR", error: activeOtpErrorMessage });
|
|
5787
|
+
}, [activeOtpErrorMessage, authenticated]);
|
|
5788
|
+
useEffect(() => {
|
|
5789
|
+
if (state.step === "otp-verify" && /^\d{6}$/.test(otpCode.trim()) && activeOtpStatus !== "submitting-code") {
|
|
5790
|
+
handleVerifyLoginCode();
|
|
5791
|
+
}
|
|
5792
|
+
}, [otpCode, state.step, activeOtpStatus, handleVerifyLoginCode]);
|
|
5793
|
+
useEffect(() => {
|
|
5794
|
+
if (!ready || !authenticated) return;
|
|
5795
|
+
if (state.step !== "login" && state.step !== "otp-verify") return;
|
|
5796
|
+
let cancelled = false;
|
|
5797
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
5798
|
+
setAuthInput("");
|
|
5799
|
+
setOtpCode("");
|
|
5800
|
+
const restoreOrDeposit = async (credId, token) => {
|
|
5801
|
+
const persisted = loadMobileFlowState();
|
|
5802
|
+
let accts = [];
|
|
5803
|
+
try {
|
|
5804
|
+
accts = await fetchAccounts(apiBaseUrl, token, credId);
|
|
5805
|
+
if (cancelled) return;
|
|
5806
|
+
} catch {
|
|
5791
5807
|
}
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5808
|
+
const resolved = resolvePostAuthStep({
|
|
5809
|
+
hasPasskey: true,
|
|
5810
|
+
accounts: accts,
|
|
5811
|
+
persistedMobileFlow: persisted,
|
|
5812
|
+
mobileSetupInProgress: false,
|
|
5813
|
+
connectingNewAccount: false
|
|
5814
|
+
});
|
|
5815
|
+
if (resolved.clearPersistedFlow) clearMobileFlowState();
|
|
5816
|
+
if (resolved.step === "deposit" && persisted && persisted.isSetup) {
|
|
5817
|
+
try {
|
|
5818
|
+
const existingTransfer = await fetchTransfer(apiBaseUrl, token, persisted.transferId);
|
|
5819
|
+
if (cancelled) return;
|
|
5820
|
+
if (existingTransfer.status === "AUTHORIZED") {
|
|
5821
|
+
await handleAuthorizedMobileReturn(existingTransfer, true);
|
|
5822
|
+
return;
|
|
5823
|
+
}
|
|
5824
|
+
} catch {
|
|
5825
|
+
}
|
|
5804
5826
|
}
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5827
|
+
if (resolved.step === "open-wallet" && persisted) {
|
|
5828
|
+
try {
|
|
5829
|
+
const existingTransfer = await fetchTransfer(apiBaseUrl, token, persisted.transferId);
|
|
5830
|
+
if (cancelled) return;
|
|
5831
|
+
const mobileResolution = resolveRestoredMobileFlow(
|
|
5832
|
+
existingTransfer.status,
|
|
5833
|
+
persisted.isSetup
|
|
5834
|
+
);
|
|
5835
|
+
if (mobileResolution.kind === "resume-setup-deposit") {
|
|
5836
|
+
await handleAuthorizedMobileReturn(existingTransfer, true);
|
|
5837
|
+
return;
|
|
5838
|
+
}
|
|
5839
|
+
if (mobileResolution.kind === "resume-confirm-sign") {
|
|
5840
|
+
await handleAuthorizedMobileReturn(existingTransfer, false);
|
|
5841
|
+
return;
|
|
5842
|
+
}
|
|
5843
|
+
if (mobileResolution.kind === "resume-success") {
|
|
5844
|
+
clearMobileFlowState();
|
|
5845
|
+
dispatch({ type: "MOBILE_RESUME_SUCCESS", transfer: existingTransfer });
|
|
5846
|
+
onComplete?.(existingTransfer);
|
|
5847
|
+
return;
|
|
5848
|
+
}
|
|
5849
|
+
if (mobileResolution.kind === "resume-failed") {
|
|
5850
|
+
clearMobileFlowState();
|
|
5851
|
+
dispatch({ type: "MOBILE_RESUME_FAILED", transfer: existingTransfer });
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
if (mobileResolution.kind === "resume-processing") {
|
|
5855
|
+
clearMobileFlowState();
|
|
5856
|
+
dispatch({ type: "MOBILE_RESUME_PROCESSING", transfer: existingTransfer });
|
|
5857
|
+
polling.startPolling(existingTransfer.id);
|
|
5858
|
+
return;
|
|
5859
|
+
}
|
|
5860
|
+
if (mobileResolution.kind === "resume-stale-setup") {
|
|
5861
|
+
clearMobileFlowState();
|
|
5862
|
+
if (!cancelled) dispatch({ type: "NAVIGATE", step: "wallet-picker" });
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
} catch (err) {
|
|
5866
|
+
if (cancelled) return;
|
|
5867
|
+
dispatch({
|
|
5868
|
+
type: "ENTER_MOBILE_FLOW",
|
|
5869
|
+
deeplinkUri: persisted.deeplinkUri,
|
|
5870
|
+
providerId: persisted.providerId,
|
|
5871
|
+
error: err instanceof Error ? err.message : "Unable to refresh wallet authorization status."
|
|
5872
|
+
});
|
|
5873
|
+
pollingTransferIdRef.current = persisted.transferId;
|
|
5874
|
+
mobileSetupFlowRef.current = persisted.isSetup;
|
|
5875
|
+
polling.startPolling(persisted.transferId);
|
|
5876
|
+
return;
|
|
5877
|
+
}
|
|
5878
|
+
dispatch({
|
|
5879
|
+
type: "ENTER_MOBILE_FLOW",
|
|
5880
|
+
deeplinkUri: persisted.deeplinkUri,
|
|
5881
|
+
providerId: persisted.providerId
|
|
5882
|
+
});
|
|
5883
|
+
pollingTransferIdRef.current = persisted.transferId;
|
|
5884
|
+
mobileSetupFlowRef.current = persisted.isSetup;
|
|
5885
|
+
polling.startPolling(persisted.transferId);
|
|
5886
|
+
return;
|
|
5818
5887
|
}
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5888
|
+
dispatch({ type: "NAVIGATE", step: resolved.step });
|
|
5889
|
+
};
|
|
5890
|
+
const checkPasskey = async () => {
|
|
5891
|
+
try {
|
|
5892
|
+
const token = await getAccessToken();
|
|
5893
|
+
if (!token || cancelled) return;
|
|
5894
|
+
const { config } = await fetchUserConfig(apiBaseUrl, token);
|
|
5895
|
+
if (cancelled) return;
|
|
5896
|
+
const allPasskeys = config.passkeys ?? (config.passkey ? [config.passkey] : []);
|
|
5897
|
+
dispatch({
|
|
5898
|
+
type: "PASSKEY_CONFIG_LOADED",
|
|
5899
|
+
knownIds: allPasskeys.map((p) => p.credentialId),
|
|
5900
|
+
oneTapLimit: config.defaultAllowance ?? void 0
|
|
5901
|
+
});
|
|
5902
|
+
if (allPasskeys.length === 0) {
|
|
5903
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5904
|
+
return;
|
|
5905
|
+
}
|
|
5906
|
+
if (state.activeCredentialId && allPasskeys.some((p) => p.credentialId === state.activeCredentialId)) {
|
|
5907
|
+
await restoreOrDeposit(state.activeCredentialId, token);
|
|
5908
|
+
return;
|
|
5909
|
+
}
|
|
5910
|
+
if (cancelled) return;
|
|
5911
|
+
if (isSafari() && isInCrossOriginIframe()) {
|
|
5912
|
+
dispatch({ type: "NAVIGATE", step: "verify-passkey" });
|
|
5913
|
+
return;
|
|
5914
|
+
}
|
|
5915
|
+
const credentialIds = allPasskeys.map((p) => p.credentialId);
|
|
5916
|
+
const matched = await findDevicePasskey(credentialIds);
|
|
5917
|
+
if (cancelled) return;
|
|
5918
|
+
if (matched) {
|
|
5919
|
+
dispatch({ type: "PASSKEY_ACTIVATED", credentialId: matched });
|
|
5920
|
+
window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, matched);
|
|
5921
|
+
reportPasskeyActivity(apiBaseUrl, token, matched).catch(() => {
|
|
5922
|
+
});
|
|
5923
|
+
await restoreOrDeposit(matched, token);
|
|
5924
|
+
return;
|
|
5925
|
+
}
|
|
5926
|
+
dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5927
|
+
} catch {
|
|
5928
|
+
if (!cancelled) dispatch({ type: "NAVIGATE", step: "create-passkey" });
|
|
5831
5929
|
}
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5930
|
+
};
|
|
5931
|
+
checkPasskey();
|
|
5932
|
+
return () => {
|
|
5933
|
+
cancelled = true;
|
|
5934
|
+
};
|
|
5935
|
+
}, [
|
|
5936
|
+
ready,
|
|
5937
|
+
authenticated,
|
|
5938
|
+
state.step,
|
|
5939
|
+
apiBaseUrl,
|
|
5940
|
+
getAccessToken,
|
|
5941
|
+
state.activeCredentialId,
|
|
5942
|
+
handleAuthorizedMobileReturn,
|
|
5943
|
+
onComplete,
|
|
5944
|
+
polling
|
|
5945
|
+
]);
|
|
5946
|
+
useEffect(() => {
|
|
5947
|
+
const loadAction = resolveDataLoadAction({
|
|
5948
|
+
authenticated,
|
|
5949
|
+
step: state.step,
|
|
5950
|
+
accountsCount: state.accounts.length,
|
|
5951
|
+
hasActiveCredential: !!state.activeCredentialId,
|
|
5952
|
+
loading: loadingDataRef.current
|
|
5953
|
+
});
|
|
5954
|
+
if (loadAction === "reset") {
|
|
5955
|
+
loadingDataRef.current = false;
|
|
5956
|
+
dispatch({ type: "DATA_LOAD_END" });
|
|
5957
|
+
return;
|
|
5837
5958
|
}
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
chainName: selectSourceChainName,
|
|
5887
|
-
tokenSymbol: selectSourceTokenSymbol
|
|
5959
|
+
if (loadAction === "wait") return;
|
|
5960
|
+
const credentialId = state.activeCredentialId;
|
|
5961
|
+
if (!credentialId) {
|
|
5962
|
+
loadingDataRef.current = false;
|
|
5963
|
+
dispatch({ type: "DATA_LOAD_END" });
|
|
5964
|
+
return;
|
|
5965
|
+
}
|
|
5966
|
+
let cancelled = false;
|
|
5967
|
+
loadingDataRef.current = true;
|
|
5968
|
+
const load = async () => {
|
|
5969
|
+
dispatch({ type: "DATA_LOAD_START" });
|
|
5970
|
+
try {
|
|
5971
|
+
const token = await getAccessToken();
|
|
5972
|
+
if (!token) throw new Error("Not authenticated");
|
|
5973
|
+
const [prov, accts, chn] = await Promise.all([
|
|
5974
|
+
fetchProviders(apiBaseUrl, token),
|
|
5975
|
+
fetchAccounts(apiBaseUrl, token, credentialId),
|
|
5976
|
+
fetchChains(apiBaseUrl, token)
|
|
5977
|
+
]);
|
|
5978
|
+
if (cancelled) return;
|
|
5979
|
+
const parsedAmt = depositAmount != null ? depositAmount : 0;
|
|
5980
|
+
const defaults = computeSmartDefaults(accts, parsedAmt);
|
|
5981
|
+
const persisted = loadMobileFlowState();
|
|
5982
|
+
const resolved = resolvePostAuthStep({
|
|
5983
|
+
hasPasskey: !!state.activeCredentialId,
|
|
5984
|
+
accounts: accts,
|
|
5985
|
+
persistedMobileFlow: persisted,
|
|
5986
|
+
mobileSetupInProgress: mobileSetupFlowRef.current,
|
|
5987
|
+
connectingNewAccount: state.connectingNewAccount
|
|
5988
|
+
});
|
|
5989
|
+
const correctableSteps = ["deposit", "wallet-picker", "open-wallet"];
|
|
5990
|
+
dispatch({
|
|
5991
|
+
type: "DATA_LOADED",
|
|
5992
|
+
providers: prov,
|
|
5993
|
+
accounts: accts,
|
|
5994
|
+
chains: chn,
|
|
5995
|
+
defaults,
|
|
5996
|
+
fallbackProviderId: !defaults && prov.length > 0 ? prov[0].id : null,
|
|
5997
|
+
resolvedStep: correctableSteps.includes(state.step) ? resolved.step : void 0,
|
|
5998
|
+
clearMobileState: resolved.clearPersistedFlow
|
|
5999
|
+
});
|
|
6000
|
+
if (resolved.clearPersistedFlow) clearMobileFlowState();
|
|
6001
|
+
} catch (err) {
|
|
6002
|
+
if (!cancelled) {
|
|
6003
|
+
captureException(err);
|
|
6004
|
+
dispatch({
|
|
6005
|
+
type: "SET_ERROR",
|
|
6006
|
+
error: err instanceof Error ? err.message : "Failed to load data"
|
|
5888
6007
|
});
|
|
5889
|
-
}
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
const succeeded = transfer?.status === "COMPLETED";
|
|
5896
|
-
const displayAmount = transfer?.amount?.amount ?? 0;
|
|
5897
|
-
const displayCurrency = transfer?.amount?.currency ?? "USD";
|
|
5898
|
-
return /* @__PURE__ */ jsx(
|
|
5899
|
-
SuccessScreen,
|
|
5900
|
-
{
|
|
5901
|
-
amount: displayAmount,
|
|
5902
|
-
currency: displayCurrency,
|
|
5903
|
-
succeeded,
|
|
5904
|
-
error,
|
|
5905
|
-
merchantName,
|
|
5906
|
-
sourceName,
|
|
5907
|
-
remainingLimit: succeeded ? (() => {
|
|
5908
|
-
const limit = selectedAccount?.remainingAllowance ?? oneTapLimit;
|
|
5909
|
-
return limit > displayAmount ? limit - displayAmount : 0;
|
|
5910
|
-
})() : void 0,
|
|
5911
|
-
onDone: onDismiss ?? handleNewPayment,
|
|
5912
|
-
onLogout: handleLogout,
|
|
5913
|
-
autoCloseSeconds
|
|
6008
|
+
}
|
|
6009
|
+
} finally {
|
|
6010
|
+
if (!cancelled) {
|
|
6011
|
+
loadingDataRef.current = false;
|
|
6012
|
+
dispatch({ type: "DATA_LOAD_END" });
|
|
6013
|
+
}
|
|
5914
6014
|
}
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
6015
|
+
};
|
|
6016
|
+
load();
|
|
6017
|
+
return () => {
|
|
6018
|
+
cancelled = true;
|
|
6019
|
+
loadingDataRef.current = false;
|
|
6020
|
+
};
|
|
6021
|
+
}, [
|
|
6022
|
+
authenticated,
|
|
6023
|
+
state.step,
|
|
6024
|
+
state.accounts.length,
|
|
6025
|
+
apiBaseUrl,
|
|
6026
|
+
getAccessToken,
|
|
6027
|
+
state.activeCredentialId,
|
|
6028
|
+
depositAmount,
|
|
6029
|
+
state.connectingNewAccount
|
|
6030
|
+
]);
|
|
6031
|
+
useEffect(() => {
|
|
6032
|
+
if (!polling.transfer) return;
|
|
6033
|
+
if (polling.transfer.status === "COMPLETED") {
|
|
6034
|
+
clearMobileFlowState();
|
|
6035
|
+
dispatch({ type: "TRANSFER_COMPLETED", transfer: polling.transfer });
|
|
6036
|
+
onComplete?.(polling.transfer);
|
|
6037
|
+
} else if (polling.transfer.status === "FAILED") {
|
|
6038
|
+
clearMobileFlowState();
|
|
6039
|
+
dispatch({ type: "TRANSFER_FAILED", transfer: polling.transfer, error: "Transfer failed." });
|
|
6040
|
+
}
|
|
6041
|
+
}, [polling.transfer, onComplete]);
|
|
6042
|
+
useEffect(() => {
|
|
6043
|
+
if (state.step !== "processing") {
|
|
6044
|
+
processingStartedAtRef.current = null;
|
|
6045
|
+
return;
|
|
6046
|
+
}
|
|
6047
|
+
if (!processingStartedAtRef.current) {
|
|
6048
|
+
processingStartedAtRef.current = Date.now();
|
|
6049
|
+
}
|
|
6050
|
+
const elapsedMs = Date.now() - processingStartedAtRef.current;
|
|
6051
|
+
const remainingMs = PROCESSING_TIMEOUT_MS - elapsedMs;
|
|
6052
|
+
const handleTimeout = () => {
|
|
6053
|
+
if (!hasProcessingTimedOut(processingStartedAtRef.current, Date.now())) return;
|
|
6054
|
+
const status = getTransferStatus(polling.transfer, state.transfer);
|
|
6055
|
+
const msg = buildProcessingTimeoutMessage(status);
|
|
6056
|
+
captureException(new Error(msg));
|
|
6057
|
+
polling.stopPolling();
|
|
6058
|
+
dispatch({ type: "PROCESSING_TIMEOUT", error: msg });
|
|
6059
|
+
onError?.(msg);
|
|
6060
|
+
};
|
|
6061
|
+
if (remainingMs <= 0) {
|
|
6062
|
+
handleTimeout();
|
|
6063
|
+
return;
|
|
6064
|
+
}
|
|
6065
|
+
const timeoutId = window.setTimeout(handleTimeout, remainingMs);
|
|
6066
|
+
return () => window.clearTimeout(timeoutId);
|
|
6067
|
+
}, [state.step, polling.transfer, state.transfer, polling.stopPolling, onError]);
|
|
6068
|
+
useEffect(() => {
|
|
6069
|
+
if (!state.mobileFlow) {
|
|
6070
|
+
handlingMobileReturnRef.current = false;
|
|
6071
|
+
return;
|
|
6072
|
+
}
|
|
6073
|
+
if (handlingMobileReturnRef.current) return;
|
|
6074
|
+
const polledTransfer = polling.transfer;
|
|
6075
|
+
if (!polledTransfer || polledTransfer.status !== "AUTHORIZED") return;
|
|
6076
|
+
void handleAuthorizedMobileReturn(polledTransfer, mobileSetupFlowRef.current);
|
|
6077
|
+
}, [state.mobileFlow, polling.transfer, handleAuthorizedMobileReturn]);
|
|
6078
|
+
useEffect(() => {
|
|
6079
|
+
if (!state.mobileFlow) return;
|
|
6080
|
+
if (handlingMobileReturnRef.current) return;
|
|
6081
|
+
const transferIdToResume = pollingTransferIdRef.current ?? state.transfer?.id;
|
|
6082
|
+
if (!transferIdToResume) return;
|
|
6083
|
+
if (!polling.isPolling) polling.startPolling(transferIdToResume);
|
|
6084
|
+
const handleVisibility = () => {
|
|
6085
|
+
if (document.visibilityState === "visible" && !handlingMobileReturnRef.current) {
|
|
6086
|
+
polling.startPolling(transferIdToResume);
|
|
5936
6087
|
}
|
|
6088
|
+
};
|
|
6089
|
+
document.addEventListener("visibilitychange", handleVisibility);
|
|
6090
|
+
return () => document.removeEventListener("visibilitychange", handleVisibility);
|
|
6091
|
+
}, [state.mobileFlow, state.transfer?.id, polling.isPolling, polling.startPolling]);
|
|
6092
|
+
useEffect(() => {
|
|
6093
|
+
if (!pendingSelectSourceAction) {
|
|
6094
|
+
initializedSelectSourceActionRef.current = null;
|
|
6095
|
+
setSelectSourceChainName("");
|
|
6096
|
+
setSelectSourceTokenSymbol("");
|
|
6097
|
+
return;
|
|
6098
|
+
}
|
|
6099
|
+
if (initializedSelectSourceActionRef.current === pendingSelectSourceAction.id) return;
|
|
6100
|
+
const hasRecommended = !!selectSourceRecommended && selectSourceChoices.some(
|
|
6101
|
+
(chain) => chain.chainName === selectSourceRecommended.chainName && chain.tokens.some((t) => t.tokenSymbol === selectSourceRecommended.tokenSymbol)
|
|
5937
6102
|
);
|
|
5938
|
-
|
|
5939
|
-
|
|
6103
|
+
if (hasRecommended && selectSourceRecommended) {
|
|
6104
|
+
setSelectSourceChainName(selectSourceRecommended.chainName);
|
|
6105
|
+
setSelectSourceTokenSymbol(selectSourceRecommended.tokenSymbol);
|
|
6106
|
+
} else if (selectSourceChoices.length > 0 && selectSourceChoices[0].tokens.length > 0) {
|
|
6107
|
+
setSelectSourceChainName(selectSourceChoices[0].chainName);
|
|
6108
|
+
setSelectSourceTokenSymbol(selectSourceChoices[0].tokens[0].tokenSymbol);
|
|
6109
|
+
} else {
|
|
6110
|
+
setSelectSourceChainName("Base");
|
|
6111
|
+
setSelectSourceTokenSymbol("USDC");
|
|
6112
|
+
}
|
|
6113
|
+
initializedSelectSourceActionRef.current = pendingSelectSourceAction.id;
|
|
6114
|
+
}, [pendingSelectSourceAction, selectSourceChoices, selectSourceRecommended]);
|
|
6115
|
+
useEffect(() => {
|
|
6116
|
+
if (pendingSelectSourceAction && state.step === "processing") {
|
|
6117
|
+
preSelectSourceStepRef.current = state.step;
|
|
6118
|
+
dispatch({ type: "NAVIGATE", step: "select-source" });
|
|
6119
|
+
} else if (!pendingSelectSourceAction && state.step === "select-source") {
|
|
6120
|
+
dispatch({ type: "NAVIGATE", step: preSelectSourceStepRef.current ?? "processing" });
|
|
6121
|
+
preSelectSourceStepRef.current = null;
|
|
6122
|
+
}
|
|
6123
|
+
}, [pendingSelectSourceAction, state.step]);
|
|
6124
|
+
const handlers = useMemo(() => ({
|
|
6125
|
+
onSendLoginCode: handleSendLoginCode,
|
|
6126
|
+
onVerifyLoginCode: handleVerifyLoginCode,
|
|
6127
|
+
onResendLoginCode: handleResendLoginCode,
|
|
6128
|
+
onBackFromOtp: () => {
|
|
6129
|
+
setOtpCode("");
|
|
6130
|
+
dispatch({ type: "BACK_TO_LOGIN" });
|
|
6131
|
+
},
|
|
6132
|
+
onRegisterPasskey: handleRegisterPasskey,
|
|
6133
|
+
onCreatePasskeyViaPopup: handleCreatePasskeyViaPopup,
|
|
6134
|
+
onVerifyPasskeyViaPopup: handleVerifyPasskeyViaPopup,
|
|
6135
|
+
onSelectProvider: handleSelectProvider,
|
|
6136
|
+
onContinueConnection: handleContinueConnection,
|
|
6137
|
+
onPay: handlePay,
|
|
6138
|
+
onIncreaseLimit: handleIncreaseLimit,
|
|
6139
|
+
onConfirmSign: handleConfirmSign,
|
|
6140
|
+
onRetryMobileStatus: handleRetryMobileStatus,
|
|
6141
|
+
onLogout: handleLogout,
|
|
6142
|
+
onNewPayment: handleNewPayment,
|
|
6143
|
+
onNavigate: (step) => dispatch({ type: "NAVIGATE", step }),
|
|
6144
|
+
onSetAuthInput: setAuthInput,
|
|
6145
|
+
onSetOtpCode: (code) => {
|
|
6146
|
+
setOtpCode(code);
|
|
6147
|
+
dispatch({ type: "SET_ERROR", error: null });
|
|
6148
|
+
},
|
|
6149
|
+
onSelectSourceChainChange: handleSelectSourceChainChange,
|
|
6150
|
+
onSetSelectSourceTokenSymbol: setSelectSourceTokenSymbol,
|
|
6151
|
+
onConfirmSelectSource: handleConfirmSelectSource
|
|
6152
|
+
}), [
|
|
6153
|
+
handleSendLoginCode,
|
|
6154
|
+
handleVerifyLoginCode,
|
|
6155
|
+
handleResendLoginCode,
|
|
6156
|
+
handleRegisterPasskey,
|
|
6157
|
+
handleCreatePasskeyViaPopup,
|
|
6158
|
+
handleVerifyPasskeyViaPopup,
|
|
6159
|
+
handleSelectProvider,
|
|
6160
|
+
handleContinueConnection,
|
|
6161
|
+
handlePay,
|
|
6162
|
+
handleIncreaseLimit,
|
|
6163
|
+
handleConfirmSign,
|
|
6164
|
+
handleRetryMobileStatus,
|
|
6165
|
+
handleLogout,
|
|
6166
|
+
handleNewPayment,
|
|
6167
|
+
handleSelectSourceChainChange,
|
|
6168
|
+
handleConfirmSelectSource
|
|
6169
|
+
]);
|
|
6170
|
+
return /* @__PURE__ */ jsx(
|
|
6171
|
+
StepRenderer,
|
|
6172
|
+
{
|
|
6173
|
+
state,
|
|
6174
|
+
ready,
|
|
6175
|
+
authenticated,
|
|
6176
|
+
activeOtpStatus,
|
|
6177
|
+
pollingTransfer: polling.transfer,
|
|
6178
|
+
pollingError: polling.error,
|
|
6179
|
+
authExecutorError: authExecutor.error,
|
|
6180
|
+
transferSigningSigning: transferSigning.signing,
|
|
6181
|
+
transferSigningError: transferSigning.error,
|
|
6182
|
+
pendingConnections,
|
|
6183
|
+
sourceName,
|
|
6184
|
+
sourceAddress,
|
|
6185
|
+
sourceVerified,
|
|
6186
|
+
maxSourceBalance,
|
|
6187
|
+
tokenCount,
|
|
6188
|
+
selectedAccount,
|
|
6189
|
+
selectSourceChoices,
|
|
6190
|
+
selectSourceRecommended,
|
|
6191
|
+
authInput,
|
|
6192
|
+
otpCode,
|
|
6193
|
+
selectSourceChainName,
|
|
6194
|
+
selectSourceTokenSymbol,
|
|
6195
|
+
merchantName,
|
|
6196
|
+
onBack,
|
|
6197
|
+
onDismiss,
|
|
6198
|
+
autoCloseSeconds,
|
|
6199
|
+
depositAmount,
|
|
6200
|
+
handlers
|
|
6201
|
+
}
|
|
6202
|
+
);
|
|
5940
6203
|
}
|
|
5941
6204
|
|
|
5942
6205
|
export { AdvancedSourceScreen, CreatePasskeyScreen, IconCircle, InfoBanner, OutlineButton, PasskeyIframeBlockedError, PoweredByFooter, PrimaryButton, ScreenHeader, ScreenLayout, SelectSourceScreen, SettingsMenu, SetupScreen, Spinner, StepList, SwypePayment, SwypeProvider, buildPasskeyPopupOptions, createPasskeyCredential, createPasskeyViaPopup, darkTheme, deviceHasPasskey, findDevicePasskey, findDevicePasskeyViaPopup, getTheme, lightTheme, resolvePasskeyRpId, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
|