@pollar/core 0.8.2 → 0.9.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/adapters/react-native-appstate.d.mts +1 -1
- package/dist/adapters/react-native-appstate.d.ts +1 -1
- package/dist/index.d.mts +947 -100
- package/dist/index.d.ts +947 -100
- package/dist/index.js +469 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +469 -58
- 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 +615 -207
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +615 -208
- package/dist/index.rn.mjs.map +1 -1
- package/dist/{types-84G_htcn.d.mts → types-Dyky8g0p.d.mts} +5 -12
- package/dist/{types-84G_htcn.d.ts → types-Dyky8g0p.d.ts} +5 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -186,17 +186,6 @@ function defaultKeyManager(storage, apiKey) {
|
|
|
186
186
|
}
|
|
187
187
|
return _factory(storage, apiKey);
|
|
188
188
|
}
|
|
189
|
-
async function sha256(data) {
|
|
190
|
-
return sha2.sha256(data);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// src/lib/api-key-hash.ts
|
|
194
|
-
async function hashApiKey(apiKey) {
|
|
195
|
-
const digest = await sha256(new TextEncoder().encode(apiKey));
|
|
196
|
-
let hex = "";
|
|
197
|
-
for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
|
|
198
|
-
return hex;
|
|
199
|
-
}
|
|
200
189
|
|
|
201
190
|
// src/lib/base64url.ts
|
|
202
191
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
@@ -234,6 +223,17 @@ function base64urlEncode(bytes) {
|
|
|
234
223
|
function base64urlEncodeString(s) {
|
|
235
224
|
return base64urlEncode(new TextEncoder().encode(s));
|
|
236
225
|
}
|
|
226
|
+
async function sha256(data) {
|
|
227
|
+
return sha2.sha256(data);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/lib/api-key-hash.ts
|
|
231
|
+
async function hashApiKey(apiKey) {
|
|
232
|
+
const digest = await sha256(new TextEncoder().encode(apiKey));
|
|
233
|
+
let hex = "";
|
|
234
|
+
for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
|
|
235
|
+
return hex;
|
|
236
|
+
}
|
|
237
237
|
|
|
238
238
|
// src/keys/thumbprint.ts
|
|
239
239
|
async function computeJwkThumbprint(jwk) {
|
|
@@ -244,11 +244,14 @@ async function computeJwkThumbprint(jwk) {
|
|
|
244
244
|
const digest = await sha256(new TextEncoder().encode(canonical));
|
|
245
245
|
return base64urlEncode(digest);
|
|
246
246
|
}
|
|
247
|
+
function toBase64url(value) {
|
|
248
|
+
return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/[^A-Za-z0-9_-]/g, "");
|
|
249
|
+
}
|
|
247
250
|
function canonicalEcJwk(jwk) {
|
|
248
251
|
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
|
|
249
252
|
throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
|
|
250
253
|
}
|
|
251
|
-
return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
254
|
+
return { kty: "EC", crv: "P-256", x: toBase64url(jwk.x), y: toBase64url(jwk.y) };
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
// src/keys/web-crypto.ts
|
|
@@ -262,7 +265,7 @@ function openDb() {
|
|
|
262
265
|
return;
|
|
263
266
|
}
|
|
264
267
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
265
|
-
req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
|
|
268
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB open failed"));
|
|
266
269
|
req.onsuccess = () => resolve(req.result);
|
|
267
270
|
req.onupgradeneeded = () => {
|
|
268
271
|
const db = req.result;
|
|
@@ -275,7 +278,7 @@ function openDb() {
|
|
|
275
278
|
function awaitTx(req) {
|
|
276
279
|
return new Promise((resolve, reject) => {
|
|
277
280
|
req.onsuccess = () => resolve(req.result);
|
|
278
|
-
req.onerror = () => reject(req.error ?? new Error("IDB request failed"));
|
|
281
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB request failed"));
|
|
279
282
|
});
|
|
280
283
|
}
|
|
281
284
|
async function dbGet(key) {
|
|
@@ -370,10 +373,39 @@ var WebCryptoKeyManager = class {
|
|
|
370
373
|
}
|
|
371
374
|
}
|
|
372
375
|
this.keyPair = pair;
|
|
373
|
-
|
|
374
|
-
this.publicJwk = canonicalEcJwk(exported);
|
|
376
|
+
this.publicJwk = await this._exportPublicJwk(pair.publicKey);
|
|
375
377
|
this.thumbprint = await computeJwkThumbprint(this.publicJwk);
|
|
376
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Derive the public JWK from a `CryptoKey`. Prefers the `'raw'` export (the
|
|
381
|
+
* 65-byte uncompressed point `0x04 || X(32) || Y(32)`) and base64url-encodes
|
|
382
|
+
* the coordinates ourselves — that sidesteps polyfills whose `exportKey('jwk')`
|
|
383
|
+
* emits non-base64url `x`/`y` (standard base64, `=` padding, or — as seen with
|
|
384
|
+
* `react-native-quick-crypto` — a stray `.`). Real browsers and most polyfills
|
|
385
|
+
* support `'raw'` for public EC keys.
|
|
386
|
+
*
|
|
387
|
+
* Falls back to the `'jwk'` export (normalized via `canonicalEcJwk`) if `'raw'`
|
|
388
|
+
* is unsupported or returns an unexpected shape, so this can't regress on a
|
|
389
|
+
* runtime that only implements the JWK path. Both routes yield identical
|
|
390
|
+
* coordinate bytes, so the `cnf.jkt` thumbprint is unchanged either way.
|
|
391
|
+
*/
|
|
392
|
+
async _exportPublicJwk(publicKey) {
|
|
393
|
+
try {
|
|
394
|
+
const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey("raw", publicKey));
|
|
395
|
+
if (raw.length !== 65 || raw[0] !== 4) {
|
|
396
|
+
throw new Error(`[PollarClient:keys] Unexpected raw EC point (len=${raw.length}, tag=${raw[0]})`);
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
kty: "EC",
|
|
400
|
+
crv: "P-256",
|
|
401
|
+
x: base64urlEncode(raw.slice(1, 33)),
|
|
402
|
+
y: base64urlEncode(raw.slice(33, 65))
|
|
403
|
+
};
|
|
404
|
+
} catch {
|
|
405
|
+
const jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
|
|
406
|
+
return canonicalEcJwk(jwk);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
377
409
|
async reset() {
|
|
378
410
|
try {
|
|
379
411
|
if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
|
|
@@ -1131,6 +1163,9 @@ function defaultStorage(options = {}) {
|
|
|
1131
1163
|
return createLocalStorageAdapter(options);
|
|
1132
1164
|
}
|
|
1133
1165
|
|
|
1166
|
+
// src/version.ts
|
|
1167
|
+
var POLLAR_CORE_VERSION = "0.9.0-rc.0" ;
|
|
1168
|
+
|
|
1134
1169
|
// src/visibility/noop.ts
|
|
1135
1170
|
function createNoopVisibilityProvider() {
|
|
1136
1171
|
return {
|
|
@@ -1195,6 +1230,7 @@ var AUTH_ERROR_CODES = {
|
|
|
1195
1230
|
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1196
1231
|
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1197
1232
|
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1233
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1198
1234
|
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1199
1235
|
};
|
|
1200
1236
|
var PollarFlowError = class extends Error {
|
|
@@ -1240,7 +1276,7 @@ var FreighterAdapter = class {
|
|
|
1240
1276
|
if (!userInfo?.publicKey) {
|
|
1241
1277
|
throw new Error("Failed to get user information from Freighter");
|
|
1242
1278
|
}
|
|
1243
|
-
return { address: userInfo.publicKey
|
|
1279
|
+
return { address: userInfo.publicKey };
|
|
1244
1280
|
}
|
|
1245
1281
|
async disconnect() {
|
|
1246
1282
|
}
|
|
@@ -1278,6 +1314,19 @@ var FreighterAdapter = class {
|
|
|
1278
1314
|
};
|
|
1279
1315
|
|
|
1280
1316
|
// src/wallets/AlbedoAdapter.ts
|
|
1317
|
+
var PUBLIC_PASSPHRASE = "Public Global Stellar Network ; September 2015";
|
|
1318
|
+
var TESTNET_PASSPHRASE = "Test SDF Network ; September 2015";
|
|
1319
|
+
function albedoNetwork(options, fallback) {
|
|
1320
|
+
switch (options?.networkPassphrase) {
|
|
1321
|
+
case PUBLIC_PASSPHRASE:
|
|
1322
|
+
return "public";
|
|
1323
|
+
case TESTNET_PASSPHRASE:
|
|
1324
|
+
return "testnet";
|
|
1325
|
+
}
|
|
1326
|
+
if (options?.network === "public" || options?.network === "mainnet") return "public";
|
|
1327
|
+
if (options?.network === "testnet") return "testnet";
|
|
1328
|
+
return fallback;
|
|
1329
|
+
}
|
|
1281
1330
|
function openAlbedoPopup(url) {
|
|
1282
1331
|
const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
|
|
1283
1332
|
if (!popup) {
|
|
@@ -1316,7 +1365,13 @@ function waitForAlbedoResult() {
|
|
|
1316
1365
|
});
|
|
1317
1366
|
}
|
|
1318
1367
|
var AlbedoAdapter = class {
|
|
1319
|
-
|
|
1368
|
+
/**
|
|
1369
|
+
* Network used for `connect` and `signAuthEntry` (which carry no per-call
|
|
1370
|
+
* network) and as the fallback for `signTransaction`. Defaults to `'testnet'`
|
|
1371
|
+
* to preserve the previous behavior when constructed with no argument.
|
|
1372
|
+
*/
|
|
1373
|
+
constructor(network = "testnet") {
|
|
1374
|
+
this.network = network;
|
|
1320
1375
|
this.type = "albedo" /* ALBEDO */;
|
|
1321
1376
|
}
|
|
1322
1377
|
async isAvailable() {
|
|
@@ -1326,7 +1381,7 @@ var AlbedoAdapter = class {
|
|
|
1326
1381
|
const url = new URL("https://albedo.link");
|
|
1327
1382
|
url.searchParams.set("intent", "public-key");
|
|
1328
1383
|
url.searchParams.set("app_name", "Pollar");
|
|
1329
|
-
url.searchParams.set("network",
|
|
1384
|
+
url.searchParams.set("network", this.network);
|
|
1330
1385
|
url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
|
|
1331
1386
|
url.searchParams.set("origin", window.location.origin);
|
|
1332
1387
|
openAlbedoPopup(url.toString());
|
|
@@ -1334,7 +1389,7 @@ var AlbedoAdapter = class {
|
|
|
1334
1389
|
if (!result.pubkey) {
|
|
1335
1390
|
throw new Error("Albedo connection rejected");
|
|
1336
1391
|
}
|
|
1337
|
-
return { address: result.pubkey
|
|
1392
|
+
return { address: result.pubkey };
|
|
1338
1393
|
}
|
|
1339
1394
|
async disconnect() {
|
|
1340
1395
|
}
|
|
@@ -1344,12 +1399,12 @@ var AlbedoAdapter = class {
|
|
|
1344
1399
|
async getNetwork() {
|
|
1345
1400
|
throw new Error("Albedo does not expose network");
|
|
1346
1401
|
}
|
|
1347
|
-
async signTransaction(xdr,
|
|
1402
|
+
async signTransaction(xdr, options) {
|
|
1348
1403
|
const url = new URL("https://albedo.link");
|
|
1349
1404
|
url.searchParams.set("intent", "tx");
|
|
1350
1405
|
url.searchParams.set("xdr", xdr);
|
|
1351
1406
|
url.searchParams.set("app_name", "Pollar");
|
|
1352
|
-
url.searchParams.set("network",
|
|
1407
|
+
url.searchParams.set("network", albedoNetwork(options, this.network));
|
|
1353
1408
|
url.searchParams.set("callback", window.location.href);
|
|
1354
1409
|
url.searchParams.set("origin", window.location.origin);
|
|
1355
1410
|
window.location.href = url.toString();
|
|
@@ -1362,7 +1417,7 @@ var AlbedoAdapter = class {
|
|
|
1362
1417
|
url.searchParams.set("intent", "sign-auth-entry");
|
|
1363
1418
|
url.searchParams.set("xdr", entryXdr);
|
|
1364
1419
|
url.searchParams.set("app_name", "Pollar");
|
|
1365
|
-
url.searchParams.set("network",
|
|
1420
|
+
url.searchParams.set("network", this.network);
|
|
1366
1421
|
url.searchParams.set("callback", window.location.href);
|
|
1367
1422
|
url.searchParams.set("origin", window.location.origin);
|
|
1368
1423
|
window.location.href = url.toString();
|
|
@@ -1449,8 +1504,12 @@ function isValidSession(value) {
|
|
|
1449
1504
|
return false;
|
|
1450
1505
|
}
|
|
1451
1506
|
const w = wallet;
|
|
1452
|
-
if (w["
|
|
1453
|
-
console.warn("[PollarClient:session] Invalid session \u2014 wallet.
|
|
1507
|
+
if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
|
|
1508
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
|
|
1509
|
+
return false;
|
|
1510
|
+
}
|
|
1511
|
+
if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
|
|
1512
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
|
|
1454
1513
|
return false;
|
|
1455
1514
|
}
|
|
1456
1515
|
if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
|
|
@@ -1461,6 +1520,10 @@ function isValidSession(value) {
|
|
|
1461
1520
|
console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
|
|
1462
1521
|
return false;
|
|
1463
1522
|
}
|
|
1523
|
+
if (w["linkedAt"] !== void 0 && (typeof w["linkedAt"] !== "number" || !Number.isFinite(w["linkedAt"]))) {
|
|
1524
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.linkedAt must be a finite number if present");
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1464
1527
|
return true;
|
|
1465
1528
|
}
|
|
1466
1529
|
async function readStorage(storage, apiKeyHash) {
|
|
@@ -1468,6 +1531,12 @@ async function readStorage(storage, apiKeyHash) {
|
|
|
1468
1531
|
if (!raw) return null;
|
|
1469
1532
|
try {
|
|
1470
1533
|
const session = JSON.parse(raw);
|
|
1534
|
+
if (typeof session === "object" && session !== null) {
|
|
1535
|
+
const w = session.wallet;
|
|
1536
|
+
if (w && w["address"] == null && typeof w["publicKey"] === "string") {
|
|
1537
|
+
w["address"] = w["publicKey"];
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1471
1540
|
if (!isValidSession(session)) {
|
|
1472
1541
|
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1473
1542
|
console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
|
|
@@ -1558,7 +1627,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1558
1627
|
}));
|
|
1559
1628
|
} catch (e) {
|
|
1560
1629
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1561
|
-
console.warn(e);
|
|
1630
|
+
console.warn("[PollarClient:stream] session-status request failed; will retry", e);
|
|
1562
1631
|
}
|
|
1563
1632
|
if (error || !data) {
|
|
1564
1633
|
await sleep(backoff);
|
|
@@ -1598,7 +1667,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1598
1667
|
} catch (e) {
|
|
1599
1668
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1600
1669
|
if (e instanceof SessionStatusError) throw e;
|
|
1601
|
-
console.warn(e);
|
|
1670
|
+
console.warn("[PollarClient:stream] session-status stream read failed; will retry", e);
|
|
1602
1671
|
} finally {
|
|
1603
1672
|
reader.releaseLock();
|
|
1604
1673
|
}
|
|
@@ -1626,7 +1695,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
|
|
|
1626
1695
|
envelope = await response.json().catch(() => null);
|
|
1627
1696
|
} catch (e) {
|
|
1628
1697
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1629
|
-
console.warn(e);
|
|
1698
|
+
console.warn("[PollarClient:stream] session-status poll failed; will retry", e);
|
|
1630
1699
|
}
|
|
1631
1700
|
if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
|
|
1632
1701
|
throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
|
|
@@ -1830,6 +1899,55 @@ async function loginOAuth(provider, deps) {
|
|
|
1830
1899
|
await authenticate(clientSessionId, deps);
|
|
1831
1900
|
}
|
|
1832
1901
|
|
|
1902
|
+
// src/client/auth/passkeyFlow.ts
|
|
1903
|
+
async function loginSmartWallet(deps) {
|
|
1904
|
+
const { api, signal, setAuthState, passkey } = deps;
|
|
1905
|
+
if (!passkey) {
|
|
1906
|
+
setAuthState({
|
|
1907
|
+
step: "error",
|
|
1908
|
+
previousStep: "creating_session",
|
|
1909
|
+
message: "Passkey support is not configured",
|
|
1910
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1911
|
+
});
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
const clientSessionId = await createAuthSession(deps);
|
|
1915
|
+
if (!clientSessionId) return;
|
|
1916
|
+
try {
|
|
1917
|
+
const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
|
|
1918
|
+
body: { clientSessionId },
|
|
1919
|
+
signal
|
|
1920
|
+
});
|
|
1921
|
+
const challenge = challengeData?.content?.challenge;
|
|
1922
|
+
if (!challengeData?.success || !challenge) {
|
|
1923
|
+
return failPasskey(setAuthState, "Failed to start passkey");
|
|
1924
|
+
}
|
|
1925
|
+
setAuthState({ step: "creating_passkey" });
|
|
1926
|
+
const ceremony = await passkey({ challenge });
|
|
1927
|
+
const response = ceremony.response;
|
|
1928
|
+
if (ceremony.kind === "register") {
|
|
1929
|
+
setAuthState({ step: "deploying_smart_account" });
|
|
1930
|
+
const { data } = await api.POST("/auth/passkey/register", {
|
|
1931
|
+
body: { clientSessionId, response },
|
|
1932
|
+
signal
|
|
1933
|
+
});
|
|
1934
|
+
if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
|
|
1935
|
+
} else {
|
|
1936
|
+
const { data } = await api.POST("/auth/passkey/login", {
|
|
1937
|
+
body: { clientSessionId, response },
|
|
1938
|
+
signal
|
|
1939
|
+
});
|
|
1940
|
+
if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
|
|
1941
|
+
}
|
|
1942
|
+
} catch {
|
|
1943
|
+
return failPasskey(setAuthState, "Passkey login failed");
|
|
1944
|
+
}
|
|
1945
|
+
await authenticate(clientSessionId, deps);
|
|
1946
|
+
}
|
|
1947
|
+
function failPasskey(setAuthState, message) {
|
|
1948
|
+
setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1833
1951
|
// src/client/auth/walletFlow.ts
|
|
1834
1952
|
function withSignal(promise, signal) {
|
|
1835
1953
|
return Promise.race([
|
|
@@ -1856,12 +1974,12 @@ async function loginWallet(type, deps) {
|
|
|
1856
1974
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
1857
1975
|
return;
|
|
1858
1976
|
}
|
|
1859
|
-
const {
|
|
1860
|
-
connectedWallet =
|
|
1977
|
+
const { address } = await withSignal(adapter.connect(), signal);
|
|
1978
|
+
connectedWallet = address;
|
|
1861
1979
|
deps.storeWalletAdapter(adapter, type);
|
|
1862
1980
|
setAuthState({ step: "authenticating_wallet" });
|
|
1863
1981
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
|
|
1864
|
-
body: { clientSessionId, walletAddress:
|
|
1982
|
+
body: { clientSessionId, walletAddress: address },
|
|
1865
1983
|
signal
|
|
1866
1984
|
});
|
|
1867
1985
|
if (walletError || !walletData?.success) {
|
|
@@ -1926,8 +2044,12 @@ var PollarClient = class {
|
|
|
1926
2044
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
1927
2045
|
this._txHistoryState = { step: "idle" };
|
|
1928
2046
|
this._txHistoryStateListeners = /* @__PURE__ */ new Set();
|
|
2047
|
+
this._sessionsState = { step: "idle" };
|
|
2048
|
+
this._sessionsStateListeners = /* @__PURE__ */ new Set();
|
|
1929
2049
|
this._walletBalanceState = { step: "idle" };
|
|
1930
2050
|
this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
|
|
2051
|
+
this._enabledAssetsState = { step: "idle" };
|
|
2052
|
+
this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
|
|
1931
2053
|
this._authState = { step: "idle" };
|
|
1932
2054
|
this._authStateListeners = /* @__PURE__ */ new Set();
|
|
1933
2055
|
this._networkState = { step: "idle" };
|
|
@@ -1943,6 +2065,8 @@ var PollarClient = class {
|
|
|
1943
2065
|
this._storageDegradeListeners = /* @__PURE__ */ new Set();
|
|
1944
2066
|
this._walletAdapter = null;
|
|
1945
2067
|
this._loginController = null;
|
|
2068
|
+
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2069
|
+
this._resumeController = null;
|
|
1946
2070
|
this.apiKey = config.apiKey;
|
|
1947
2071
|
this.id = randomUUID();
|
|
1948
2072
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
@@ -1955,6 +2079,8 @@ var PollarClient = class {
|
|
|
1955
2079
|
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
1956
2080
|
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
1957
2081
|
this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
|
|
2082
|
+
this._passkey = config.passkey ?? null;
|
|
2083
|
+
this._passkeySign = config.passkeySign ?? null;
|
|
1958
2084
|
this._deviceLabel = config.deviceLabel;
|
|
1959
2085
|
this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
|
|
1960
2086
|
this._maxIdleMs = config.maxIdleMs;
|
|
@@ -1968,7 +2094,9 @@ var PollarClient = class {
|
|
|
1968
2094
|
this._initialized = Promise.resolve();
|
|
1969
2095
|
return;
|
|
1970
2096
|
}
|
|
1971
|
-
console.info(
|
|
2097
|
+
console.info(
|
|
2098
|
+
`[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
|
|
2099
|
+
);
|
|
1972
2100
|
this._initialized = this._initialize();
|
|
1973
2101
|
}
|
|
1974
2102
|
/**
|
|
@@ -2006,7 +2134,11 @@ var PollarClient = class {
|
|
|
2006
2134
|
}
|
|
2007
2135
|
await this._restoreSession();
|
|
2008
2136
|
this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
|
|
2009
|
-
if (visible)
|
|
2137
|
+
if (!visible) return;
|
|
2138
|
+
void this._maybeProactiveRefresh();
|
|
2139
|
+
if (this._authState.step === "authenticated" && !this._authState.verified) {
|
|
2140
|
+
void this._resume();
|
|
2141
|
+
}
|
|
2010
2142
|
});
|
|
2011
2143
|
}
|
|
2012
2144
|
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
@@ -2017,6 +2149,8 @@ var PollarClient = class {
|
|
|
2017
2149
|
}
|
|
2018
2150
|
this._loginController?.abort();
|
|
2019
2151
|
this._loginController = null;
|
|
2152
|
+
this._resumeController?.abort();
|
|
2153
|
+
this._resumeController = null;
|
|
2020
2154
|
this._clearRefreshTimer();
|
|
2021
2155
|
if (this._visibilityUnsubscribe) {
|
|
2022
2156
|
this._visibilityUnsubscribe();
|
|
@@ -2031,7 +2165,9 @@ var PollarClient = class {
|
|
|
2031
2165
|
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
2032
2166
|
self._lastRequestAt = Date.now();
|
|
2033
2167
|
await self._initialized;
|
|
2034
|
-
|
|
2168
|
+
const cacheMethod = request.method.toUpperCase();
|
|
2169
|
+
const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
|
|
2170
|
+
if (cacheBodyAllowed && request.body != null) {
|
|
2035
2171
|
try {
|
|
2036
2172
|
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
2037
2173
|
} catch (err) {
|
|
@@ -2118,7 +2254,9 @@ var PollarClient = class {
|
|
|
2118
2254
|
}
|
|
2119
2255
|
}
|
|
2120
2256
|
}
|
|
2121
|
-
const
|
|
2257
|
+
const retryMethod = originalRequest.method.toUpperCase();
|
|
2258
|
+
const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
|
|
2259
|
+
const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
|
|
2122
2260
|
const retried = new Request(originalRequest.url, {
|
|
2123
2261
|
method: originalRequest.method,
|
|
2124
2262
|
headers,
|
|
@@ -2162,19 +2300,26 @@ var PollarClient = class {
|
|
|
2162
2300
|
throw err;
|
|
2163
2301
|
}
|
|
2164
2302
|
if (error || !data) {
|
|
2165
|
-
console.
|
|
2303
|
+
console.error("[PollarClient] /auth/refresh returned error", { error });
|
|
2166
2304
|
await this._clearSession();
|
|
2167
2305
|
throw new Error("Refresh failed");
|
|
2168
2306
|
}
|
|
2169
2307
|
const successData = data;
|
|
2170
2308
|
if (!successData.success || !successData.content?.token) {
|
|
2171
|
-
console.
|
|
2309
|
+
console.error("[PollarClient] /auth/refresh response malformed", {
|
|
2310
|
+
success: successData.success,
|
|
2311
|
+
hasToken: !!successData.content?.token
|
|
2312
|
+
});
|
|
2172
2313
|
await this._clearSession();
|
|
2173
2314
|
throw new Error("Refresh response malformed");
|
|
2174
2315
|
}
|
|
2175
2316
|
const newToken = successData.content.token;
|
|
2176
2317
|
if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
|
|
2177
|
-
console.
|
|
2318
|
+
console.error("[PollarClient] /auth/refresh token shape invalid", {
|
|
2319
|
+
accessToken: typeof newToken.accessToken,
|
|
2320
|
+
refreshToken: typeof newToken.refreshToken,
|
|
2321
|
+
expiresAt: typeof newToken.expiresAt
|
|
2322
|
+
});
|
|
2178
2323
|
await this._clearSession();
|
|
2179
2324
|
throw new Error("Refresh response token shape invalid");
|
|
2180
2325
|
}
|
|
@@ -2368,6 +2513,19 @@ var PollarClient = class {
|
|
|
2368
2513
|
const controller = this._newController();
|
|
2369
2514
|
loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2370
2515
|
}
|
|
2516
|
+
/**
|
|
2517
|
+
* "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
|
|
2518
|
+
* user, creates a sponsored smart-account C-address. Requires the `passkey`
|
|
2519
|
+
* ceremony to be configured (e.g. via `@pollar/react`).
|
|
2520
|
+
*/
|
|
2521
|
+
loginSmartWallet() {
|
|
2522
|
+
if (!isClientRuntime) {
|
|
2523
|
+
warnServerSide("loginSmartWallet");
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
const controller = this._newController();
|
|
2527
|
+
loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2528
|
+
}
|
|
2371
2529
|
// ─── Cancel ───────────────────────────────────────────────────────────────
|
|
2372
2530
|
cancelLogin() {
|
|
2373
2531
|
this._loginController?.abort();
|
|
@@ -2430,6 +2588,29 @@ var PollarClient = class {
|
|
|
2430
2588
|
}
|
|
2431
2589
|
return data.content.sessions;
|
|
2432
2590
|
}
|
|
2591
|
+
getSessionsState() {
|
|
2592
|
+
return this._sessionsState;
|
|
2593
|
+
}
|
|
2594
|
+
onSessionsStateChange(cb) {
|
|
2595
|
+
this._sessionsStateListeners.add(cb);
|
|
2596
|
+
cb(this._sessionsState);
|
|
2597
|
+
return () => this._sessionsStateListeners.delete(cb);
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Fire-and-forget variant of {@link listSessions} that drives the observable
|
|
2601
|
+
* `SessionsState` store instead of returning the array. UI layers subscribe
|
|
2602
|
+
* via `onSessionsStateChange` and stay pure readers — mirrors `fetchTxHistory`.
|
|
2603
|
+
*/
|
|
2604
|
+
async fetchSessions() {
|
|
2605
|
+
this._setSessionsState({ step: "loading" });
|
|
2606
|
+
try {
|
|
2607
|
+
const sessions = await this.listSessions();
|
|
2608
|
+
this._setSessionsState({ step: "loaded", sessions });
|
|
2609
|
+
} catch (err) {
|
|
2610
|
+
const message = err instanceof Error ? err.message : "Failed to load sessions";
|
|
2611
|
+
this._setSessionsState({ step: "error", message });
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2433
2614
|
/**
|
|
2434
2615
|
* Revoke a specific refresh-token family (a single device session). Use
|
|
2435
2616
|
* `listSessions` to enumerate the familyIds. Revoking the current session
|
|
@@ -2506,16 +2687,19 @@ var PollarClient = class {
|
|
|
2506
2687
|
cb(this._walletBalanceState);
|
|
2507
2688
|
return () => this._walletBalanceStateListeners.delete(cb);
|
|
2508
2689
|
}
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2690
|
+
/**
|
|
2691
|
+
* Refreshes the balances of the authenticated user's OWN wallet. The wallet
|
|
2692
|
+
* and network are resolved server-side from the session — no arguments. Drives
|
|
2693
|
+
* `walletBalanceState`. For an arbitrary wallet, use {@link getWalletBalance}.
|
|
2694
|
+
*/
|
|
2695
|
+
async refreshBalance() {
|
|
2696
|
+
if (!this._session?.wallet?.address) {
|
|
2512
2697
|
this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
|
|
2513
2698
|
return;
|
|
2514
2699
|
}
|
|
2515
2700
|
this._setWalletBalanceState({ step: "loading" });
|
|
2516
2701
|
try {
|
|
2517
|
-
const
|
|
2518
|
-
const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
|
|
2702
|
+
const { data, error } = await this._api.GET("/wallet/balance");
|
|
2519
2703
|
if (!error && data?.success && data.content) {
|
|
2520
2704
|
this._setWalletBalanceState({ step: "loaded", data: data.content });
|
|
2521
2705
|
} else {
|
|
@@ -2525,6 +2709,53 @@ var PollarClient = class {
|
|
|
2525
2709
|
this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
|
|
2526
2710
|
}
|
|
2527
2711
|
}
|
|
2712
|
+
/**
|
|
2713
|
+
* General-purpose balance lookup for ANY wallet on ANY network — not scoped
|
|
2714
|
+
* to this application. Enumerates the account's real on-chain holdings via
|
|
2715
|
+
* Horizon (server-side) and returns the data directly (no reactive state).
|
|
2716
|
+
* `network` defaults to the client's current network.
|
|
2717
|
+
*/
|
|
2718
|
+
async getWalletBalance(publicKey, network) {
|
|
2719
|
+
const { data, error } = await this._api.GET("/wallet/{publicKey}/balance", {
|
|
2720
|
+
params: { path: { publicKey }, query: { network: network ?? this.getNetwork() } }
|
|
2721
|
+
});
|
|
2722
|
+
if (error || !data?.success || !data.content) {
|
|
2723
|
+
throw new Error("[PollarClient] Failed to load wallet balance");
|
|
2724
|
+
}
|
|
2725
|
+
return data.content;
|
|
2726
|
+
}
|
|
2727
|
+
// ─── Enabled assets ───────────────────────────────────────────────────────
|
|
2728
|
+
getEnabledAssetsState() {
|
|
2729
|
+
return this._enabledAssetsState;
|
|
2730
|
+
}
|
|
2731
|
+
onEnabledAssetsStateChange(cb) {
|
|
2732
|
+
this._enabledAssetsStateListeners.add(cb);
|
|
2733
|
+
cb(this._enabledAssetsState);
|
|
2734
|
+
return () => this._enabledAssetsStateListeners.delete(cb);
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Loads the application's enabled assets paired with the authenticated
|
|
2738
|
+
* wallet's on-chain trustline state — so the SDK knows which trustlines still
|
|
2739
|
+
* need to be added. Wallet and network are resolved server-side from the
|
|
2740
|
+
* session. Drives `enabledAssetsState`; mirrors {@link refreshBalance}.
|
|
2741
|
+
*/
|
|
2742
|
+
async refreshAssets() {
|
|
2743
|
+
if (!this._session?.wallet?.address) {
|
|
2744
|
+
this._setEnabledAssetsState({ step: "error", message: "No wallet connected" });
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
this._setEnabledAssetsState({ step: "loading" });
|
|
2748
|
+
try {
|
|
2749
|
+
const { data, error } = await this._api.GET("/wallet/assets");
|
|
2750
|
+
if (!error && data?.success && data.content) {
|
|
2751
|
+
this._setEnabledAssetsState({ step: "loaded", data: data.content });
|
|
2752
|
+
} else {
|
|
2753
|
+
this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
|
|
2754
|
+
}
|
|
2755
|
+
} catch {
|
|
2756
|
+
this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2528
2759
|
// ─── Transactions ─────────────────────────────────────────────────────────
|
|
2529
2760
|
/**
|
|
2530
2761
|
* Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
|
|
@@ -2532,14 +2763,14 @@ var PollarClient = class {
|
|
|
2532
2763
|
* inspect the result without subscribing to state changes.
|
|
2533
2764
|
*/
|
|
2534
2765
|
async buildTx(operation, params, options) {
|
|
2535
|
-
if (!this._session?.wallet?.
|
|
2766
|
+
if (!this._session?.wallet?.address) {
|
|
2536
2767
|
const details = "No wallet connected";
|
|
2537
2768
|
this._setTransactionState({ step: "error", phase: "building", details });
|
|
2538
2769
|
return { status: "error", details };
|
|
2539
2770
|
}
|
|
2540
2771
|
const body = {
|
|
2541
2772
|
network: this.getNetwork(),
|
|
2542
|
-
|
|
2773
|
+
address: this._session.wallet.address,
|
|
2543
2774
|
operation,
|
|
2544
2775
|
params,
|
|
2545
2776
|
options: options ?? {}
|
|
@@ -2580,7 +2811,7 @@ var PollarClient = class {
|
|
|
2580
2811
|
const buildData = this._currentBuildData();
|
|
2581
2812
|
this._setTransactionState({ step: "signing", ...buildData && { buildData } });
|
|
2582
2813
|
if (this._walletAdapter) {
|
|
2583
|
-
const accountToSign = this._session?.wallet?.
|
|
2814
|
+
const accountToSign = this._session?.wallet?.address;
|
|
2584
2815
|
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2585
2816
|
try {
|
|
2586
2817
|
const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
|
|
@@ -2601,10 +2832,10 @@ var PollarClient = class {
|
|
|
2601
2832
|
return { status: "error", ...details && { details } };
|
|
2602
2833
|
}
|
|
2603
2834
|
}
|
|
2604
|
-
const
|
|
2835
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2605
2836
|
try {
|
|
2606
2837
|
const { data, error } = await this._api.POST("/tx/sign", {
|
|
2607
|
-
body: { network: this.getNetwork(),
|
|
2838
|
+
body: { network: this.getNetwork(), address, unsignedXdr }
|
|
2608
2839
|
});
|
|
2609
2840
|
if (!error && data?.success && data.content?.signedXdr) {
|
|
2610
2841
|
const { signedXdr, idempotencyKey } = data.content;
|
|
@@ -2657,12 +2888,12 @@ var PollarClient = class {
|
|
|
2657
2888
|
const buildData = this._currentBuildData();
|
|
2658
2889
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
2659
2890
|
this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
|
|
2660
|
-
const
|
|
2891
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2661
2892
|
try {
|
|
2662
2893
|
const { data, error } = await this._api.POST("/tx/submit", {
|
|
2663
2894
|
body: {
|
|
2664
2895
|
network: this.getNetwork(),
|
|
2665
|
-
|
|
2896
|
+
address,
|
|
2666
2897
|
signedXdr,
|
|
2667
2898
|
...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
|
|
2668
2899
|
}
|
|
@@ -2722,6 +2953,19 @@ var PollarClient = class {
|
|
|
2722
2953
|
* `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
|
|
2723
2954
|
*/
|
|
2724
2955
|
async signAndSubmitTx(unsignedXdr) {
|
|
2956
|
+
if (this._session?.wallet?.type === "smart") {
|
|
2957
|
+
const buildData2 = this._currentBuildData();
|
|
2958
|
+
if (!buildData2?.smart) {
|
|
2959
|
+
const details = "no prepared smart transaction; call buildTx first";
|
|
2960
|
+
this._setTransactionState({ step: "error", phase: "signing", details });
|
|
2961
|
+
return { status: "error", details };
|
|
2962
|
+
}
|
|
2963
|
+
return this._signSubmitSmart(buildData2);
|
|
2964
|
+
}
|
|
2965
|
+
if (!unsignedXdr) {
|
|
2966
|
+
this._setTransactionState({ step: "error", phase: "signing", details: "missing unsigned transaction" });
|
|
2967
|
+
return { status: "error", details: "missing unsigned transaction" };
|
|
2968
|
+
}
|
|
2725
2969
|
if (this._walletAdapter) {
|
|
2726
2970
|
const signed = await this.signTx(unsignedXdr);
|
|
2727
2971
|
if (signed.status === "error") {
|
|
@@ -2739,7 +2983,7 @@ var PollarClient = class {
|
|
|
2739
2983
|
this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
|
|
2740
2984
|
const body = {
|
|
2741
2985
|
network: this.getNetwork(),
|
|
2742
|
-
|
|
2986
|
+
address: this._session?.wallet?.address ?? "",
|
|
2743
2987
|
unsignedXdr
|
|
2744
2988
|
};
|
|
2745
2989
|
try {
|
|
@@ -2808,14 +3052,20 @@ var PollarClient = class {
|
|
|
2808
3052
|
* `signTx`, and `submitTx` separately instead.
|
|
2809
3053
|
*/
|
|
2810
3054
|
async buildAndSignAndSubmitTx(operation, params, options) {
|
|
3055
|
+
if (this._session?.wallet?.type === "smart") {
|
|
3056
|
+
return this._runSmartTx(operation, params, options);
|
|
3057
|
+
}
|
|
2811
3058
|
if (this._walletAdapter) {
|
|
2812
3059
|
const built = await this.buildTx(operation, params, options);
|
|
2813
3060
|
if (built.status === "error") {
|
|
2814
3061
|
return { status: "error", ...built.details && { details: built.details } };
|
|
2815
3062
|
}
|
|
3063
|
+
if (!built.buildData.unsignedXdr) {
|
|
3064
|
+
return { status: "error", details: "build returned no unsigned transaction" };
|
|
3065
|
+
}
|
|
2816
3066
|
return this.signAndSubmitTx(built.buildData.unsignedXdr);
|
|
2817
3067
|
}
|
|
2818
|
-
if (!this._session?.wallet?.
|
|
3068
|
+
if (!this._session?.wallet?.address) {
|
|
2819
3069
|
this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
|
|
2820
3070
|
return { status: "error", details: "No wallet connected" };
|
|
2821
3071
|
}
|
|
@@ -2824,7 +3074,7 @@ var PollarClient = class {
|
|
|
2824
3074
|
const { data, error } = await this._api.POST("/tx/build-sign-submit", {
|
|
2825
3075
|
body: {
|
|
2826
3076
|
network: this.getNetwork(),
|
|
2827
|
-
|
|
3077
|
+
address: this._session.wallet.address,
|
|
2828
3078
|
operation,
|
|
2829
3079
|
params,
|
|
2830
3080
|
options: options ?? {}
|
|
@@ -2868,6 +3118,113 @@ var PollarClient = class {
|
|
|
2868
3118
|
async runTx(operation, params, options) {
|
|
2869
3119
|
return this.buildAndSignAndSubmitTx(operation, params, options);
|
|
2870
3120
|
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Smart-wallet (passkey / C-address) transaction: build (server prepares the
|
|
3123
|
+
* SAC transfer + returns the auth digest) → sign the digest with the passkey
|
|
3124
|
+
* → submit (server assembles the signed auth entry and broadcasts; the
|
|
3125
|
+
* sponsor pays the fee). State machine: building → built → signing →
|
|
3126
|
+
* submitting → success.
|
|
3127
|
+
*/
|
|
3128
|
+
async _runSmartTx(operation, params, options) {
|
|
3129
|
+
const address = this._session?.wallet?.address;
|
|
3130
|
+
if (!address) {
|
|
3131
|
+
this._setTransactionState({ step: "error", phase: "building", details: "No wallet connected" });
|
|
3132
|
+
return { status: "error", details: "No wallet connected" };
|
|
3133
|
+
}
|
|
3134
|
+
if (!this._passkeySign) {
|
|
3135
|
+
const details = "Passkey signer not configured";
|
|
3136
|
+
this._setTransactionState({ step: "error", phase: "signing", details });
|
|
3137
|
+
return { status: "error", details };
|
|
3138
|
+
}
|
|
3139
|
+
this._setTransactionState({ step: "building" });
|
|
3140
|
+
let buildData;
|
|
3141
|
+
try {
|
|
3142
|
+
const body = {
|
|
3143
|
+
network: this.getNetwork(),
|
|
3144
|
+
address,
|
|
3145
|
+
operation,
|
|
3146
|
+
params,
|
|
3147
|
+
options: options ?? {}
|
|
3148
|
+
};
|
|
3149
|
+
const { data, error } = await this._api.POST("/tx/build", { body });
|
|
3150
|
+
if (error || !data?.success || !data.content?.smart) {
|
|
3151
|
+
const details = error?.details ?? "Failed to build transaction";
|
|
3152
|
+
this._setTransactionState({ step: "error", phase: "building", details });
|
|
3153
|
+
return { status: "error", details };
|
|
3154
|
+
}
|
|
3155
|
+
buildData = data.content;
|
|
3156
|
+
} catch (err) {
|
|
3157
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3158
|
+
this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
|
|
3159
|
+
return { status: "error", ...details && { details } };
|
|
3160
|
+
}
|
|
3161
|
+
this._setTransactionState({ step: "built", buildData });
|
|
3162
|
+
return this._signSubmitSmart(buildData);
|
|
3163
|
+
}
|
|
3164
|
+
/**
|
|
3165
|
+
* Steps 2–3 of the smart-wallet flow: sign the prepared auth digest with the
|
|
3166
|
+
* passkey, then submit. Shared by `_runSmartTx` (atomic) and `signAndSubmitTx`
|
|
3167
|
+
* (split flow, when a smart build is already on the state machine).
|
|
3168
|
+
*/
|
|
3169
|
+
async _signSubmitSmart(buildData) {
|
|
3170
|
+
const address = this._session?.wallet?.address;
|
|
3171
|
+
const smart = buildData.smart;
|
|
3172
|
+
if (!address || !smart) {
|
|
3173
|
+
const details = "no prepared smart transaction";
|
|
3174
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, details });
|
|
3175
|
+
return { status: "error", buildData, details };
|
|
3176
|
+
}
|
|
3177
|
+
if (!this._passkeySign) {
|
|
3178
|
+
const details = "Passkey signer not configured";
|
|
3179
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, details });
|
|
3180
|
+
return { status: "error", buildData, details };
|
|
3181
|
+
}
|
|
3182
|
+
this._setTransactionState({ step: "signing", buildData });
|
|
3183
|
+
let assertion;
|
|
3184
|
+
try {
|
|
3185
|
+
assertion = await this._passkeySign({ credentialId: smart.credentialId, challenge: smart.digest });
|
|
3186
|
+
} catch (err) {
|
|
3187
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3188
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, ...details && { details } });
|
|
3189
|
+
return { status: "error", buildData, ...details && { details } };
|
|
3190
|
+
}
|
|
3191
|
+
this._setTransactionState({ step: "submitting", buildData });
|
|
3192
|
+
const outcomeExtra = { buildData };
|
|
3193
|
+
try {
|
|
3194
|
+
const { data, error } = await this._api.POST("/tx/submit", {
|
|
3195
|
+
body: {
|
|
3196
|
+
network: this.getNetwork(),
|
|
3197
|
+
address,
|
|
3198
|
+
smart: { entryXdr: smart.entryXdr, funcXdr: smart.funcXdr, assertion }
|
|
3199
|
+
}
|
|
3200
|
+
});
|
|
3201
|
+
if (!error && data?.success && data.content) {
|
|
3202
|
+
const { hash, status: backendStatus, resultCode } = data.content;
|
|
3203
|
+
if (backendStatus === "SUCCESS") {
|
|
3204
|
+
this._setTransactionState({ step: "success", hash, buildData });
|
|
3205
|
+
return { status: "success", hash, ...outcomeExtra };
|
|
3206
|
+
}
|
|
3207
|
+
if (backendStatus === "PENDING") {
|
|
3208
|
+
this._setTransactionState({ step: "submitted", hash, buildData });
|
|
3209
|
+
return { status: "pending", hash, ...outcomeExtra };
|
|
3210
|
+
}
|
|
3211
|
+
this._setTransactionState({
|
|
3212
|
+
step: "error",
|
|
3213
|
+
phase: "submitting",
|
|
3214
|
+
buildData,
|
|
3215
|
+
...resultCode && { details: resultCode }
|
|
3216
|
+
});
|
|
3217
|
+
return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
|
|
3218
|
+
}
|
|
3219
|
+
const details = error?.details;
|
|
3220
|
+
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
3221
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
3222
|
+
} catch (err) {
|
|
3223
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3224
|
+
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
3225
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
2871
3228
|
// ─── App config ───────────────────────────────────────────────────────────
|
|
2872
3229
|
async getAppConfig() {
|
|
2873
3230
|
try {
|
|
@@ -2921,10 +3278,18 @@ var PollarClient = class {
|
|
|
2921
3278
|
this._txHistoryState = next;
|
|
2922
3279
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
2923
3280
|
}
|
|
3281
|
+
_setSessionsState(next) {
|
|
3282
|
+
this._sessionsState = next;
|
|
3283
|
+
for (const cb of this._sessionsStateListeners) cb(next);
|
|
3284
|
+
}
|
|
2924
3285
|
_setWalletBalanceState(next) {
|
|
2925
3286
|
this._walletBalanceState = next;
|
|
2926
3287
|
for (const cb of this._walletBalanceStateListeners) cb(next);
|
|
2927
3288
|
}
|
|
3289
|
+
_setEnabledAssetsState(next) {
|
|
3290
|
+
this._enabledAssetsState = next;
|
|
3291
|
+
for (const cb of this._enabledAssetsStateListeners) cb(next);
|
|
3292
|
+
}
|
|
2928
3293
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
2929
3294
|
_newController() {
|
|
2930
3295
|
this._loginController?.abort();
|
|
@@ -2949,6 +3314,7 @@ var PollarClient = class {
|
|
|
2949
3314
|
this._walletAdapter = adapter;
|
|
2950
3315
|
await writeWalletType(this._storage, this.apiKeyHash, id);
|
|
2951
3316
|
},
|
|
3317
|
+
...this._passkey ? { passkey: this._passkey } : {},
|
|
2952
3318
|
...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
|
|
2953
3319
|
};
|
|
2954
3320
|
}
|
|
@@ -2978,7 +3344,7 @@ var PollarClient = class {
|
|
|
2978
3344
|
}
|
|
2979
3345
|
}
|
|
2980
3346
|
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
2981
|
-
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
3347
|
+
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
|
|
2982
3348
|
throw new Error(
|
|
2983
3349
|
`[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
|
|
2984
3350
|
);
|
|
@@ -3019,21 +3385,66 @@ var PollarClient = class {
|
|
|
3019
3385
|
}
|
|
3020
3386
|
}
|
|
3021
3387
|
console.info("[PollarClient] Session restored from storage");
|
|
3022
|
-
this._setAuthState({ step: "authenticated", session: this._session });
|
|
3388
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: false });
|
|
3023
3389
|
this._scheduleNextRefresh();
|
|
3390
|
+
void this._resume();
|
|
3024
3391
|
} else {
|
|
3025
3392
|
console.info("[PollarClient] No session in storage");
|
|
3026
3393
|
}
|
|
3027
3394
|
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Validate the restored session against the server and repopulate the
|
|
3397
|
+
* in-memory profile (PII is never persisted, so it's null after a cold
|
|
3398
|
+
* reload). Goes through the normal authed client, so it coalesces with any
|
|
3399
|
+
* in-flight refresh (onRequest awaits `_refreshPromise`) and, being a GET,
|
|
3400
|
+
* is auto-retried after a 401-triggered refresh.
|
|
3401
|
+
*
|
|
3402
|
+
* - 200 → store profile, mark the session `verified`.
|
|
3403
|
+
* - 401 → the refresh-on-401 path already ran; if the family was
|
|
3404
|
+
* revoked, refresh failed and `_clearSession()` took us to
|
|
3405
|
+
* idle. Nothing to do here — don't double-handle.
|
|
3406
|
+
* - network error → stay optimistic (do NOT log out); revalidated later on
|
|
3407
|
+
* `visibilitychange` or first use.
|
|
3408
|
+
*/
|
|
3409
|
+
async _resume() {
|
|
3410
|
+
if (!this._session) return;
|
|
3411
|
+
this._resumeController?.abort();
|
|
3412
|
+
const controller = new AbortController();
|
|
3413
|
+
this._resumeController = controller;
|
|
3414
|
+
try {
|
|
3415
|
+
const { data, error } = await this._api.GET("/auth/session/resume", { signal: controller.signal });
|
|
3416
|
+
if (error || !data) return;
|
|
3417
|
+
const content = data.content;
|
|
3418
|
+
if (!content || !this._session) return;
|
|
3419
|
+
this._profile = { ...content };
|
|
3420
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: true });
|
|
3421
|
+
} catch (err) {
|
|
3422
|
+
if (err?.name === "AbortError") return;
|
|
3423
|
+
console.warn("[PollarClient] resume failed (network); will retry", err);
|
|
3424
|
+
} finally {
|
|
3425
|
+
if (this._resumeController === controller) this._resumeController = null;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3028
3428
|
async _storeSession(session) {
|
|
3029
3429
|
console.info("[PollarClient] Session stored");
|
|
3430
|
+
const w = session.wallet;
|
|
3030
3431
|
const persisted = {
|
|
3031
3432
|
clientSessionId: session.clientSessionId,
|
|
3032
3433
|
userId: session.userId ?? null,
|
|
3033
3434
|
status: session.status,
|
|
3034
3435
|
token: session.token,
|
|
3035
3436
|
user: session.user,
|
|
3036
|
-
|
|
3437
|
+
// The wire response still carries the legacy `publicKey` alias (kept for
|
|
3438
|
+
// older SDKs); the persisted session standardizes on `address` only.
|
|
3439
|
+
wallet: {
|
|
3440
|
+
type: w.type,
|
|
3441
|
+
address: w.address ?? w.publicKey ?? null,
|
|
3442
|
+
...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
|
|
3443
|
+
...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
|
|
3444
|
+
...w.linkedAt !== void 0 ? { linkedAt: w.linkedAt } : {},
|
|
3445
|
+
...w.network !== void 0 ? { network: w.network } : {},
|
|
3446
|
+
...w.deployTxHash !== void 0 ? { deployTxHash: w.deployTxHash } : {}
|
|
3447
|
+
}
|
|
3037
3448
|
};
|
|
3038
3449
|
this._session = persisted;
|
|
3039
3450
|
if (session.data) {
|
|
@@ -3046,7 +3457,7 @@ var PollarClient = class {
|
|
|
3046
3457
|
};
|
|
3047
3458
|
}
|
|
3048
3459
|
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
3049
|
-
this._setAuthState({ step: "authenticated", session: persisted });
|
|
3460
|
+
this._setAuthState({ step: "authenticated", session: persisted, verified: true });
|
|
3050
3461
|
this._scheduleNextRefresh();
|
|
3051
3462
|
}
|
|
3052
3463
|
async _clearSession() {
|
|
@@ -3132,6 +3543,7 @@ _setDefaultKeyManagerFactory((_storage, apiKey) => new WebCryptoKeyManager(apiKe
|
|
|
3132
3543
|
exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
|
|
3133
3544
|
exports.AlbedoAdapter = AlbedoAdapter;
|
|
3134
3545
|
exports.FreighterAdapter = FreighterAdapter;
|
|
3546
|
+
exports.POLLAR_CORE_VERSION = POLLAR_CORE_VERSION;
|
|
3135
3547
|
exports.PollarClient = PollarClient;
|
|
3136
3548
|
exports.StellarClient = StellarClient;
|
|
3137
3549
|
exports.WalletType = WalletType;
|