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