@pollar/core 0.8.2 → 0.9.0-rc.1

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.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) {
@@ -338,7 +341,6 @@ var WebCryptoKeyManager = class {
338
341
  if (this.keyPair) return;
339
342
  if (!this._initPromise) {
340
343
  this._initPromise = this._doInit().catch((err) => {
341
- console.error("[PollarClient:keys] WebCryptoKeyManager init failed", err);
342
344
  this._initPromise = null;
343
345
  throw err;
344
346
  });
@@ -370,10 +372,39 @@ var WebCryptoKeyManager = class {
370
372
  }
371
373
  }
372
374
  this.keyPair = pair;
373
- const exported = await globalThis.crypto.subtle.exportKey("jwk", pair.publicKey);
374
- this.publicJwk = canonicalEcJwk(exported);
375
+ this.publicJwk = await this._exportPublicJwk(pair.publicKey);
375
376
  this.thumbprint = await computeJwkThumbprint(this.publicJwk);
376
377
  }
378
+ /**
379
+ * Derive the public JWK from a `CryptoKey`. Prefers the `'raw'` export (the
380
+ * 65-byte uncompressed point `0x04 || X(32) || Y(32)`) and base64url-encodes
381
+ * the coordinates ourselves — that sidesteps polyfills whose `exportKey('jwk')`
382
+ * emits non-base64url `x`/`y` (standard base64, `=` padding, or — as seen with
383
+ * `react-native-quick-crypto` — a stray `.`). Real browsers and most polyfills
384
+ * support `'raw'` for public EC keys.
385
+ *
386
+ * Falls back to the `'jwk'` export (normalized via `canonicalEcJwk`) if `'raw'`
387
+ * is unsupported or returns an unexpected shape, so this can't regress on a
388
+ * runtime that only implements the JWK path. Both routes yield identical
389
+ * coordinate bytes, so the `cnf.jkt` thumbprint is unchanged either way.
390
+ */
391
+ async _exportPublicJwk(publicKey) {
392
+ try {
393
+ const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey("raw", publicKey));
394
+ if (raw.length !== 65 || raw[0] !== 4) {
395
+ throw new Error(`[PollarClient:keys] Unexpected raw EC point (len=${raw.length}, tag=${raw[0]})`);
396
+ }
397
+ return {
398
+ kty: "EC",
399
+ crv: "P-256",
400
+ x: base64urlEncode(raw.slice(1, 33)),
401
+ y: base64urlEncode(raw.slice(33, 65))
402
+ };
403
+ } catch {
404
+ const jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
405
+ return canonicalEcJwk(jwk);
406
+ }
407
+ }
377
408
  async reset() {
378
409
  try {
379
410
  if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
@@ -1044,6 +1075,16 @@ function normalizeHtu(rawUrl) {
1044
1075
  return `${scheme}//${host}${portPart}${url.pathname}`;
1045
1076
  }
1046
1077
 
1078
+ // src/lib/logger.ts
1079
+ var RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
1080
+ function createLogger(level = "info", sink = console) {
1081
+ const threshold = RANK[level];
1082
+ const gate = (lvl) => (...args) => {
1083
+ if (threshold >= RANK[lvl]) sink[lvl](...args);
1084
+ };
1085
+ return { error: gate("error"), warn: gate("warn"), info: gate("info"), debug: gate("debug") };
1086
+ }
1087
+
1047
1088
  // src/storage/web.ts
1048
1089
  var LOG_PREFIX = "[PollarClient:storage]";
1049
1090
  function createMemoryAdapter() {
@@ -1067,7 +1108,7 @@ function createLocalStorageAdapter(options = {}) {
1067
1108
  function degrade(reason, error) {
1068
1109
  if (degraded) return;
1069
1110
  degraded = true;
1070
- console.warn(`${LOG_PREFIX} localStorage unavailable (${reason}); degrading to in-memory storage`);
1111
+ (options.logger ?? console).warn(`${LOG_PREFIX} localStorage unavailable (${reason}); degrading to in-memory storage`);
1071
1112
  options.onDegrade?.(reason, error);
1072
1113
  }
1073
1114
  return {
@@ -1131,6 +1172,9 @@ function defaultStorage(options = {}) {
1131
1172
  return createLocalStorageAdapter(options);
1132
1173
  }
1133
1174
 
1175
+ // src/version.ts
1176
+ var POLLAR_CORE_VERSION = "0.9.0-rc.1" ;
1177
+
1134
1178
  // src/visibility/noop.ts
1135
1179
  function createNoopVisibilityProvider() {
1136
1180
  return {
@@ -1195,6 +1239,7 @@ var AUTH_ERROR_CODES = {
1195
1239
  WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
1196
1240
  WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
1197
1241
  WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
1242
+ PASSKEY_FAILED: "PASSKEY_FAILED",
1198
1243
  UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
1199
1244
  };
1200
1245
  var PollarFlowError = class extends Error {
@@ -1240,7 +1285,7 @@ var FreighterAdapter = class {
1240
1285
  if (!userInfo?.publicKey) {
1241
1286
  throw new Error("Failed to get user information from Freighter");
1242
1287
  }
1243
- return { address: userInfo.publicKey, publicKey: userInfo.publicKey };
1288
+ return { address: userInfo.publicKey };
1244
1289
  }
1245
1290
  async disconnect() {
1246
1291
  }
@@ -1278,6 +1323,19 @@ var FreighterAdapter = class {
1278
1323
  };
1279
1324
 
1280
1325
  // src/wallets/AlbedoAdapter.ts
1326
+ var PUBLIC_PASSPHRASE = "Public Global Stellar Network ; September 2015";
1327
+ var TESTNET_PASSPHRASE = "Test SDF Network ; September 2015";
1328
+ function albedoNetwork(options, fallback) {
1329
+ switch (options?.networkPassphrase) {
1330
+ case PUBLIC_PASSPHRASE:
1331
+ return "public";
1332
+ case TESTNET_PASSPHRASE:
1333
+ return "testnet";
1334
+ }
1335
+ if (options?.network === "public" || options?.network === "mainnet") return "public";
1336
+ if (options?.network === "testnet") return "testnet";
1337
+ return fallback;
1338
+ }
1281
1339
  function openAlbedoPopup(url) {
1282
1340
  const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
1283
1341
  if (!popup) {
@@ -1316,7 +1374,13 @@ function waitForAlbedoResult() {
1316
1374
  });
1317
1375
  }
1318
1376
  var AlbedoAdapter = class {
1319
- constructor() {
1377
+ /**
1378
+ * Network used for `connect` and `signAuthEntry` (which carry no per-call
1379
+ * network) and as the fallback for `signTransaction`. Defaults to `'testnet'`
1380
+ * to preserve the previous behavior when constructed with no argument.
1381
+ */
1382
+ constructor(network = "testnet") {
1383
+ this.network = network;
1320
1384
  this.type = "albedo" /* ALBEDO */;
1321
1385
  }
1322
1386
  async isAvailable() {
@@ -1326,7 +1390,7 @@ var AlbedoAdapter = class {
1326
1390
  const url = new URL("https://albedo.link");
1327
1391
  url.searchParams.set("intent", "public-key");
1328
1392
  url.searchParams.set("app_name", "Pollar");
1329
- url.searchParams.set("network", "testnet");
1393
+ url.searchParams.set("network", this.network);
1330
1394
  url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1331
1395
  url.searchParams.set("origin", window.location.origin);
1332
1396
  openAlbedoPopup(url.toString());
@@ -1334,7 +1398,7 @@ var AlbedoAdapter = class {
1334
1398
  if (!result.pubkey) {
1335
1399
  throw new Error("Albedo connection rejected");
1336
1400
  }
1337
- return { address: result.pubkey, publicKey: result.pubkey };
1401
+ return { address: result.pubkey };
1338
1402
  }
1339
1403
  async disconnect() {
1340
1404
  }
@@ -1344,12 +1408,12 @@ var AlbedoAdapter = class {
1344
1408
  async getNetwork() {
1345
1409
  throw new Error("Albedo does not expose network");
1346
1410
  }
1347
- async signTransaction(xdr, _options) {
1411
+ async signTransaction(xdr, options) {
1348
1412
  const url = new URL("https://albedo.link");
1349
1413
  url.searchParams.set("intent", "tx");
1350
1414
  url.searchParams.set("xdr", xdr);
1351
1415
  url.searchParams.set("app_name", "Pollar");
1352
- url.searchParams.set("network", "testnet");
1416
+ url.searchParams.set("network", albedoNetwork(options, this.network));
1353
1417
  url.searchParams.set("callback", window.location.href);
1354
1418
  url.searchParams.set("origin", window.location.origin);
1355
1419
  window.location.href = url.toString();
@@ -1362,7 +1426,7 @@ var AlbedoAdapter = class {
1362
1426
  url.searchParams.set("intent", "sign-auth-entry");
1363
1427
  url.searchParams.set("xdr", entryXdr);
1364
1428
  url.searchParams.set("app_name", "Pollar");
1365
- url.searchParams.set("network", "testnet");
1429
+ url.searchParams.set("network", this.network);
1366
1430
  url.searchParams.set("callback", window.location.href);
1367
1431
  url.searchParams.set("origin", window.location.origin);
1368
1432
  window.location.href = url.toString();
@@ -1393,84 +1457,98 @@ function isBoundedString(v, max, allowEmpty = false) {
1393
1457
  if (!allowEmpty && v.length === 0) return false;
1394
1458
  return v.length <= max;
1395
1459
  }
1396
- function isValidSession(value) {
1460
+ function isValidSession(value, logger = console) {
1397
1461
  if (typeof value !== "object" || value === null) {
1398
- console.warn("[PollarClient:session] Invalid session \u2014 value is not an object");
1462
+ logger.debug("[PollarClient:session] Invalid session \u2014 value is not an object");
1399
1463
  return false;
1400
1464
  }
1401
1465
  const s = value;
1402
1466
  if (!isBoundedString(s["clientSessionId"], MAX_CLIENT_SESSION_ID)) {
1403
- console.warn("[PollarClient:session] Invalid session \u2014 clientSessionId missing/empty/too long");
1467
+ logger.debug("[PollarClient:session] Invalid session \u2014 clientSessionId missing/empty/too long");
1404
1468
  return false;
1405
1469
  }
1406
1470
  if (s["userId"] !== null && !isBoundedString(s["userId"], MAX_USER_ID)) {
1407
- console.warn("[PollarClient:session] Invalid session \u2014 userId must be string|null");
1471
+ logger.debug("[PollarClient:session] Invalid session \u2014 userId must be string|null");
1408
1472
  return false;
1409
1473
  }
1410
1474
  if (!isBoundedString(s["status"], MAX_STATUS)) {
1411
- console.warn("[PollarClient:session] Invalid session \u2014 status must be string");
1475
+ logger.debug("[PollarClient:session] Invalid session \u2014 status must be string");
1412
1476
  return false;
1413
1477
  }
1414
1478
  const token = s["token"];
1415
1479
  if (typeof token !== "object" || token === null) {
1416
- console.warn("[PollarClient:session] Invalid session \u2014 token missing or not an object");
1480
+ logger.debug("[PollarClient:session] Invalid session \u2014 token missing or not an object");
1417
1481
  return false;
1418
1482
  }
1419
1483
  const t = token;
1420
1484
  if (!isBoundedString(t["accessToken"], MAX_ACCESS_TOKEN)) {
1421
- console.warn("[PollarClient:session] Invalid session \u2014 token.accessToken missing/empty/too long");
1485
+ logger.debug("[PollarClient:session] Invalid session \u2014 token.accessToken missing/empty/too long");
1422
1486
  return false;
1423
1487
  }
1424
1488
  if (!isBoundedString(t["refreshToken"], MAX_REFRESH_TOKEN)) {
1425
- console.warn("[PollarClient:session] Invalid session \u2014 token.refreshToken missing/empty/too long");
1489
+ logger.debug("[PollarClient:session] Invalid session \u2014 token.refreshToken missing/empty/too long");
1426
1490
  return false;
1427
1491
  }
1428
1492
  if (typeof t["expiresAt"] !== "number" || !Number.isFinite(t["expiresAt"])) {
1429
- console.warn("[PollarClient:session] Invalid session \u2014 token.expiresAt must be a finite number");
1493
+ logger.debug("[PollarClient:session] Invalid session \u2014 token.expiresAt must be a finite number");
1430
1494
  return false;
1431
1495
  }
1432
1496
  const user = s["user"];
1433
1497
  if (typeof user !== "object" || user === null) {
1434
- console.warn("[PollarClient:session] Invalid session \u2014 user missing or not an object");
1498
+ logger.debug("[PollarClient:session] Invalid session \u2014 user missing or not an object");
1435
1499
  return false;
1436
1500
  }
1437
1501
  const u = user;
1438
1502
  if (u["id"] !== void 0 && !isBoundedString(u["id"], MAX_USER_ID)) {
1439
- console.warn("[PollarClient:session] Invalid session \u2014 user.id must be string if present");
1503
+ logger.debug("[PollarClient:session] Invalid session \u2014 user.id must be string if present");
1440
1504
  return false;
1441
1505
  }
1442
1506
  if (typeof u["ready"] !== "boolean") {
1443
- console.warn("[PollarClient:session] Invalid session \u2014 user.ready must be boolean");
1507
+ logger.debug("[PollarClient:session] Invalid session \u2014 user.ready must be boolean");
1444
1508
  return false;
1445
1509
  }
1446
1510
  const wallet = s["wallet"];
1447
1511
  if (typeof wallet !== "object" || wallet === null) {
1448
- console.warn("[PollarClient:session] Invalid session \u2014 wallet missing or not an object");
1512
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet missing or not an object");
1449
1513
  return false;
1450
1514
  }
1451
1515
  const w = wallet;
1452
- if (w["publicKey"] !== null && !isBoundedString(w["publicKey"], MAX_WALLET_PUBLIC_KEY)) {
1453
- console.warn("[PollarClient:session] Invalid session \u2014 wallet.publicKey must be string|null");
1516
+ if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
1517
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
1518
+ return false;
1519
+ }
1520
+ if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
1521
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
1454
1522
  return false;
1455
1523
  }
1456
1524
  if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
1457
- console.warn("[PollarClient:session] Invalid session \u2014 wallet.existsOnStellar must be boolean if present");
1525
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.existsOnStellar must be boolean if present");
1458
1526
  return false;
1459
1527
  }
1460
1528
  if (w["createdAt"] !== void 0 && (typeof w["createdAt"] !== "number" || !Number.isFinite(w["createdAt"]))) {
1461
- console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
1529
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
1530
+ return false;
1531
+ }
1532
+ if (w["linkedAt"] !== void 0 && (typeof w["linkedAt"] !== "number" || !Number.isFinite(w["linkedAt"]))) {
1533
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.linkedAt must be a finite number if present");
1462
1534
  return false;
1463
1535
  }
1464
1536
  return true;
1465
1537
  }
1466
- async function readStorage(storage, apiKeyHash) {
1538
+ async function readStorage(storage, apiKeyHash, logger = console) {
1467
1539
  const raw = await storage.get(sessionStorageKey(apiKeyHash));
1468
1540
  if (!raw) return null;
1469
1541
  try {
1470
1542
  const session = JSON.parse(raw);
1471
- if (!isValidSession(session)) {
1543
+ if (typeof session === "object" && session !== null) {
1544
+ const w = session.wallet;
1545
+ if (w && w["address"] == null && typeof w["publicKey"] === "string") {
1546
+ w["address"] = w["publicKey"];
1547
+ }
1548
+ }
1549
+ if (!isValidSession(session, logger)) {
1472
1550
  await storage.remove(sessionStorageKey(apiKeyHash));
1473
- console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
1551
+ logger.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
1474
1552
  return null;
1475
1553
  }
1476
1554
  if (session.token.expiresAt * 1e3 < Date.now()) {
@@ -1478,7 +1556,7 @@ async function readStorage(storage, apiKeyHash) {
1478
1556
  }
1479
1557
  return session;
1480
1558
  } catch (error) {
1481
- console.error("[PollarClient:session] Failed to parse session from storage", error);
1559
+ logger.error("[PollarClient:session] Failed to parse session from storage", error);
1482
1560
  await storage.remove(sessionStorageKey(apiKeyHash));
1483
1561
  return null;
1484
1562
  }
@@ -1540,7 +1618,7 @@ function abortableDelay(ms, signal) {
1540
1618
  });
1541
1619
  }
1542
1620
  var MAX_BACKOFF_MS = 5e3;
1543
- async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200, signal) {
1621
+ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200, signal, logger = console) {
1544
1622
  let backoff = retryDelayMs;
1545
1623
  const sleep = async (ms) => {
1546
1624
  if (ms <= 0) return;
@@ -1558,7 +1636,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1558
1636
  }));
1559
1637
  } catch (e) {
1560
1638
  if (e instanceof Error && e.name === "AbortError") throw e;
1561
- console.warn(e);
1639
+ logger.debug("[PollarClient:stream] session-status request failed; will retry", e);
1562
1640
  }
1563
1641
  if (error || !data) {
1564
1642
  await sleep(backoff);
@@ -1598,7 +1676,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1598
1676
  } catch (e) {
1599
1677
  if (e instanceof Error && e.name === "AbortError") throw e;
1600
1678
  if (e instanceof SessionStatusError) throw e;
1601
- console.warn(e);
1679
+ logger.debug("[PollarClient:stream] session-status stream read failed; will retry", e);
1602
1680
  } finally {
1603
1681
  reader.releaseLock();
1604
1682
  }
@@ -1608,7 +1686,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1608
1686
  if (delay) await sleep(delay);
1609
1687
  }
1610
1688
  }
1611
- async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal) {
1689
+ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal, logger = console) {
1612
1690
  const url = `${baseUrl}/auth/session/status/${encodeURIComponent(clientSessionId)}/poll`;
1613
1691
  let backoff = intervalMs;
1614
1692
  const sleep = async (ms) => {
@@ -1626,7 +1704,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
1626
1704
  envelope = await response.json().catch(() => null);
1627
1705
  } catch (e) {
1628
1706
  if (e instanceof Error && e.name === "AbortError") throw e;
1629
- console.warn(e);
1707
+ logger.debug("[PollarClient:stream] session-status poll failed; will retry", e);
1630
1708
  }
1631
1709
  if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
1632
1710
  throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
@@ -1643,13 +1721,13 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
1643
1721
  }
1644
1722
  }
1645
1723
  function waitForSessionReady(args) {
1646
- const { api, baseUrl, clientSessionId, check, useStreaming, retryDelayMs, signal } = args;
1647
- return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal);
1724
+ const { api, baseUrl, clientSessionId, check, useStreaming, retryDelayMs, signal, logger = console } = args;
1725
+ return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal, logger) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal, logger);
1648
1726
  }
1649
1727
 
1650
1728
  // src/client/auth/authenticate.ts
1651
1729
  async function authenticate(clientSessionId, deps, expectedWallet) {
1652
- const { api, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
1730
+ const { api, logger, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
1653
1731
  setAuthState({ step: "authenticating" });
1654
1732
  try {
1655
1733
  await waitForSessionReady({
@@ -1658,7 +1736,8 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1658
1736
  clientSessionId,
1659
1737
  check: (data2) => data2?.status === "READY",
1660
1738
  useStreaming,
1661
- signal
1739
+ signal,
1740
+ logger
1662
1741
  });
1663
1742
  } catch (err) {
1664
1743
  if (err instanceof SessionStatusError) {
@@ -1683,7 +1762,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1683
1762
  },
1684
1763
  signal
1685
1764
  });
1686
- if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content)) {
1765
+ if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content, logger)) {
1687
1766
  if (expectedWallet && data.content.data.providers.wallet?.address !== expectedWallet) {
1688
1767
  setAuthState({
1689
1768
  step: "error",
@@ -1830,6 +1909,55 @@ async function loginOAuth(provider, deps) {
1830
1909
  await authenticate(clientSessionId, deps);
1831
1910
  }
1832
1911
 
1912
+ // src/client/auth/passkeyFlow.ts
1913
+ async function loginSmartWallet(deps) {
1914
+ const { api, signal, setAuthState, passkey } = deps;
1915
+ if (!passkey) {
1916
+ setAuthState({
1917
+ step: "error",
1918
+ previousStep: "creating_session",
1919
+ message: "Passkey support is not configured",
1920
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1921
+ });
1922
+ return;
1923
+ }
1924
+ const clientSessionId = await createAuthSession(deps);
1925
+ if (!clientSessionId) return;
1926
+ try {
1927
+ const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
1928
+ body: { clientSessionId },
1929
+ signal
1930
+ });
1931
+ const challenge = challengeData?.content?.challenge;
1932
+ if (!challengeData?.success || !challenge) {
1933
+ return failPasskey(setAuthState, "Failed to start passkey");
1934
+ }
1935
+ setAuthState({ step: "creating_passkey" });
1936
+ const ceremony = await passkey({ challenge });
1937
+ const response = ceremony.response;
1938
+ if (ceremony.kind === "register") {
1939
+ setAuthState({ step: "deploying_smart_account" });
1940
+ const { data } = await api.POST("/auth/passkey/register", {
1941
+ body: { clientSessionId, response },
1942
+ signal
1943
+ });
1944
+ if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
1945
+ } else {
1946
+ const { data } = await api.POST("/auth/passkey/login", {
1947
+ body: { clientSessionId, response },
1948
+ signal
1949
+ });
1950
+ if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
1951
+ }
1952
+ } catch {
1953
+ return failPasskey(setAuthState, "Passkey login failed");
1954
+ }
1955
+ await authenticate(clientSessionId, deps);
1956
+ }
1957
+ function failPasskey(setAuthState, message) {
1958
+ setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
1959
+ }
1960
+
1833
1961
  // src/client/auth/walletFlow.ts
1834
1962
  function withSignal(promise, signal) {
1835
1963
  return Promise.race([
@@ -1856,12 +1984,12 @@ async function loginWallet(type, deps) {
1856
1984
  setAuthState({ step: "wallet_not_installed", walletType: type });
1857
1985
  return;
1858
1986
  }
1859
- const { publicKey } = await withSignal(adapter.connect(), signal);
1860
- connectedWallet = publicKey;
1987
+ const { address } = await withSignal(adapter.connect(), signal);
1988
+ connectedWallet = address;
1861
1989
  deps.storeWalletAdapter(adapter, type);
1862
1990
  setAuthState({ step: "authenticating_wallet" });
1863
1991
  const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
1864
- body: { clientSessionId, walletAddress: publicKey },
1992
+ body: { clientSessionId, walletAddress: address },
1865
1993
  signal
1866
1994
  });
1867
1995
  if (walletError || !walletData?.success) {
@@ -1926,8 +2054,12 @@ var PollarClient = class {
1926
2054
  this._transactionStateListeners = /* @__PURE__ */ new Set();
1927
2055
  this._txHistoryState = { step: "idle" };
1928
2056
  this._txHistoryStateListeners = /* @__PURE__ */ new Set();
2057
+ this._sessionsState = { step: "idle" };
2058
+ this._sessionsStateListeners = /* @__PURE__ */ new Set();
1929
2059
  this._walletBalanceState = { step: "idle" };
1930
2060
  this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
2061
+ this._enabledAssetsState = { step: "idle" };
2062
+ this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
1931
2063
  this._authState = { step: "idle" };
1932
2064
  this._authStateListeners = /* @__PURE__ */ new Set();
1933
2065
  this._networkState = { step: "idle" };
@@ -1943,10 +2075,14 @@ var PollarClient = class {
1943
2075
  this._storageDegradeListeners = /* @__PURE__ */ new Set();
1944
2076
  this._walletAdapter = null;
1945
2077
  this._loginController = null;
2078
+ /** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
2079
+ this._resumeController = null;
1946
2080
  this.apiKey = config.apiKey;
1947
2081
  this.id = randomUUID();
1948
2082
  this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
2083
+ this._log = createLogger(config.logLevel ?? "info", config.logger);
1949
2084
  this._storage = config.storage ?? defaultStorage({
2085
+ logger: this._log,
1950
2086
  onDegrade: (reason, error) => {
1951
2087
  config.onStorageDegrade?.(reason, error);
1952
2088
  this._dispatchStorageDegrade(reason, error);
@@ -1955,6 +2091,8 @@ var PollarClient = class {
1955
2091
  this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
1956
2092
  this._walletAdapterResolver = config.walletAdapter ?? null;
1957
2093
  this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
2094
+ this._passkey = config.passkey ?? null;
2095
+ this._passkeySign = config.passkeySign ?? null;
1958
2096
  this._deviceLabel = config.deviceLabel;
1959
2097
  this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
1960
2098
  this._maxIdleMs = config.maxIdleMs;
@@ -1968,7 +2106,9 @@ var PollarClient = class {
1968
2106
  this._initialized = Promise.resolve();
1969
2107
  return;
1970
2108
  }
1971
- console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`);
2109
+ this._log.info(
2110
+ `[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
2111
+ );
1972
2112
  this._initialized = this._initialize();
1973
2113
  }
1974
2114
  /**
@@ -1993,7 +2133,7 @@ var PollarClient = class {
1993
2133
  const sessionKey = sessionStorageKey(this._apiKeyHash);
1994
2134
  const handler = (e) => {
1995
2135
  if (e.key === sessionKey) {
1996
- this._restoreSession().catch((err) => console.error("[PollarClient] Cross-tab restore failed", err));
2136
+ this._restoreSession().catch((err) => this._log.error("[PollarClient] Cross-tab restore failed", err));
1997
2137
  }
1998
2138
  };
1999
2139
  window.addEventListener("storage", handler);
@@ -2002,11 +2142,15 @@ var PollarClient = class {
2002
2142
  try {
2003
2143
  await this._keyManager.init();
2004
2144
  } catch (err) {
2005
- console.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
2145
+ this._log.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
2006
2146
  }
2007
2147
  await this._restoreSession();
2008
2148
  this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
2009
- if (visible) void this._maybeProactiveRefresh();
2149
+ if (!visible) return;
2150
+ void this._maybeProactiveRefresh();
2151
+ if (this._authState.step === "authenticated" && !this._authState.verified) {
2152
+ void this._resume();
2153
+ }
2010
2154
  });
2011
2155
  }
2012
2156
  /** Detach the cross-tab storage listener and abort any in-flight login. */
@@ -2017,6 +2161,8 @@ var PollarClient = class {
2017
2161
  }
2018
2162
  this._loginController?.abort();
2019
2163
  this._loginController = null;
2164
+ this._resumeController?.abort();
2165
+ this._resumeController = null;
2020
2166
  this._clearRefreshTimer();
2021
2167
  if (this._visibilityUnsubscribe) {
2022
2168
  this._visibilityUnsubscribe();
@@ -2031,11 +2177,13 @@ var PollarClient = class {
2031
2177
  request.headers.set("x-pollar-api-key", self.apiKey);
2032
2178
  self._lastRequestAt = Date.now();
2033
2179
  await self._initialized;
2034
- if (request.body !== null) {
2180
+ const cacheMethod = request.method.toUpperCase();
2181
+ const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
2182
+ if (cacheBodyAllowed && request.body != null) {
2035
2183
  try {
2036
2184
  self._requestBodyCache.set(request, await request.clone().arrayBuffer());
2037
2185
  } catch (err) {
2038
- console.warn("[PollarClient] Could not snapshot request body for retry", err);
2186
+ this._log.warn("[PollarClient] Could not snapshot request body for retry", err);
2039
2187
  }
2040
2188
  }
2041
2189
  const isRefresh = request.url.includes("/auth/refresh");
@@ -2094,7 +2242,7 @@ var PollarClient = class {
2094
2242
  this._keyManager
2095
2243
  );
2096
2244
  } catch (err) {
2097
- console.warn("[PollarClient] DPoP proof build failed", err);
2245
+ this._log.warn("[PollarClient] DPoP proof build failed", err);
2098
2246
  return null;
2099
2247
  }
2100
2248
  }
@@ -2118,7 +2266,9 @@ var PollarClient = class {
2118
2266
  }
2119
2267
  }
2120
2268
  }
2121
- const cachedBody = this._requestBodyCache.get(originalRequest);
2269
+ const retryMethod = originalRequest.method.toUpperCase();
2270
+ const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
2271
+ const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
2122
2272
  const retried = new Request(originalRequest.url, {
2123
2273
  method: originalRequest.method,
2124
2274
  headers,
@@ -2146,7 +2296,7 @@ var PollarClient = class {
2146
2296
  async _doRefresh() {
2147
2297
  const refreshToken = this._session?.token?.refreshToken;
2148
2298
  if (!refreshToken) {
2149
- console.warn("[PollarClient] Refresh skipped: no refresh token in session");
2299
+ this._log.warn("[PollarClient] Refresh skipped: no refresh token in session");
2150
2300
  await this._clearSession();
2151
2301
  throw new Error("No refresh token available");
2152
2302
  }
@@ -2157,24 +2307,31 @@ var PollarClient = class {
2157
2307
  data = response.data;
2158
2308
  error = response.error;
2159
2309
  } catch (err) {
2160
- console.error("[PollarClient] /auth/refresh request threw", err);
2310
+ this._log.error("[PollarClient] /auth/refresh request threw", err);
2161
2311
  await this._clearSession();
2162
2312
  throw err;
2163
2313
  }
2164
2314
  if (error || !data) {
2165
- console.warn("[PollarClient] /auth/refresh returned error", { error });
2315
+ this._log.error("[PollarClient] /auth/refresh returned error", { error });
2166
2316
  await this._clearSession();
2167
2317
  throw new Error("Refresh failed");
2168
2318
  }
2169
2319
  const successData = data;
2170
2320
  if (!successData.success || !successData.content?.token) {
2171
- console.warn("[PollarClient] /auth/refresh response malformed", successData);
2321
+ this._log.error("[PollarClient] /auth/refresh response malformed", {
2322
+ success: successData.success,
2323
+ hasToken: !!successData.content?.token
2324
+ });
2172
2325
  await this._clearSession();
2173
2326
  throw new Error("Refresh response malformed");
2174
2327
  }
2175
2328
  const newToken = successData.content.token;
2176
2329
  if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
2177
- console.warn("[PollarClient] /auth/refresh token shape invalid", newToken);
2330
+ this._log.error("[PollarClient] /auth/refresh token shape invalid", {
2331
+ accessToken: typeof newToken.accessToken,
2332
+ refreshToken: typeof newToken.refreshToken,
2333
+ expiresAt: typeof newToken.expiresAt
2334
+ });
2178
2335
  await this._clearSession();
2179
2336
  throw new Error("Refresh response token shape invalid");
2180
2337
  }
@@ -2182,9 +2339,9 @@ var PollarClient = class {
2182
2339
  try {
2183
2340
  this._session = { ...this._session, token: newToken };
2184
2341
  await writeStorage(this._storage, this.apiKeyHash, this._session);
2185
- console.info("[PollarClient] Tokens refreshed");
2342
+ this._log.info("[PollarClient] Tokens refreshed");
2186
2343
  } catch (err) {
2187
- console.error("[PollarClient] Failed to persist refreshed session", err);
2344
+ this._log.error("[PollarClient] Failed to persist refreshed session", err);
2188
2345
  }
2189
2346
  this._scheduleNextRefresh();
2190
2347
  }
@@ -2238,7 +2395,7 @@ var PollarClient = class {
2238
2395
  try {
2239
2396
  await this.refresh();
2240
2397
  } catch (err) {
2241
- console.warn("[PollarClient] Proactive refresh failed; session cleared", err);
2398
+ this._log.warn("[PollarClient] Proactive refresh failed; session cleared", err);
2242
2399
  }
2243
2400
  }
2244
2401
  _clearRefreshTimer() {
@@ -2284,7 +2441,7 @@ var PollarClient = class {
2284
2441
  try {
2285
2442
  cb(reason, error);
2286
2443
  } catch (err) {
2287
- console.error("[PollarClient] onStorageDegrade listener threw", err);
2444
+ this._log.error("[PollarClient] onStorageDegrade listener threw", err);
2288
2445
  }
2289
2446
  }
2290
2447
  }
@@ -2368,6 +2525,19 @@ var PollarClient = class {
2368
2525
  const controller = this._newController();
2369
2526
  loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2370
2527
  }
2528
+ /**
2529
+ * "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
2530
+ * user, creates a sponsored smart-account C-address. Requires the `passkey`
2531
+ * ceremony to be configured (e.g. via `@pollar/react`).
2532
+ */
2533
+ loginSmartWallet() {
2534
+ if (!isClientRuntime) {
2535
+ warnServerSide("loginSmartWallet");
2536
+ return;
2537
+ }
2538
+ const controller = this._newController();
2539
+ loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2540
+ }
2371
2541
  // ─── Cancel ───────────────────────────────────────────────────────────────
2372
2542
  cancelLogin() {
2373
2543
  this._loginController?.abort();
@@ -2391,20 +2561,20 @@ var PollarClient = class {
2391
2561
  warnServerSide("logout");
2392
2562
  return;
2393
2563
  }
2394
- console.info("[PollarClient] Logout requested", { everywhere: !!options.everywhere });
2564
+ this._log.info("[PollarClient] Logout requested", { everywhere: !!options.everywhere });
2395
2565
  if (this._session?.token?.accessToken) {
2396
2566
  try {
2397
2567
  await this._api.POST("/auth/logout", {
2398
2568
  body: options.everywhere ? { everywhere: true } : {}
2399
2569
  });
2400
2570
  } catch (err) {
2401
- console.warn("[PollarClient] Server logout failed (continuing with local clear)", err);
2571
+ this._log.warn("[PollarClient] Server logout failed (continuing with local clear)", err);
2402
2572
  }
2403
2573
  }
2404
2574
  try {
2405
2575
  await this._clearSession();
2406
2576
  } catch (err) {
2407
- console.warn("[PollarClient] Local logout cleanup failed", err);
2577
+ this._log.warn("[PollarClient] Local logout cleanup failed", err);
2408
2578
  }
2409
2579
  }
2410
2580
  /** Convenience: revoke every active session for this user (all devices). */
@@ -2430,6 +2600,29 @@ var PollarClient = class {
2430
2600
  }
2431
2601
  return data.content.sessions;
2432
2602
  }
2603
+ getSessionsState() {
2604
+ return this._sessionsState;
2605
+ }
2606
+ onSessionsStateChange(cb) {
2607
+ this._sessionsStateListeners.add(cb);
2608
+ cb(this._sessionsState);
2609
+ return () => this._sessionsStateListeners.delete(cb);
2610
+ }
2611
+ /**
2612
+ * Fire-and-forget variant of {@link listSessions} that drives the observable
2613
+ * `SessionsState` store instead of returning the array. UI layers subscribe
2614
+ * via `onSessionsStateChange` and stay pure readers — mirrors `fetchTxHistory`.
2615
+ */
2616
+ async fetchSessions() {
2617
+ this._setSessionsState({ step: "loading" });
2618
+ try {
2619
+ const sessions = await this.listSessions();
2620
+ this._setSessionsState({ step: "loaded", sessions });
2621
+ } catch (err) {
2622
+ const message = err instanceof Error ? err.message : "Failed to load sessions";
2623
+ this._setSessionsState({ step: "error", message });
2624
+ }
2625
+ }
2433
2626
  /**
2434
2627
  * Revoke a specific refresh-token family (a single device session). Use
2435
2628
  * `listSessions` to enumerate the familyIds. Revoking the current session
@@ -2457,6 +2650,14 @@ var PollarClient = class {
2457
2650
  getNetworkState() {
2458
2651
  return this._networkState;
2459
2652
  }
2653
+ /**
2654
+ * The client's level-gated logger (built from `logLevel` / `logger`). Exposed
2655
+ * so the runtime layer (`@pollar/react`) can route its own logs through the
2656
+ * same level and sink instead of calling `console` directly.
2657
+ */
2658
+ getLogger() {
2659
+ return this._log;
2660
+ }
2460
2661
  setNetwork(network) {
2461
2662
  this._setNetworkState({ step: "connected", network });
2462
2663
  }
@@ -2506,16 +2707,19 @@ var PollarClient = class {
2506
2707
  cb(this._walletBalanceState);
2507
2708
  return () => this._walletBalanceStateListeners.delete(cb);
2508
2709
  }
2509
- async refreshBalance(publicKey) {
2510
- const pk = publicKey ?? this._session?.wallet?.publicKey;
2511
- if (!pk) {
2710
+ /**
2711
+ * Refreshes the balances of the authenticated user's OWN wallet. The wallet
2712
+ * and network are resolved server-side from the session — no arguments. Drives
2713
+ * `walletBalanceState`. For an arbitrary wallet, use {@link getWalletBalance}.
2714
+ */
2715
+ async refreshBalance() {
2716
+ if (!this._session?.wallet?.address) {
2512
2717
  this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
2513
2718
  return;
2514
2719
  }
2515
2720
  this._setWalletBalanceState({ step: "loading" });
2516
2721
  try {
2517
- const network = this.getNetwork();
2518
- const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
2722
+ const { data, error } = await this._api.GET("/wallet/balance");
2519
2723
  if (!error && data?.success && data.content) {
2520
2724
  this._setWalletBalanceState({ step: "loaded", data: data.content });
2521
2725
  } else {
@@ -2525,6 +2729,53 @@ var PollarClient = class {
2525
2729
  this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
2526
2730
  }
2527
2731
  }
2732
+ /**
2733
+ * General-purpose balance lookup for ANY wallet on ANY network — not scoped
2734
+ * to this application. Enumerates the account's real on-chain holdings via
2735
+ * Horizon (server-side) and returns the data directly (no reactive state).
2736
+ * `network` defaults to the client's current network.
2737
+ */
2738
+ async getWalletBalance(publicKey, network) {
2739
+ const { data, error } = await this._api.GET("/wallet/{publicKey}/balance", {
2740
+ params: { path: { publicKey }, query: { network: network ?? this.getNetwork() } }
2741
+ });
2742
+ if (error || !data?.success || !data.content) {
2743
+ throw new Error("[PollarClient] Failed to load wallet balance");
2744
+ }
2745
+ return data.content;
2746
+ }
2747
+ // ─── Enabled assets ───────────────────────────────────────────────────────
2748
+ getEnabledAssetsState() {
2749
+ return this._enabledAssetsState;
2750
+ }
2751
+ onEnabledAssetsStateChange(cb) {
2752
+ this._enabledAssetsStateListeners.add(cb);
2753
+ cb(this._enabledAssetsState);
2754
+ return () => this._enabledAssetsStateListeners.delete(cb);
2755
+ }
2756
+ /**
2757
+ * Loads the application's enabled assets paired with the authenticated
2758
+ * wallet's on-chain trustline state — so the SDK knows which trustlines still
2759
+ * need to be added. Wallet and network are resolved server-side from the
2760
+ * session. Drives `enabledAssetsState`; mirrors {@link refreshBalance}.
2761
+ */
2762
+ async refreshAssets() {
2763
+ if (!this._session?.wallet?.address) {
2764
+ this._setEnabledAssetsState({ step: "error", message: "No wallet connected" });
2765
+ return;
2766
+ }
2767
+ this._setEnabledAssetsState({ step: "loading" });
2768
+ try {
2769
+ const { data, error } = await this._api.GET("/wallet/assets");
2770
+ if (!error && data?.success && data.content) {
2771
+ this._setEnabledAssetsState({ step: "loaded", data: data.content });
2772
+ } else {
2773
+ this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
2774
+ }
2775
+ } catch {
2776
+ this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
2777
+ }
2778
+ }
2528
2779
  // ─── Transactions ─────────────────────────────────────────────────────────
2529
2780
  /**
2530
2781
  * Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
@@ -2532,14 +2783,14 @@ var PollarClient = class {
2532
2783
  * inspect the result without subscribing to state changes.
2533
2784
  */
2534
2785
  async buildTx(operation, params, options) {
2535
- if (!this._session?.wallet?.publicKey) {
2786
+ if (!this._session?.wallet?.address) {
2536
2787
  const details = "No wallet connected";
2537
2788
  this._setTransactionState({ step: "error", phase: "building", details });
2538
2789
  return { status: "error", details };
2539
2790
  }
2540
2791
  const body = {
2541
2792
  network: this.getNetwork(),
2542
- publicKey: this._session.wallet.publicKey,
2793
+ address: this._session.wallet.address,
2543
2794
  operation,
2544
2795
  params,
2545
2796
  options: options ?? {}
@@ -2555,7 +2806,7 @@ var PollarClient = class {
2555
2806
  this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
2556
2807
  return { status: "error", ...details && { details } };
2557
2808
  } catch (err) {
2558
- console.error("[PollarClient] buildTx failed", err);
2809
+ this._log.error("[PollarClient] buildTx failed", err);
2559
2810
  this._setTransactionState({ step: "error", phase: "building" });
2560
2811
  return { status: "error" };
2561
2812
  }
@@ -2580,7 +2831,7 @@ var PollarClient = class {
2580
2831
  const buildData = this._currentBuildData();
2581
2832
  this._setTransactionState({ step: "signing", ...buildData && { buildData } });
2582
2833
  if (this._walletAdapter) {
2583
- const accountToSign = this._session?.wallet?.publicKey;
2834
+ const accountToSign = this._session?.wallet?.address;
2584
2835
  const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
2585
2836
  try {
2586
2837
  const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
@@ -2601,10 +2852,10 @@ var PollarClient = class {
2601
2852
  return { status: "error", ...details && { details } };
2602
2853
  }
2603
2854
  }
2604
- const publicKey = this._session?.wallet?.publicKey ?? "";
2855
+ const address = this._session?.wallet?.address ?? "";
2605
2856
  try {
2606
2857
  const { data, error } = await this._api.POST("/tx/sign", {
2607
- body: { network: this.getNetwork(), publicKey, unsignedXdr }
2858
+ body: { network: this.getNetwork(), address, unsignedXdr }
2608
2859
  });
2609
2860
  if (!error && data?.success && data.content?.signedXdr) {
2610
2861
  const { signedXdr, idempotencyKey } = data.content;
@@ -2657,12 +2908,12 @@ var PollarClient = class {
2657
2908
  const buildData = this._currentBuildData();
2658
2909
  const outcomeExtra = buildData ? { buildData } : {};
2659
2910
  this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
2660
- const publicKey = this._session?.wallet?.publicKey ?? "";
2911
+ const address = this._session?.wallet?.address ?? "";
2661
2912
  try {
2662
2913
  const { data, error } = await this._api.POST("/tx/submit", {
2663
2914
  body: {
2664
2915
  network: this.getNetwork(),
2665
- publicKey,
2916
+ address,
2666
2917
  signedXdr,
2667
2918
  ...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
2668
2919
  }
@@ -2722,6 +2973,19 @@ var PollarClient = class {
2722
2973
  * `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
2723
2974
  */
2724
2975
  async signAndSubmitTx(unsignedXdr) {
2976
+ if (this._session?.wallet?.type === "smart") {
2977
+ const buildData2 = this._currentBuildData();
2978
+ if (!buildData2?.smart) {
2979
+ const details = "no prepared smart transaction; call buildTx first";
2980
+ this._setTransactionState({ step: "error", phase: "signing", details });
2981
+ return { status: "error", details };
2982
+ }
2983
+ return this._signSubmitSmart(buildData2);
2984
+ }
2985
+ if (!unsignedXdr) {
2986
+ this._setTransactionState({ step: "error", phase: "signing", details: "missing unsigned transaction" });
2987
+ return { status: "error", details: "missing unsigned transaction" };
2988
+ }
2725
2989
  if (this._walletAdapter) {
2726
2990
  const signed = await this.signTx(unsignedXdr);
2727
2991
  if (signed.status === "error") {
@@ -2739,7 +3003,7 @@ var PollarClient = class {
2739
3003
  this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
2740
3004
  const body = {
2741
3005
  network: this.getNetwork(),
2742
- publicKey: this._session?.wallet?.publicKey ?? "",
3006
+ address: this._session?.wallet?.address ?? "",
2743
3007
  unsignedXdr
2744
3008
  };
2745
3009
  try {
@@ -2808,14 +3072,20 @@ var PollarClient = class {
2808
3072
  * `signTx`, and `submitTx` separately instead.
2809
3073
  */
2810
3074
  async buildAndSignAndSubmitTx(operation, params, options) {
3075
+ if (this._session?.wallet?.type === "smart") {
3076
+ return this._runSmartTx(operation, params, options);
3077
+ }
2811
3078
  if (this._walletAdapter) {
2812
3079
  const built = await this.buildTx(operation, params, options);
2813
3080
  if (built.status === "error") {
2814
3081
  return { status: "error", ...built.details && { details: built.details } };
2815
3082
  }
3083
+ if (!built.buildData.unsignedXdr) {
3084
+ return { status: "error", details: "build returned no unsigned transaction" };
3085
+ }
2816
3086
  return this.signAndSubmitTx(built.buildData.unsignedXdr);
2817
3087
  }
2818
- if (!this._session?.wallet?.publicKey) {
3088
+ if (!this._session?.wallet?.address) {
2819
3089
  this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
2820
3090
  return { status: "error", details: "No wallet connected" };
2821
3091
  }
@@ -2824,7 +3094,7 @@ var PollarClient = class {
2824
3094
  const { data, error } = await this._api.POST("/tx/build-sign-submit", {
2825
3095
  body: {
2826
3096
  network: this.getNetwork(),
2827
- publicKey: this._session.wallet.publicKey,
3097
+ address: this._session.wallet.address,
2828
3098
  operation,
2829
3099
  params,
2830
3100
  options: options ?? {}
@@ -2868,6 +3138,113 @@ var PollarClient = class {
2868
3138
  async runTx(operation, params, options) {
2869
3139
  return this.buildAndSignAndSubmitTx(operation, params, options);
2870
3140
  }
3141
+ /**
3142
+ * Smart-wallet (passkey / C-address) transaction: build (server prepares the
3143
+ * SAC transfer + returns the auth digest) → sign the digest with the passkey
3144
+ * → submit (server assembles the signed auth entry and broadcasts; the
3145
+ * sponsor pays the fee). State machine: building → built → signing →
3146
+ * submitting → success.
3147
+ */
3148
+ async _runSmartTx(operation, params, options) {
3149
+ const address = this._session?.wallet?.address;
3150
+ if (!address) {
3151
+ this._setTransactionState({ step: "error", phase: "building", details: "No wallet connected" });
3152
+ return { status: "error", details: "No wallet connected" };
3153
+ }
3154
+ if (!this._passkeySign) {
3155
+ const details = "Passkey signer not configured";
3156
+ this._setTransactionState({ step: "error", phase: "signing", details });
3157
+ return { status: "error", details };
3158
+ }
3159
+ this._setTransactionState({ step: "building" });
3160
+ let buildData;
3161
+ try {
3162
+ const body = {
3163
+ network: this.getNetwork(),
3164
+ address,
3165
+ operation,
3166
+ params,
3167
+ options: options ?? {}
3168
+ };
3169
+ const { data, error } = await this._api.POST("/tx/build", { body });
3170
+ if (error || !data?.success || !data.content?.smart) {
3171
+ const details = error?.details ?? "Failed to build transaction";
3172
+ this._setTransactionState({ step: "error", phase: "building", details });
3173
+ return { status: "error", details };
3174
+ }
3175
+ buildData = data.content;
3176
+ } catch (err) {
3177
+ const details = err instanceof Error ? err.message : void 0;
3178
+ this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
3179
+ return { status: "error", ...details && { details } };
3180
+ }
3181
+ this._setTransactionState({ step: "built", buildData });
3182
+ return this._signSubmitSmart(buildData);
3183
+ }
3184
+ /**
3185
+ * Steps 2–3 of the smart-wallet flow: sign the prepared auth digest with the
3186
+ * passkey, then submit. Shared by `_runSmartTx` (atomic) and `signAndSubmitTx`
3187
+ * (split flow, when a smart build is already on the state machine).
3188
+ */
3189
+ async _signSubmitSmart(buildData) {
3190
+ const address = this._session?.wallet?.address;
3191
+ const smart = buildData.smart;
3192
+ if (!address || !smart) {
3193
+ const details = "no prepared smart transaction";
3194
+ this._setTransactionState({ step: "error", phase: "signing", buildData, details });
3195
+ return { status: "error", buildData, details };
3196
+ }
3197
+ if (!this._passkeySign) {
3198
+ const details = "Passkey signer not configured";
3199
+ this._setTransactionState({ step: "error", phase: "signing", buildData, details });
3200
+ return { status: "error", buildData, details };
3201
+ }
3202
+ this._setTransactionState({ step: "signing", buildData });
3203
+ let assertion;
3204
+ try {
3205
+ assertion = await this._passkeySign({ credentialId: smart.credentialId, challenge: smart.digest });
3206
+ } catch (err) {
3207
+ const details = err instanceof Error ? err.message : void 0;
3208
+ this._setTransactionState({ step: "error", phase: "signing", buildData, ...details && { details } });
3209
+ return { status: "error", buildData, ...details && { details } };
3210
+ }
3211
+ this._setTransactionState({ step: "submitting", buildData });
3212
+ const outcomeExtra = { buildData };
3213
+ try {
3214
+ const { data, error } = await this._api.POST("/tx/submit", {
3215
+ body: {
3216
+ network: this.getNetwork(),
3217
+ address,
3218
+ smart: { entryXdr: smart.entryXdr, funcXdr: smart.funcXdr, assertion }
3219
+ }
3220
+ });
3221
+ if (!error && data?.success && data.content) {
3222
+ const { hash, status: backendStatus, resultCode } = data.content;
3223
+ if (backendStatus === "SUCCESS") {
3224
+ this._setTransactionState({ step: "success", hash, buildData });
3225
+ return { status: "success", hash, ...outcomeExtra };
3226
+ }
3227
+ if (backendStatus === "PENDING") {
3228
+ this._setTransactionState({ step: "submitted", hash, buildData });
3229
+ return { status: "pending", hash, ...outcomeExtra };
3230
+ }
3231
+ this._setTransactionState({
3232
+ step: "error",
3233
+ phase: "submitting",
3234
+ buildData,
3235
+ ...resultCode && { details: resultCode }
3236
+ });
3237
+ return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
3238
+ }
3239
+ const details = error?.details;
3240
+ this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
3241
+ return { status: "error", ...outcomeExtra, ...details && { details } };
3242
+ } catch (err) {
3243
+ const details = err instanceof Error ? err.message : void 0;
3244
+ this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
3245
+ return { status: "error", ...outcomeExtra, ...details && { details } };
3246
+ }
3247
+ }
2871
3248
  // ─── App config ───────────────────────────────────────────────────────────
2872
3249
  async getAppConfig() {
2873
3250
  try {
@@ -2921,10 +3298,18 @@ var PollarClient = class {
2921
3298
  this._txHistoryState = next;
2922
3299
  for (const cb of this._txHistoryStateListeners) cb(next);
2923
3300
  }
3301
+ _setSessionsState(next) {
3302
+ this._sessionsState = next;
3303
+ for (const cb of this._sessionsStateListeners) cb(next);
3304
+ }
2924
3305
  _setWalletBalanceState(next) {
2925
3306
  this._walletBalanceState = next;
2926
3307
  for (const cb of this._walletBalanceStateListeners) cb(next);
2927
3308
  }
3309
+ _setEnabledAssetsState(next) {
3310
+ this._enabledAssetsState = next;
3311
+ for (const cb of this._enabledAssetsStateListeners) cb(next);
3312
+ }
2928
3313
  // ─── Private ──────────────────────────────────────────────────────────────
2929
3314
  _newController() {
2930
3315
  this._loginController?.abort();
@@ -2934,6 +3319,7 @@ var PollarClient = class {
2934
3319
  _flowDeps(signal) {
2935
3320
  return {
2936
3321
  api: this._api,
3322
+ logger: this._log,
2937
3323
  basePath: this.basePath,
2938
3324
  // SSE status streaming works on web; React Native's `fetch` has no
2939
3325
  // readable `response.body`, so those clients poll the non-streaming
@@ -2949,6 +3335,7 @@ var PollarClient = class {
2949
3335
  this._walletAdapter = adapter;
2950
3336
  await writeWalletType(this._storage, this.apiKeyHash, id);
2951
3337
  },
3338
+ ...this._passkey ? { passkey: this._passkey } : {},
2952
3339
  ...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
2953
3340
  };
2954
3341
  }
@@ -2978,19 +3365,19 @@ var PollarClient = class {
2978
3365
  }
2979
3366
  }
2980
3367
  if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
2981
- if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
3368
+ if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
2982
3369
  throw new Error(
2983
3370
  `[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
2984
3371
  );
2985
3372
  }
2986
3373
  _handleFlowError(error) {
2987
3374
  if (error instanceof Error && error.name === "AbortError") {
2988
- console.info("[PollarClient] Login cancelled");
3375
+ this._log.debug("[PollarClient] Login cancelled");
2989
3376
  this._setAuthState({ step: "idle" });
2990
3377
  return;
2991
3378
  }
2992
3379
  if (error instanceof Error && error.code === AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT) {
2993
- console.error("[PollarClient]", error.message);
3380
+ this._log.error("[PollarClient]", error.message);
2994
3381
  this._setAuthState({
2995
3382
  step: "error",
2996
3383
  previousStep: this._authState.step,
@@ -2999,7 +3386,7 @@ var PollarClient = class {
2999
3386
  });
3000
3387
  return;
3001
3388
  }
3002
- console.error("[PollarClient] Unexpected error in auth flow", error);
3389
+ this._log.error("[PollarClient] Unexpected error in auth flow", error);
3003
3390
  this._setAuthState({
3004
3391
  step: "error",
3005
3392
  previousStep: this._authState.step,
@@ -3008,32 +3395,77 @@ var PollarClient = class {
3008
3395
  });
3009
3396
  }
3010
3397
  async _restoreSession() {
3011
- this._session = await readStorage(this._storage, this.apiKeyHash);
3398
+ this._session = await readStorage(this._storage, this.apiKeyHash, this._log);
3012
3399
  if (this._session) {
3013
3400
  const storedType = await readWalletType(this._storage, this.apiKeyHash);
3014
3401
  if (storedType) {
3015
3402
  try {
3016
3403
  this._walletAdapter = await this._resolveWalletAdapter(storedType);
3017
3404
  } catch (err) {
3018
- console.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
3405
+ this._log.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
3019
3406
  }
3020
3407
  }
3021
- console.info("[PollarClient] Session restored from storage");
3022
- this._setAuthState({ step: "authenticated", session: this._session });
3408
+ this._log.info("[PollarClient] Session restored from storage");
3409
+ this._setAuthState({ step: "authenticated", session: this._session, verified: false });
3023
3410
  this._scheduleNextRefresh();
3411
+ void this._resume();
3024
3412
  } else {
3025
- console.info("[PollarClient] No session in storage");
3413
+ this._log.info("[PollarClient] No session in storage");
3414
+ }
3415
+ }
3416
+ /**
3417
+ * Validate the restored session against the server and repopulate the
3418
+ * in-memory profile (PII is never persisted, so it's null after a cold
3419
+ * reload). Goes through the normal authed client, so it coalesces with any
3420
+ * in-flight refresh (onRequest awaits `_refreshPromise`) and, being a GET,
3421
+ * is auto-retried after a 401-triggered refresh.
3422
+ *
3423
+ * - 200 → store profile, mark the session `verified`.
3424
+ * - 401 → the refresh-on-401 path already ran; if the family was
3425
+ * revoked, refresh failed and `_clearSession()` took us to
3426
+ * idle. Nothing to do here — don't double-handle.
3427
+ * - network error → stay optimistic (do NOT log out); revalidated later on
3428
+ * `visibilitychange` or first use.
3429
+ */
3430
+ async _resume() {
3431
+ if (!this._session) return;
3432
+ this._resumeController?.abort();
3433
+ const controller = new AbortController();
3434
+ this._resumeController = controller;
3435
+ try {
3436
+ const { data, error } = await this._api.GET("/auth/session/resume", { signal: controller.signal });
3437
+ if (error || !data) return;
3438
+ const content = data.content;
3439
+ if (!content || !this._session) return;
3440
+ this._profile = { ...content };
3441
+ this._setAuthState({ step: "authenticated", session: this._session, verified: true });
3442
+ } catch (err) {
3443
+ if (err?.name === "AbortError") return;
3444
+ this._log.warn("[PollarClient] resume failed (network); will retry", err);
3445
+ } finally {
3446
+ if (this._resumeController === controller) this._resumeController = null;
3026
3447
  }
3027
3448
  }
3028
3449
  async _storeSession(session) {
3029
- console.info("[PollarClient] Session stored");
3450
+ this._log.info("[PollarClient] Session stored");
3451
+ const w = session.wallet;
3030
3452
  const persisted = {
3031
3453
  clientSessionId: session.clientSessionId,
3032
3454
  userId: session.userId ?? null,
3033
3455
  status: session.status,
3034
3456
  token: session.token,
3035
3457
  user: session.user,
3036
- wallet: session.wallet
3458
+ // The wire response still carries the legacy `publicKey` alias (kept for
3459
+ // older SDKs); the persisted session standardizes on `address` only.
3460
+ wallet: {
3461
+ type: w.type,
3462
+ address: w.address ?? w.publicKey ?? null,
3463
+ ...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
3464
+ ...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
3465
+ ...w.linkedAt !== void 0 ? { linkedAt: w.linkedAt } : {},
3466
+ ...w.network !== void 0 ? { network: w.network } : {},
3467
+ ...w.deployTxHash !== void 0 ? { deployTxHash: w.deployTxHash } : {}
3468
+ }
3037
3469
  };
3038
3470
  this._session = persisted;
3039
3471
  if (session.data) {
@@ -3046,11 +3478,11 @@ var PollarClient = class {
3046
3478
  };
3047
3479
  }
3048
3480
  await writeStorage(this._storage, this.apiKeyHash, persisted);
3049
- this._setAuthState({ step: "authenticated", session: persisted });
3481
+ this._setAuthState({ step: "authenticated", session: persisted, verified: true });
3050
3482
  this._scheduleNextRefresh();
3051
3483
  }
3052
3484
  async _clearSession() {
3053
- console.info("[PollarClient] Session cleared");
3485
+ this._log.info("[PollarClient] Session cleared");
3054
3486
  this._clearRefreshTimer();
3055
3487
  this._session = null;
3056
3488
  this._profile = null;
@@ -3059,7 +3491,7 @@ var PollarClient = class {
3059
3491
  try {
3060
3492
  await this._keyManager.reset();
3061
3493
  } catch (err) {
3062
- console.warn("[PollarClient] KeyManager reset failed during clearSession", err);
3494
+ this._log.warn("[PollarClient] KeyManager reset failed during clearSession", err);
3063
3495
  }
3064
3496
  await removeStorage(this._storage, this.apiKeyHash);
3065
3497
  this._transactionState = null;
@@ -3071,17 +3503,17 @@ var PollarClient = class {
3071
3503
  _setNetworkState(next) {
3072
3504
  this._networkState = next;
3073
3505
  const label = next.step === "connected" ? next.network : next.step;
3074
- console.info(`[PollarClient] network:${label}`);
3506
+ this._log.debug(`[PollarClient] network:${label}`);
3075
3507
  for (const cb of this._networkStateListeners) cb(next);
3076
3508
  }
3077
3509
  _setAuthState(next) {
3078
3510
  this._authState = next;
3079
- console.info(`[PollarClient] auth:${next.step}`);
3511
+ this._log.debug(`[PollarClient] auth:${next.step}`);
3080
3512
  for (const cb of this._authStateListeners) cb(next);
3081
3513
  }
3082
3514
  _setTransactionState(next) {
3083
3515
  this._transactionState = next;
3084
- console.info(`[PollarClient] transaction:${next.step}`);
3516
+ this._log.debug(`[PollarClient] transaction:${next.step}`);
3085
3517
  for (const cb of this._transactionStateListeners) cb(next);
3086
3518
  }
3087
3519
  /**
@@ -3132,6 +3564,7 @@ _setDefaultKeyManagerFactory((_storage, apiKey) => new WebCryptoKeyManager(apiKe
3132
3564
  exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
3133
3565
  exports.AlbedoAdapter = AlbedoAdapter;
3134
3566
  exports.FreighterAdapter = FreighterAdapter;
3567
+ exports.POLLAR_CORE_VERSION = POLLAR_CORE_VERSION;
3135
3568
  exports.PollarClient = PollarClient;
3136
3569
  exports.StellarClient = StellarClient;
3137
3570
  exports.WalletType = WalletType;
@@ -3141,6 +3574,7 @@ exports.canonicalEcJwk = canonicalEcJwk;
3141
3574
  exports.claimDistributionRule = claimDistributionRule;
3142
3575
  exports.computeJwkThumbprint = computeJwkThumbprint;
3143
3576
  exports.createLocalStorageAdapter = createLocalStorageAdapter;
3577
+ exports.createLogger = createLogger;
3144
3578
  exports.createMemoryAdapter = createMemoryAdapter;
3145
3579
  exports.createOffRamp = createOffRamp;
3146
3580
  exports.createOnRamp = createOnRamp;