@pollar/core 0.9.1-rc.0 → 0.10.0-rc.0
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.d.mts +2275 -1426
- package/dist/index.d.ts +2275 -1426
- package/dist/index.js +498 -137
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +498 -137
- package/dist/index.mjs.map +1 -1
- package/dist/index.rn.d.mts +2 -2
- package/dist/index.rn.d.ts +2 -2
- package/dist/index.rn.js +475 -136
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +475 -136
- package/dist/index.rn.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.rn.js
CHANGED
|
@@ -1022,7 +1022,19 @@ function createLogger(level = "info", sink = console) {
|
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
1024
1024
|
// src/lib/logging.ts
|
|
1025
|
-
var SENSITIVE_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
1025
|
+
var SENSITIVE_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
1026
|
+
"email",
|
|
1027
|
+
"code",
|
|
1028
|
+
"walletAddress",
|
|
1029
|
+
"dpopJwk",
|
|
1030
|
+
"response",
|
|
1031
|
+
"refreshToken",
|
|
1032
|
+
// SEP-10 challenge envelopes: a counter-signed challenge is a live, replayable
|
|
1033
|
+
// auth credential — never log it in the clear.
|
|
1034
|
+
"signedChallengeXdr",
|
|
1035
|
+
"challengeXdr",
|
|
1036
|
+
"signedTxXdr"
|
|
1037
|
+
]);
|
|
1026
1038
|
function redactBody(body) {
|
|
1027
1039
|
if (!body || typeof body !== "object") return body;
|
|
1028
1040
|
const out = {};
|
|
@@ -1119,8 +1131,36 @@ function defaultStorage(options = {}) {
|
|
|
1119
1131
|
return createLocalStorageAdapter(options);
|
|
1120
1132
|
}
|
|
1121
1133
|
|
|
1134
|
+
// src/types.ts
|
|
1135
|
+
var AUTH_ERROR_CODES = {
|
|
1136
|
+
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
1137
|
+
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
1138
|
+
SESSION_INVALID: "SESSION_INVALID",
|
|
1139
|
+
EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
|
|
1140
|
+
EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
|
|
1141
|
+
EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
|
|
1142
|
+
EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
|
|
1143
|
+
AUTH_FAILED: "AUTH_FAILED",
|
|
1144
|
+
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1145
|
+
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1146
|
+
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1147
|
+
EXTERNAL_AUTH_FAILED: "EXTERNAL_AUTH_FAILED",
|
|
1148
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1149
|
+
// Generic bucket for on-chain transaction failures; the precise reason is the
|
|
1150
|
+
// backend `code` (e.g. TX_FEE_LIMIT_EXCEEDED) carried alongside on the outcome.
|
|
1151
|
+
TX_FAILED: "TX_FAILED",
|
|
1152
|
+
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1153
|
+
};
|
|
1154
|
+
var PollarFlowError = class extends Error {
|
|
1155
|
+
constructor(message) {
|
|
1156
|
+
super(message);
|
|
1157
|
+
this.code = "INVALID_FLOW";
|
|
1158
|
+
this.name = "PollarFlowError";
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1122
1162
|
// src/version.ts
|
|
1123
|
-
var POLLAR_CORE_VERSION = "0.
|
|
1163
|
+
var POLLAR_CORE_VERSION = "0.10.0-rc.0" ;
|
|
1124
1164
|
|
|
1125
1165
|
// src/visibility/noop.ts
|
|
1126
1166
|
function createNoopVisibilityProvider() {
|
|
@@ -1173,30 +1213,6 @@ function defaultVisibilityProvider() {
|
|
|
1173
1213
|
return createNoopVisibilityProvider();
|
|
1174
1214
|
}
|
|
1175
1215
|
|
|
1176
|
-
// src/types.ts
|
|
1177
|
-
var AUTH_ERROR_CODES = {
|
|
1178
|
-
SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
|
|
1179
|
-
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
1180
|
-
SESSION_INVALID: "SESSION_INVALID",
|
|
1181
|
-
EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
|
|
1182
|
-
EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
|
|
1183
|
-
EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
|
|
1184
|
-
EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
|
|
1185
|
-
AUTH_FAILED: "AUTH_FAILED",
|
|
1186
|
-
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1187
|
-
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1188
|
-
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1189
|
-
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1190
|
-
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1191
|
-
};
|
|
1192
|
-
var PollarFlowError = class extends Error {
|
|
1193
|
-
constructor(message) {
|
|
1194
|
-
super(message);
|
|
1195
|
-
this.code = "INVALID_FLOW";
|
|
1196
|
-
this.name = "PollarFlowError";
|
|
1197
|
-
}
|
|
1198
|
-
};
|
|
1199
|
-
|
|
1200
1216
|
// src/wallets/FreighterAdapter.ts
|
|
1201
1217
|
var import_freighter_api = __toESM(require_index_min());
|
|
1202
1218
|
|
|
@@ -1452,6 +1468,10 @@ function isValidSession(value, logger = console) {
|
|
|
1452
1468
|
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be internal|smart|external");
|
|
1453
1469
|
return false;
|
|
1454
1470
|
}
|
|
1471
|
+
if (w["provider"] !== void 0 && typeof w["provider"] !== "string") {
|
|
1472
|
+
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.provider must be a string if present");
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
1455
1475
|
if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
|
|
1456
1476
|
logger.debug("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
|
|
1457
1477
|
return false;
|
|
@@ -1747,14 +1767,91 @@ async function createAuthSession(deps) {
|
|
|
1747
1767
|
return data.content.clientSessionId;
|
|
1748
1768
|
}
|
|
1749
1769
|
|
|
1770
|
+
// src/client/auth/errorMessages.ts
|
|
1771
|
+
var CATALOG = {
|
|
1772
|
+
// ── Smart-account deploy / sponsor wallet ──────────────────────────────────
|
|
1773
|
+
SPONSOR_NOT_FUNDED: {
|
|
1774
|
+
message: "This app can't create your wallet yet \u2014 its sponsor account isn't funded. Please contact the app's developer.",
|
|
1775
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1776
|
+
},
|
|
1777
|
+
APP_WALLET_NOT_FOUND: {
|
|
1778
|
+
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1779
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1780
|
+
},
|
|
1781
|
+
WALLET_NOT_FOUND: {
|
|
1782
|
+
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1783
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1784
|
+
},
|
|
1785
|
+
PASSKEY_DEPLOY_FAILED: {
|
|
1786
|
+
message: "We couldn't finish creating your wallet. Please try again in a moment.",
|
|
1787
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1788
|
+
},
|
|
1789
|
+
// ── Passkey ceremony ────────────────────────────────────────────────────────
|
|
1790
|
+
PASSKEY_ALREADY_REGISTERED: {
|
|
1791
|
+
message: "A passkey is already registered for this account. Try signing in instead.",
|
|
1792
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1793
|
+
},
|
|
1794
|
+
PASSKEY_UNKNOWN_CREDENTIAL: {
|
|
1795
|
+
message: "We don't recognize this passkey. Try creating a new one.",
|
|
1796
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1797
|
+
},
|
|
1798
|
+
PASSKEY_VERIFICATION_FAILED: {
|
|
1799
|
+
message: "We couldn't verify your passkey. Please try again.",
|
|
1800
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1801
|
+
},
|
|
1802
|
+
PASSKEY_CHALLENGE_MISSING: {
|
|
1803
|
+
message: "Your passkey session expired. Please start again.",
|
|
1804
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1805
|
+
},
|
|
1806
|
+
// ── On-chain transaction failures (surfaced during deploy/transfer) ─────────
|
|
1807
|
+
// These map to the TX_FAILED bucket (not PASSKEY_FAILED) — the precise reason
|
|
1808
|
+
// is the entry key itself, surfaced as the raw `code` on the tx outcome.
|
|
1809
|
+
TX_INSUFFICIENT_BALANCE: {
|
|
1810
|
+
message: "Insufficient balance to complete this transaction.",
|
|
1811
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1812
|
+
},
|
|
1813
|
+
TX_INSUFFICIENT_FEE: {
|
|
1814
|
+
message: "Not enough XLM to cover the network fee. Add more XLM to your wallet and try again.",
|
|
1815
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1816
|
+
},
|
|
1817
|
+
TX_FEE_LIMIT_EXCEEDED: {
|
|
1818
|
+
message: "The transaction fee is above the allowed limit. Please try again.",
|
|
1819
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1820
|
+
},
|
|
1821
|
+
TX_CONTRACT_FAILED: {
|
|
1822
|
+
message: "The contract rejected this operation. Check the operation is allowed right now and try again.",
|
|
1823
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1824
|
+
},
|
|
1825
|
+
TX_DESTINATION_NOT_FOUND: {
|
|
1826
|
+
message: "The destination account doesn't exist on the network yet.",
|
|
1827
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1828
|
+
},
|
|
1829
|
+
TX_NO_TRUSTLINE: {
|
|
1830
|
+
message: "The destination can't receive this asset yet (no trustline).",
|
|
1831
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1832
|
+
},
|
|
1833
|
+
TX_BAD_SEQUENCE: {
|
|
1834
|
+
message: "Something went out of sync. Please try again.",
|
|
1835
|
+
errorCode: AUTH_ERROR_CODES.TX_FAILED
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
function resolveAuthError(code, fallbackMessage) {
|
|
1839
|
+
if (code && CATALOG[code]) return CATALOG[code];
|
|
1840
|
+
return { message: fallbackMessage, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED };
|
|
1841
|
+
}
|
|
1842
|
+
function extractErrorCode(error, data) {
|
|
1843
|
+
return error?.code ?? data?.code ?? void 0;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1750
1846
|
// src/client/auth/emailFlow.ts
|
|
1751
|
-
async function initEmailSession(
|
|
1752
|
-
const clientSessionId = await
|
|
1753
|
-
if (!clientSessionId) return;
|
|
1754
|
-
|
|
1847
|
+
async function initEmailSession(ctx) {
|
|
1848
|
+
const clientSessionId = await ctx.createSession();
|
|
1849
|
+
if (!clientSessionId) return null;
|
|
1850
|
+
ctx.setAuthState({ step: "entering_email", clientSessionId });
|
|
1851
|
+
return clientSessionId;
|
|
1755
1852
|
}
|
|
1756
|
-
async function sendEmailCode(email, clientSessionId,
|
|
1757
|
-
const { api, logger, signal, setAuthState } =
|
|
1853
|
+
async function sendEmailCode(email, clientSessionId, ctx) {
|
|
1854
|
+
const { api, logger, signal, setAuthState } = ctx;
|
|
1758
1855
|
setAuthState({ step: "sending_email", email });
|
|
1759
1856
|
const body = { clientSessionId, email };
|
|
1760
1857
|
const { data, error } = await api.POST("/auth/email", { body, signal });
|
|
@@ -1770,13 +1867,13 @@ async function sendEmailCode(email, clientSessionId, deps) {
|
|
|
1770
1867
|
}
|
|
1771
1868
|
setAuthState({ step: "entering_code", clientSessionId, email });
|
|
1772
1869
|
}
|
|
1773
|
-
async function verifyAndAuthenticate(code, clientSessionId, email,
|
|
1774
|
-
const { api, logger, signal, setAuthState } =
|
|
1870
|
+
async function verifyAndAuthenticate(code, clientSessionId, email, ctx) {
|
|
1871
|
+
const { api, logger, signal, setAuthState } = ctx;
|
|
1775
1872
|
setAuthState({ step: "verifying_email_code", clientSessionId, email });
|
|
1776
1873
|
const body = { clientSessionId, code };
|
|
1777
1874
|
const { data, error } = await api.POST("/auth/email/verify-code", { body, signal });
|
|
1778
1875
|
if (data?.code === "SDK_EMAIL_CODE_VERIFIED") {
|
|
1779
|
-
await authenticate(clientSessionId
|
|
1876
|
+
await ctx.authenticate(clientSessionId);
|
|
1780
1877
|
return;
|
|
1781
1878
|
}
|
|
1782
1879
|
const errCode = error?.error ?? data?.code;
|
|
@@ -1854,68 +1951,6 @@ async function loginOAuth(provider, deps) {
|
|
|
1854
1951
|
await authenticate(clientSessionId, deps);
|
|
1855
1952
|
}
|
|
1856
1953
|
|
|
1857
|
-
// src/client/auth/errorMessages.ts
|
|
1858
|
-
var CATALOG = {
|
|
1859
|
-
// ── Smart-account deploy / sponsor wallet ──────────────────────────────────
|
|
1860
|
-
SPONSOR_NOT_FUNDED: {
|
|
1861
|
-
message: "This app can't create your wallet yet \u2014 its sponsor account isn't funded. Please contact the app's developer.",
|
|
1862
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1863
|
-
},
|
|
1864
|
-
APP_WALLET_NOT_FOUND: {
|
|
1865
|
-
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1866
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1867
|
-
},
|
|
1868
|
-
WALLET_NOT_FOUND: {
|
|
1869
|
-
message: "This app isn't fully set up to create wallets yet. Please contact the app's developer.",
|
|
1870
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1871
|
-
},
|
|
1872
|
-
PASSKEY_DEPLOY_FAILED: {
|
|
1873
|
-
message: "We couldn't finish creating your wallet. Please try again in a moment.",
|
|
1874
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1875
|
-
},
|
|
1876
|
-
// ── Passkey ceremony ────────────────────────────────────────────────────────
|
|
1877
|
-
PASSKEY_ALREADY_REGISTERED: {
|
|
1878
|
-
message: "A passkey is already registered for this account. Try signing in instead.",
|
|
1879
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1880
|
-
},
|
|
1881
|
-
PASSKEY_UNKNOWN_CREDENTIAL: {
|
|
1882
|
-
message: "We don't recognize this passkey. Try creating a new one.",
|
|
1883
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1884
|
-
},
|
|
1885
|
-
PASSKEY_VERIFICATION_FAILED: {
|
|
1886
|
-
message: "We couldn't verify your passkey. Please try again.",
|
|
1887
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1888
|
-
},
|
|
1889
|
-
PASSKEY_CHALLENGE_MISSING: {
|
|
1890
|
-
message: "Your passkey session expired. Please start again.",
|
|
1891
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1892
|
-
},
|
|
1893
|
-
// ── On-chain transaction failures (surfaced during deploy/transfer) ─────────
|
|
1894
|
-
TX_INSUFFICIENT_BALANCE: {
|
|
1895
|
-
message: "Insufficient balance to complete this transaction.",
|
|
1896
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1897
|
-
},
|
|
1898
|
-
TX_DESTINATION_NOT_FOUND: {
|
|
1899
|
-
message: "The destination account doesn't exist on the network yet.",
|
|
1900
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1901
|
-
},
|
|
1902
|
-
TX_NO_TRUSTLINE: {
|
|
1903
|
-
message: "The destination can't receive this asset yet (no trustline).",
|
|
1904
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1905
|
-
},
|
|
1906
|
-
TX_BAD_SEQUENCE: {
|
|
1907
|
-
message: "Something went out of sync. Please try again.",
|
|
1908
|
-
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1909
|
-
}
|
|
1910
|
-
};
|
|
1911
|
-
function resolveAuthError(code, fallbackMessage) {
|
|
1912
|
-
if (code && CATALOG[code]) return CATALOG[code];
|
|
1913
|
-
return { message: fallbackMessage, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED };
|
|
1914
|
-
}
|
|
1915
|
-
function extractErrorCode(error, data) {
|
|
1916
|
-
return error?.code ?? data?.code ?? void 0;
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
1954
|
// src/client/auth/passkeyFlow.ts
|
|
1920
1955
|
async function smartWalletFlow(deps, mode) {
|
|
1921
1956
|
const { api, logger, signal, setAuthState, passkey } = deps;
|
|
@@ -1972,6 +2007,71 @@ function failPasskey(setAuthState, code, fallbackMessage) {
|
|
|
1972
2007
|
setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode });
|
|
1973
2008
|
}
|
|
1974
2009
|
|
|
2010
|
+
// src/client/auth/providers.ts
|
|
2011
|
+
function oauthProvider(provider) {
|
|
2012
|
+
return {
|
|
2013
|
+
id: provider,
|
|
2014
|
+
login: (ctx) => ctx.startHostedOAuth(provider)
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
2017
|
+
function emailProvider() {
|
|
2018
|
+
return {
|
|
2019
|
+
id: "email",
|
|
2020
|
+
login: async (ctx, options) => {
|
|
2021
|
+
const email = options.email ?? "";
|
|
2022
|
+
const clientSessionId = await initEmailSession(ctx);
|
|
2023
|
+
if (clientSessionId) await sendEmailCode(email, clientSessionId, ctx);
|
|
2024
|
+
},
|
|
2025
|
+
actions: {
|
|
2026
|
+
begin: async (ctx) => {
|
|
2027
|
+
await initEmailSession(ctx);
|
|
2028
|
+
},
|
|
2029
|
+
sendCode: (ctx, payload) => {
|
|
2030
|
+
const { email, clientSessionId } = payload ?? {};
|
|
2031
|
+
return sendEmailCode(email, clientSessionId, ctx);
|
|
2032
|
+
},
|
|
2033
|
+
verifyCode: (ctx, payload) => {
|
|
2034
|
+
const { code, clientSessionId, email } = payload ?? {};
|
|
2035
|
+
return verifyAndAuthenticate(code, clientSessionId, email, ctx);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// src/client/auth/sep10-challenge.ts
|
|
2042
|
+
var ENVELOPE_TYPE_TX_V0 = 0;
|
|
2043
|
+
var ENVELOPE_TYPE_TX = 2;
|
|
2044
|
+
var KEY_TYPE_ED25519 = 0;
|
|
2045
|
+
var SEQ_OFFSET_V1 = 44;
|
|
2046
|
+
var SEQ_OFFSET_V0 = 40;
|
|
2047
|
+
function base64ToBytes(b64) {
|
|
2048
|
+
return base64urlDecode(b64.replace(/\+/g, "-").replace(/\//g, "_"));
|
|
2049
|
+
}
|
|
2050
|
+
function isI64Zero(view, offset) {
|
|
2051
|
+
return view.getUint32(offset, false) === 0 && view.getUint32(offset + 4, false) === 0;
|
|
2052
|
+
}
|
|
2053
|
+
function isValidSep10Challenge(challengeXdr) {
|
|
2054
|
+
try {
|
|
2055
|
+
const bytes = base64ToBytes(challengeXdr.trim());
|
|
2056
|
+
if (bytes.length < 8) return false;
|
|
2057
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2058
|
+
const envelopeType = view.getUint32(0, false);
|
|
2059
|
+
let seqOffset;
|
|
2060
|
+
if (envelopeType === ENVELOPE_TYPE_TX) {
|
|
2061
|
+
if (view.getUint32(4, false) !== KEY_TYPE_ED25519) return false;
|
|
2062
|
+
seqOffset = SEQ_OFFSET_V1;
|
|
2063
|
+
} else if (envelopeType === ENVELOPE_TYPE_TX_V0) {
|
|
2064
|
+
seqOffset = SEQ_OFFSET_V0;
|
|
2065
|
+
} else {
|
|
2066
|
+
return false;
|
|
2067
|
+
}
|
|
2068
|
+
if (bytes.length < seqOffset + 8) return false;
|
|
2069
|
+
return isI64Zero(view, seqOffset);
|
|
2070
|
+
} catch {
|
|
2071
|
+
return false;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
|
|
1975
2075
|
// src/client/auth/walletFlow.ts
|
|
1976
2076
|
function withSignal(promise, signal) {
|
|
1977
2077
|
return Promise.race([
|
|
@@ -1985,6 +2085,16 @@ function withSignal(promise, signal) {
|
|
|
1985
2085
|
})
|
|
1986
2086
|
]);
|
|
1987
2087
|
}
|
|
2088
|
+
async function requestWalletChallenge(clientSessionId, walletAddress, deps) {
|
|
2089
|
+
const { api, logger, signal } = deps;
|
|
2090
|
+
const body = { clientSessionId, walletAddress };
|
|
2091
|
+
const { data, error } = await api.POST("/auth/wallet/challenge", { body, signal });
|
|
2092
|
+
if (error || !data?.success) {
|
|
2093
|
+
if (!error) logApiError(logger, "POST /auth/wallet/challenge", { body, data });
|
|
2094
|
+
return null;
|
|
2095
|
+
}
|
|
2096
|
+
return data.content.challengeXdr;
|
|
2097
|
+
}
|
|
1988
2098
|
async function loginWallet(type, deps) {
|
|
1989
2099
|
const { api, logger, signal, setAuthState } = deps;
|
|
1990
2100
|
const clientSessionId = await createAuthSession(deps);
|
|
@@ -2001,8 +2111,33 @@ async function loginWallet(type, deps) {
|
|
|
2001
2111
|
const { address } = await withSignal(adapter.connect(), signal);
|
|
2002
2112
|
connectedWallet = address;
|
|
2003
2113
|
deps.storeWalletAdapter(adapter, type);
|
|
2114
|
+
setAuthState({ step: "signing_wallet_challenge", walletType: type });
|
|
2115
|
+
const challengeXdr = await requestWalletChallenge(clientSessionId, address, deps);
|
|
2116
|
+
if (!challengeXdr) {
|
|
2117
|
+
setAuthState({
|
|
2118
|
+
step: "error",
|
|
2119
|
+
previousStep: "signing_wallet_challenge",
|
|
2120
|
+
message: "Failed to obtain wallet challenge",
|
|
2121
|
+
errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
|
|
2122
|
+
});
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
if (!isValidSep10Challenge(challengeXdr)) {
|
|
2126
|
+
logApiError(logger, "SEP-10 challenge validation", { error: "unexpected challenge structure (sequence != 0?)" });
|
|
2127
|
+
setAuthState({
|
|
2128
|
+
step: "error",
|
|
2129
|
+
previousStep: "signing_wallet_challenge",
|
|
2130
|
+
message: "Invalid wallet challenge",
|
|
2131
|
+
errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
|
|
2132
|
+
});
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
const { signedTxXdr } = await withSignal(
|
|
2136
|
+
adapter.signTransaction(challengeXdr, { networkPassphrase: deps.networkPassphrase }),
|
|
2137
|
+
signal
|
|
2138
|
+
);
|
|
2004
2139
|
setAuthState({ step: "authenticating_wallet" });
|
|
2005
|
-
const body = { clientSessionId, walletAddress: address };
|
|
2140
|
+
const body = { clientSessionId, walletAddress: address, signedChallengeXdr: signedTxXdr };
|
|
2006
2141
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", { body, signal });
|
|
2007
2142
|
if (walletError || !walletData?.success) {
|
|
2008
2143
|
if (!walletError) logApiError(logger, "POST /auth/wallet", { body, data: walletData });
|
|
@@ -2091,6 +2226,13 @@ var PollarClient = class {
|
|
|
2091
2226
|
this._loginController = null;
|
|
2092
2227
|
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2093
2228
|
this._resumeController = null;
|
|
2229
|
+
/**
|
|
2230
|
+
* Registry of pluggable login strategies, keyed by provider id. Seeded with
|
|
2231
|
+
* the built-ins (`google`, `github`, `email`) and then any `config.providers`
|
|
2232
|
+
* (which can override a built-in by reusing its id). `wallet` is deliberately
|
|
2233
|
+
* absent — it keeps its own dedicated flow. See {@link PollarAuthProvider}.
|
|
2234
|
+
*/
|
|
2235
|
+
this._providers = /* @__PURE__ */ new Map();
|
|
2094
2236
|
this.apiKey = config.apiKey;
|
|
2095
2237
|
this.id = randomUUID();
|
|
2096
2238
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
@@ -2112,6 +2254,12 @@ var PollarClient = class {
|
|
|
2112
2254
|
this._maxIdleMs = config.maxIdleMs;
|
|
2113
2255
|
this._openAuthUrl = config.openAuthUrl ?? defaultWebOAuthOpener;
|
|
2114
2256
|
this._oauthRedirectUri = config.oauthRedirectUri ?? (isBrowser ? window.location?.origin ?? "" : "");
|
|
2257
|
+
for (const provider of [oauthProvider("google"), oauthProvider("github"), emailProvider()]) {
|
|
2258
|
+
this._providers.set(provider.id, provider);
|
|
2259
|
+
}
|
|
2260
|
+
for (const provider of config.providers ?? []) {
|
|
2261
|
+
this._providers.set(provider.id, provider);
|
|
2262
|
+
}
|
|
2115
2263
|
this._api = createApiClient(this.basePath);
|
|
2116
2264
|
this._wireMiddlewares();
|
|
2117
2265
|
this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
|
|
@@ -2518,28 +2666,42 @@ var PollarClient = class {
|
|
|
2518
2666
|
warnServerSide("login");
|
|
2519
2667
|
return;
|
|
2520
2668
|
}
|
|
2521
|
-
if (options.provider === "
|
|
2522
|
-
const controller = this._newController();
|
|
2523
|
-
const deps = this._flowDeps(controller.signal);
|
|
2524
|
-
if (options.provider === "google" || options.provider === "github") {
|
|
2525
|
-
loginOAuth(options.provider, {
|
|
2526
|
-
...deps,
|
|
2527
|
-
basePath: this.basePath,
|
|
2528
|
-
apiKey: this.apiKey,
|
|
2529
|
-
openAuthUrl: this._openAuthUrl,
|
|
2530
|
-
redirectUri: this._oauthRedirectUri
|
|
2531
|
-
}).catch((err) => this._handleFlowError(err));
|
|
2532
|
-
} else if (options.provider === "email") {
|
|
2533
|
-
const { email } = options;
|
|
2534
|
-
initEmailSession(deps).then(() => {
|
|
2535
|
-
if (this._authState.step === "entering_email") {
|
|
2536
|
-
return sendEmailCode(email, this._authState.clientSessionId, deps);
|
|
2537
|
-
}
|
|
2538
|
-
}).catch((err) => this._handleFlowError(err));
|
|
2539
|
-
}
|
|
2540
|
-
} else if (options.provider === "wallet") {
|
|
2669
|
+
if (options.provider === "wallet") {
|
|
2541
2670
|
this.loginWallet(options.type);
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
const provider = this._providers.get(options.provider);
|
|
2674
|
+
if (!provider?.login) {
|
|
2675
|
+
this._setAuthState({
|
|
2676
|
+
step: "error",
|
|
2677
|
+
previousStep: this._authState.step,
|
|
2678
|
+
message: `No auth provider registered for '${options.provider}'`,
|
|
2679
|
+
errorCode: AUTH_ERROR_CODES.AUTH_FAILED
|
|
2680
|
+
});
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
const controller = this._newController();
|
|
2684
|
+
provider.login(this._providerContext(controller.signal), options).catch((err) => this._handleFlowError(err));
|
|
2685
|
+
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Invoke a named secondary step on a registered provider (e.g. email's
|
|
2688
|
+
* `sendCode` / `verifyCode`, or a custom provider's multi-step continuation).
|
|
2689
|
+
* Reuses the in-flight login `AbortController` when one exists so the step
|
|
2690
|
+
* stays cancellable via `cancelLogin()`; otherwise starts a fresh one. The
|
|
2691
|
+
* built-in email steps also have dedicated typed methods
|
|
2692
|
+
* ({@link sendEmailCode} / {@link verifyEmailCode}) — prefer those for email.
|
|
2693
|
+
*/
|
|
2694
|
+
providerAction(provider, action, payload) {
|
|
2695
|
+
if (!isClientRuntime) {
|
|
2696
|
+
warnServerSide("providerAction");
|
|
2697
|
+
return;
|
|
2542
2698
|
}
|
|
2699
|
+
const fn = this._providers.get(provider)?.actions?.[action];
|
|
2700
|
+
if (!fn) {
|
|
2701
|
+
throw new PollarFlowError(`Auth provider '${provider}' has no action '${action}'`);
|
|
2702
|
+
}
|
|
2703
|
+
const signal = this._loginController?.signal ?? this._newController().signal;
|
|
2704
|
+
fn(this._providerContext(signal), payload).catch((err) => this._handleFlowError(err));
|
|
2543
2705
|
}
|
|
2544
2706
|
// ─── Email OTP flow (3 steps) ─────────────────────────────────────────────
|
|
2545
2707
|
beginEmailLogin() {
|
|
@@ -2548,7 +2710,7 @@ var PollarClient = class {
|
|
|
2548
2710
|
return;
|
|
2549
2711
|
}
|
|
2550
2712
|
const controller = this._newController();
|
|
2551
|
-
initEmailSession(this.
|
|
2713
|
+
initEmailSession(this._providerContext(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2552
2714
|
}
|
|
2553
2715
|
sendEmailCode(email) {
|
|
2554
2716
|
if (!isClientRuntime) {
|
|
@@ -2560,7 +2722,7 @@ var PollarClient = class {
|
|
|
2560
2722
|
}
|
|
2561
2723
|
const { clientSessionId } = this._authState;
|
|
2562
2724
|
const signal = this._loginController.signal;
|
|
2563
|
-
sendEmailCode(email, clientSessionId, this.
|
|
2725
|
+
sendEmailCode(email, clientSessionId, this._providerContext(signal)).catch((err) => this._handleFlowError(err));
|
|
2564
2726
|
}
|
|
2565
2727
|
verifyEmailCode(code) {
|
|
2566
2728
|
if (!isClientRuntime) {
|
|
@@ -2575,7 +2737,7 @@ var PollarClient = class {
|
|
|
2575
2737
|
const clientSessionId = state.step === "entering_code" ? state.clientSessionId : state.clientSessionId;
|
|
2576
2738
|
const email = state.step === "entering_code" ? state.email : state.email ?? "";
|
|
2577
2739
|
const controller = this._newController();
|
|
2578
|
-
verifyAndAuthenticate(code, clientSessionId, email, this.
|
|
2740
|
+
verifyAndAuthenticate(code, clientSessionId, email, this._providerContext(controller.signal)).catch(
|
|
2579
2741
|
(err) => this._handleFlowError(err)
|
|
2580
2742
|
);
|
|
2581
2743
|
}
|
|
@@ -2940,6 +3102,29 @@ var PollarClient = class {
|
|
|
2940
3102
|
getWalletType() {
|
|
2941
3103
|
return this._walletAdapter?.type ?? null;
|
|
2942
3104
|
}
|
|
3105
|
+
/**
|
|
3106
|
+
* The authenticated user's wallet as a {@link WalletInfo} discriminated union,
|
|
3107
|
+
* or `null` when there's no session (or the session carries no address yet).
|
|
3108
|
+
*
|
|
3109
|
+
* `custody` strictly determines `provider` (the mapping is 1:1 and fixed at
|
|
3110
|
+
* account creation server-side): `external` reports the connected adapter id
|
|
3111
|
+
* (`getWalletType()`), `smart` is always `'passkey'`, and `internal` reports
|
|
3112
|
+
* the login method the backend recorded (`null` for pre-provider sessions).
|
|
3113
|
+
*/
|
|
3114
|
+
getWallet() {
|
|
3115
|
+
const w = this._session?.wallet;
|
|
3116
|
+
if (!w || !w.address) return null;
|
|
3117
|
+
switch (w.type) {
|
|
3118
|
+
case "external":
|
|
3119
|
+
return { custody: "external", address: w.address, provider: this._walletAdapter?.type ?? null };
|
|
3120
|
+
case "smart":
|
|
3121
|
+
return { custody: "smart", address: w.address, provider: "passkey" };
|
|
3122
|
+
case "internal":
|
|
3123
|
+
return { custody: "internal", address: w.address, provider: w.provider ?? null };
|
|
3124
|
+
default:
|
|
3125
|
+
return null;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
2943
3128
|
/**
|
|
2944
3129
|
* Signs the given unsigned XDR and returns the signed XDR.
|
|
2945
3130
|
*
|
|
@@ -2993,14 +3178,16 @@ var PollarClient = class {
|
|
|
2993
3178
|
});
|
|
2994
3179
|
return { status: "signed", signedXdr, submissionToken: idempotencyKey };
|
|
2995
3180
|
}
|
|
2996
|
-
const details = error
|
|
3181
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
2997
3182
|
this._setTransactionState({
|
|
2998
3183
|
step: "error",
|
|
2999
3184
|
phase: "signing",
|
|
3000
3185
|
...buildData && { buildData },
|
|
3001
|
-
...details && { details }
|
|
3186
|
+
...details && { details },
|
|
3187
|
+
...code && { code },
|
|
3188
|
+
...message && { message }
|
|
3002
3189
|
});
|
|
3003
|
-
return { status: "error", ...details && { details } };
|
|
3190
|
+
return { status: "error", ...details && { details }, ...code && { code }, ...message && { message } };
|
|
3004
3191
|
} catch (err) {
|
|
3005
3192
|
const details = err instanceof Error ? err.message : void 0;
|
|
3006
3193
|
this._setTransactionState({
|
|
@@ -3012,6 +3199,54 @@ var PollarClient = class {
|
|
|
3012
3199
|
return { status: "error", ...details && { details } };
|
|
3013
3200
|
}
|
|
3014
3201
|
}
|
|
3202
|
+
/**
|
|
3203
|
+
* Sign a single Soroban authorization entry (`SorobanAuthorizationEntry`).
|
|
3204
|
+
*
|
|
3205
|
+
* Use this when a contract is the transaction source (e.g. it sponsors the
|
|
3206
|
+
* gas and swaps the fee out of the user's token) and only needs the user's
|
|
3207
|
+
* address-credentials authorization, not a full signed envelope. The signed
|
|
3208
|
+
* entry is returned as base64 XDR for the caller to compose into its tx.
|
|
3209
|
+
*
|
|
3210
|
+
* - External wallets (Freighter/Albedo) sign the entry via the provider.
|
|
3211
|
+
* - Custodial wallets are signed by the backend, which FIRST validates the
|
|
3212
|
+
* entry's invocation tree against the app's contract/function allowlist and
|
|
3213
|
+
* caps the validity window — entries touching a non-allowlisted contract or
|
|
3214
|
+
* function, or expiring too far ahead, are rejected.
|
|
3215
|
+
*
|
|
3216
|
+
* @param entryXdr base64 XDR of the unsigned `SorobanAuthorizationEntry`.
|
|
3217
|
+
* @param options.validUntilLedger absolute ledger the signature expires at
|
|
3218
|
+
* (computed from the network's latest ledger). Ignored on the external-wallet
|
|
3219
|
+
* path, where the provider sets its own expiration.
|
|
3220
|
+
*/
|
|
3221
|
+
async signAuthEntry(entryXdr, options) {
|
|
3222
|
+
if (this._walletAdapter) {
|
|
3223
|
+
const accountToSign = this._session?.wallet?.address;
|
|
3224
|
+
try {
|
|
3225
|
+
const { signedAuthEntry } = await this._walletAdapter.signAuthEntry(
|
|
3226
|
+
entryXdr,
|
|
3227
|
+
accountToSign ? { accountToSign } : void 0
|
|
3228
|
+
);
|
|
3229
|
+
return { status: "signed", signedAuthEntry };
|
|
3230
|
+
} catch (err) {
|
|
3231
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3232
|
+
return { status: "error", ...details && { details } };
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
const address = this._session?.wallet?.address ?? "";
|
|
3236
|
+
try {
|
|
3237
|
+
const { data, error } = await this._api.POST("/tx/sign-auth-entry", {
|
|
3238
|
+
body: { network: this.getNetwork(), address, entryXdr, validUntilLedger: options.validUntilLedger }
|
|
3239
|
+
});
|
|
3240
|
+
if (!error && data?.success && data.content?.signedAuthEntry) {
|
|
3241
|
+
return { status: "signed", signedAuthEntry: data.content.signedAuthEntry };
|
|
3242
|
+
}
|
|
3243
|
+
const details = error?.details;
|
|
3244
|
+
return { status: "error", ...details && { details } };
|
|
3245
|
+
} catch (err) {
|
|
3246
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3247
|
+
return { status: "error", ...details && { details } };
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3015
3250
|
/**
|
|
3016
3251
|
* Submits a signed XDR via `/tx/submit` regardless of wallet type
|
|
3017
3252
|
* (custodial or external). Routing through sdk-api gives us:
|
|
@@ -3030,6 +3265,21 @@ var PollarClient = class {
|
|
|
3030
3265
|
* `submitted` on Horizon ack (pending), `success` on ledger confirmation,
|
|
3031
3266
|
* or `error[phase: 'submitting']` on failure.
|
|
3032
3267
|
*/
|
|
3268
|
+
/**
|
|
3269
|
+
* Normalize a backend API error into { details, code, message }. `code` is the
|
|
3270
|
+
* precise backend ErrorCode (e.g. `TX_FEE_LIMIT_EXCEEDED`) for programmatic
|
|
3271
|
+
* handling; `message` is a friendly string from the error catalog; `details`
|
|
3272
|
+
* is the raw diagnostic. Lets tx flows surface a typed reason instead of an
|
|
3273
|
+
* opaque details string.
|
|
3274
|
+
*/
|
|
3275
|
+
_resolveTxApiError(error) {
|
|
3276
|
+
const e = error;
|
|
3277
|
+
const details = e?.details ?? e?.message;
|
|
3278
|
+
const code = e?.code;
|
|
3279
|
+
if (!code) return details ? { details } : {};
|
|
3280
|
+
const { message } = resolveAuthError(code, details ?? code);
|
|
3281
|
+
return { code, message, ...details && { details } };
|
|
3282
|
+
}
|
|
3033
3283
|
async submitTx(signedXdr, opts) {
|
|
3034
3284
|
const buildData = this._currentBuildData();
|
|
3035
3285
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
@@ -3067,14 +3317,22 @@ var PollarClient = class {
|
|
|
3067
3317
|
...resultCode && { details: resultCode, resultCode }
|
|
3068
3318
|
};
|
|
3069
3319
|
}
|
|
3070
|
-
const details = error
|
|
3320
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3071
3321
|
this._setTransactionState({
|
|
3072
3322
|
step: "error",
|
|
3073
3323
|
phase: "submitting",
|
|
3074
3324
|
...buildData && { buildData },
|
|
3075
|
-
...details && { details }
|
|
3325
|
+
...details && { details },
|
|
3326
|
+
...code && { code },
|
|
3327
|
+
...message && { message }
|
|
3076
3328
|
});
|
|
3077
|
-
return {
|
|
3329
|
+
return {
|
|
3330
|
+
status: "error",
|
|
3331
|
+
...outcomeExtra,
|
|
3332
|
+
...details && { details },
|
|
3333
|
+
...code && { code },
|
|
3334
|
+
...message && { message }
|
|
3335
|
+
};
|
|
3078
3336
|
} catch (err) {
|
|
3079
3337
|
const details = err instanceof Error ? err.message : void 0;
|
|
3080
3338
|
this._setTransactionState({
|
|
@@ -3161,14 +3419,22 @@ var PollarClient = class {
|
|
|
3161
3419
|
...resultCode && { details: resultCode, resultCode }
|
|
3162
3420
|
};
|
|
3163
3421
|
}
|
|
3164
|
-
const details = error
|
|
3422
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3165
3423
|
this._setTransactionState({
|
|
3166
3424
|
step: "error",
|
|
3167
3425
|
phase: "signing-submitting",
|
|
3168
3426
|
...buildData && { buildData },
|
|
3169
|
-
...details && { details }
|
|
3427
|
+
...details && { details },
|
|
3428
|
+
...code && { code },
|
|
3429
|
+
...message && { message }
|
|
3170
3430
|
});
|
|
3171
|
-
return {
|
|
3431
|
+
return {
|
|
3432
|
+
status: "error",
|
|
3433
|
+
...outcomeExtra,
|
|
3434
|
+
...details && { details },
|
|
3435
|
+
...code && { code },
|
|
3436
|
+
...message && { message }
|
|
3437
|
+
};
|
|
3172
3438
|
} catch (err) {
|
|
3173
3439
|
const details = err instanceof Error ? err.message : void 0;
|
|
3174
3440
|
this._setTransactionState({
|
|
@@ -3243,13 +3509,15 @@ var PollarClient = class {
|
|
|
3243
3509
|
});
|
|
3244
3510
|
return { status: "error", hash, ...resultCode && { details: resultCode, resultCode } };
|
|
3245
3511
|
}
|
|
3246
|
-
const details = error
|
|
3512
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3247
3513
|
this._setTransactionState({
|
|
3248
3514
|
step: "error",
|
|
3249
3515
|
phase: "building-signing-submitting",
|
|
3250
|
-
...details && { details }
|
|
3516
|
+
...details && { details },
|
|
3517
|
+
...code && { code },
|
|
3518
|
+
...message && { message }
|
|
3251
3519
|
});
|
|
3252
|
-
return { status: "error", ...details && { details } };
|
|
3520
|
+
return { status: "error", ...details && { details }, ...code && { code }, ...message && { message } };
|
|
3253
3521
|
} catch (err) {
|
|
3254
3522
|
const details = err instanceof Error ? err.message : void 0;
|
|
3255
3523
|
this._setTransactionState({
|
|
@@ -3362,9 +3630,22 @@ var PollarClient = class {
|
|
|
3362
3630
|
});
|
|
3363
3631
|
return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
|
|
3364
3632
|
}
|
|
3365
|
-
const details = error
|
|
3366
|
-
this._setTransactionState({
|
|
3367
|
-
|
|
3633
|
+
const { details, code, message } = this._resolveTxApiError(error);
|
|
3634
|
+
this._setTransactionState({
|
|
3635
|
+
step: "error",
|
|
3636
|
+
phase: "submitting",
|
|
3637
|
+
buildData,
|
|
3638
|
+
...details && { details },
|
|
3639
|
+
...code && { code },
|
|
3640
|
+
...message && { message }
|
|
3641
|
+
});
|
|
3642
|
+
return {
|
|
3643
|
+
status: "error",
|
|
3644
|
+
...outcomeExtra,
|
|
3645
|
+
...details && { details },
|
|
3646
|
+
...code && { code },
|
|
3647
|
+
...message && { message }
|
|
3648
|
+
};
|
|
3368
3649
|
} catch (err) {
|
|
3369
3650
|
const details = err instanceof Error ? err.message : void 0;
|
|
3370
3651
|
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
@@ -3442,11 +3723,67 @@ var PollarClient = class {
|
|
|
3442
3723
|
this._loginController = new AbortController();
|
|
3443
3724
|
return this._loginController;
|
|
3444
3725
|
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Build the {@link AuthProviderContext} facade for one login attempt. Wraps
|
|
3728
|
+
* the internal `FlowDeps` so providers get only the curated primitives —
|
|
3729
|
+
* `createSession`, `authenticate`, `exchangeExternalToken`, `startHostedOAuth`
|
|
3730
|
+
* — while storage / wallet-adapter / key-manager internals stay private. All
|
|
3731
|
+
* legs share the same `signal`, so `cancelLogin()` aborts the whole chain.
|
|
3732
|
+
*/
|
|
3733
|
+
_providerContext(signal) {
|
|
3734
|
+
const deps = this._flowDeps(signal);
|
|
3735
|
+
return {
|
|
3736
|
+
signal,
|
|
3737
|
+
api: this._api,
|
|
3738
|
+
basePath: this.basePath,
|
|
3739
|
+
apiKey: this.apiKey,
|
|
3740
|
+
logger: this._log,
|
|
3741
|
+
setAuthState: this._setAuthState.bind(this),
|
|
3742
|
+
createSession: () => createAuthSession(deps),
|
|
3743
|
+
authenticate: (clientSessionId) => authenticate(clientSessionId, deps),
|
|
3744
|
+
requestChallenge: (clientSessionId, walletAddress) => requestWalletChallenge(clientSessionId, walletAddress, deps),
|
|
3745
|
+
exchangeExternalToken: (clientSessionId, body) => this._exchangeExternalToken(clientSessionId, body, signal),
|
|
3746
|
+
startHostedOAuth: (provider) => loginOAuth(provider, {
|
|
3747
|
+
...deps,
|
|
3748
|
+
basePath: this.basePath,
|
|
3749
|
+
apiKey: this.apiKey,
|
|
3750
|
+
openAuthUrl: this._openAuthUrl,
|
|
3751
|
+
redirectUri: this._oauthRedirectUri
|
|
3752
|
+
})
|
|
3753
|
+
};
|
|
3754
|
+
}
|
|
3755
|
+
/**
|
|
3756
|
+
* Generic external-provider exchange leg (`POST /auth/external`). Custom
|
|
3757
|
+
* providers call this (via the context) after their own SDK has authenticated
|
|
3758
|
+
* the user and the wallet has counter-signed the SEP-10 challenge
|
|
3759
|
+
* (`{ provider, walletAddress, signedChallengeXdr }`). On success the session
|
|
3760
|
+
* is marked READY server-side and the provider should then call
|
|
3761
|
+
* `ctx.authenticate(clientSessionId)`. Returns `false` (and sets an error
|
|
3762
|
+
* state) on failure.
|
|
3763
|
+
*/
|
|
3764
|
+
async _exchangeExternalToken(clientSessionId, body, signal) {
|
|
3765
|
+
const { data, error } = await this._api.POST("/auth/external", {
|
|
3766
|
+
body: { clientSessionId, ...body },
|
|
3767
|
+
signal
|
|
3768
|
+
});
|
|
3769
|
+
if (error || !data?.success) {
|
|
3770
|
+
this._log.error("[PollarClient] External provider authentication failed", { error });
|
|
3771
|
+
this._setAuthState({
|
|
3772
|
+
step: "error",
|
|
3773
|
+
previousStep: this._authState.step,
|
|
3774
|
+
message: "External provider authentication failed",
|
|
3775
|
+
errorCode: AUTH_ERROR_CODES.EXTERNAL_AUTH_FAILED
|
|
3776
|
+
});
|
|
3777
|
+
return false;
|
|
3778
|
+
}
|
|
3779
|
+
return true;
|
|
3780
|
+
}
|
|
3445
3781
|
_flowDeps(signal) {
|
|
3446
3782
|
return {
|
|
3447
3783
|
api: this._api,
|
|
3448
3784
|
logger: this._log,
|
|
3449
3785
|
basePath: this.basePath,
|
|
3786
|
+
networkPassphrase: this._networkPassphrase(),
|
|
3450
3787
|
// SSE status streaming works on web; React Native's `fetch` has no
|
|
3451
3788
|
// readable `response.body`, so those clients poll the non-streaming
|
|
3452
3789
|
// status endpoint instead. `isBrowser` is false in RN and SSR alike.
|
|
@@ -3578,6 +3915,7 @@ var PollarClient = class {
|
|
|
3578
3915
|
async _storeSession(session) {
|
|
3579
3916
|
this._log.info("[PollarClient] Session stored");
|
|
3580
3917
|
const w = session.wallet;
|
|
3918
|
+
const wireProvider = w.provider;
|
|
3581
3919
|
const persisted = {
|
|
3582
3920
|
clientSessionId: session.clientSessionId,
|
|
3583
3921
|
userId: session.userId ?? null,
|
|
@@ -3591,6 +3929,7 @@ var PollarClient = class {
|
|
|
3591
3929
|
// persisted session speak one vocabulary while the wire stays compatible.
|
|
3592
3930
|
wallet: {
|
|
3593
3931
|
type: w.type === "custodial" ? "internal" : w.type,
|
|
3932
|
+
...wireProvider ? { provider: wireProvider } : {},
|
|
3594
3933
|
address: w.address ?? w.publicKey ?? null,
|
|
3595
3934
|
...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
|
|
3596
3935
|
...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
|