@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.rn.js CHANGED
@@ -267,11 +267,14 @@ async function computeJwkThumbprint(jwk) {
267
267
  const digest = await sha256(new TextEncoder().encode(canonical));
268
268
  return base64urlEncode(digest);
269
269
  }
270
+ function toBase64url(value) {
271
+ return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/[^A-Za-z0-9_-]/g, "");
272
+ }
270
273
  function canonicalEcJwk(jwk) {
271
274
  if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
272
275
  throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
273
276
  }
274
- return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
277
+ return { kty: "EC", crv: "P-256", x: toBase64url(jwk.x), y: toBase64url(jwk.y) };
275
278
  }
276
279
 
277
280
  // src/keys/noble.ts
@@ -301,7 +304,6 @@ var NobleKeyManager = class {
301
304
  if (this.privateKey) return;
302
305
  if (!this._initPromise) {
303
306
  this._initPromise = this._doInit().catch((err) => {
304
- console.error("[PollarClient:keys] NobleKeyManager init failed", err);
305
307
  this._initPromise = null;
306
308
  throw err;
307
309
  });
@@ -377,167 +379,6 @@ var NobleKeyManager = class {
377
379
  }
378
380
  };
379
381
 
380
- // src/keys/web-crypto.ts
381
- var DB_NAME = "pollar-keys";
382
- var DB_VERSION = 1;
383
- var STORE_NAME = "keys";
384
- function openDb() {
385
- return new Promise((resolve, reject) => {
386
- if (typeof indexedDB === "undefined") {
387
- reject(new Error("[PollarClient:keys] IndexedDB not available"));
388
- return;
389
- }
390
- const req = indexedDB.open(DB_NAME, DB_VERSION);
391
- req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
392
- req.onsuccess = () => resolve(req.result);
393
- req.onupgradeneeded = () => {
394
- const db = req.result;
395
- if (!db.objectStoreNames.contains(STORE_NAME)) {
396
- db.createObjectStore(STORE_NAME);
397
- }
398
- };
399
- });
400
- }
401
- function awaitTx(req) {
402
- return new Promise((resolve, reject) => {
403
- req.onsuccess = () => resolve(req.result);
404
- req.onerror = () => reject(req.error ?? new Error("IDB request failed"));
405
- });
406
- }
407
- async function dbGet(key) {
408
- const db = await openDb();
409
- try {
410
- const tx = db.transaction(STORE_NAME, "readonly");
411
- const result = await awaitTx(tx.objectStore(STORE_NAME).get(key));
412
- return result;
413
- } finally {
414
- db.close();
415
- }
416
- }
417
- async function dbPut(key, value) {
418
- const db = await openDb();
419
- try {
420
- const tx = db.transaction(STORE_NAME, "readwrite");
421
- await awaitTx(tx.objectStore(STORE_NAME).put(value, key));
422
- } finally {
423
- db.close();
424
- }
425
- }
426
- async function dbDelete(key) {
427
- const db = await openDb();
428
- try {
429
- const tx = db.transaction(STORE_NAME, "readwrite");
430
- await awaitTx(tx.objectStore(STORE_NAME).delete(key));
431
- } finally {
432
- db.close();
433
- }
434
- }
435
- function isCryptoKeyPair(v) {
436
- if (typeof v !== "object" || v === null) return false;
437
- const obj = v;
438
- return obj.privateKey !== void 0 && obj.publicKey !== void 0;
439
- }
440
- var WebCryptoKeyManager = class {
441
- constructor(apiKey) {
442
- this.apiKeyHash = null;
443
- this.keyPair = null;
444
- this.publicJwk = null;
445
- this.thumbprint = null;
446
- /**
447
- * Cached in-flight init. Lets `init()` be called concurrently (or implicitly
448
- * from `getPublicJwk` / `sign`) without doing the work twice. Cleared on
449
- * failure so callers can retry, and cleared on `reset()`.
450
- */
451
- this._initPromise = null;
452
- if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
453
- throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
454
- }
455
- this.apiKey = apiKey;
456
- }
457
- /**
458
- * Idempotent and safe under concurrency. The first call kicks off the real
459
- * init; subsequent (and concurrent) calls return the same in-flight promise.
460
- * Other methods (`getPublicJwk`, `getThumbprint`, `sign`) auto-await this so
461
- * the manager is self-healing if `init()` was never explicitly invoked.
462
- */
463
- async init() {
464
- if (this.keyPair) return;
465
- if (!this._initPromise) {
466
- this._initPromise = this._doInit().catch((err) => {
467
- console.error("[PollarClient:keys] WebCryptoKeyManager init failed", err);
468
- this._initPromise = null;
469
- throw err;
470
- });
471
- }
472
- return this._initPromise;
473
- }
474
- async _doInit() {
475
- if (!this.apiKeyHash) {
476
- this.apiKeyHash = await hashApiKey(this.apiKey);
477
- }
478
- let pair;
479
- try {
480
- pair = await dbGet(this.apiKeyHash);
481
- if (pair && !isCryptoKeyPair(pair)) pair = void 0;
482
- } catch {
483
- pair = void 0;
484
- }
485
- if (!pair) {
486
- pair = await globalThis.crypto.subtle.generateKey(
487
- { name: "ECDSA", namedCurve: "P-256" },
488
- // false → private key non-extractable; per W3C ECDSA spec the public
489
- // key is always extractable regardless of this flag.
490
- false,
491
- ["sign", "verify"]
492
- );
493
- try {
494
- await dbPut(this.apiKeyHash, pair);
495
- } catch {
496
- }
497
- }
498
- this.keyPair = pair;
499
- const exported = await globalThis.crypto.subtle.exportKey("jwk", pair.publicKey);
500
- this.publicJwk = canonicalEcJwk(exported);
501
- this.thumbprint = await computeJwkThumbprint(this.publicJwk);
502
- }
503
- async reset() {
504
- try {
505
- if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
506
- } catch {
507
- }
508
- this.keyPair = null;
509
- this.publicJwk = null;
510
- this.thumbprint = null;
511
- this._initPromise = null;
512
- }
513
- async getPublicJwk() {
514
- if (!this.publicJwk) await this.init();
515
- if (!this.publicJwk) {
516
- throw new Error("[PollarClient:keys] Keypair initialization failed; getPublicJwk unavailable");
517
- }
518
- return { kty: this.publicJwk.kty, crv: this.publicJwk.crv, x: this.publicJwk.x, y: this.publicJwk.y };
519
- }
520
- async getThumbprint() {
521
- if (!this.thumbprint) await this.init();
522
- if (!this.thumbprint) {
523
- throw new Error("[PollarClient:keys] Keypair initialization failed; getThumbprint unavailable");
524
- }
525
- return this.thumbprint;
526
- }
527
- async sign(payload) {
528
- if (!this.keyPair) await this.init();
529
- if (!this.keyPair) {
530
- throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
531
- }
532
- const sig = await globalThis.crypto.subtle.sign(
533
- { name: "ECDSA", hash: "SHA-256" },
534
- this.keyPair.privateKey,
535
- payload
536
- );
537
- return new Uint8Array(sig);
538
- }
539
- };
540
-
541
382
  // ../../node_modules/openapi-fetch/dist/index.mjs
542
383
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
543
384
  var supportsRequestInitExt = () => {
@@ -1170,6 +1011,16 @@ function normalizeHtu(rawUrl) {
1170
1011
  return `${scheme}//${host}${portPart}${url.pathname}`;
1171
1012
  }
1172
1013
 
1014
+ // src/lib/logger.ts
1015
+ var RANK = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
1016
+ function createLogger(level = "info", sink = console) {
1017
+ const threshold = RANK[level];
1018
+ const gate = (lvl) => (...args) => {
1019
+ if (threshold >= RANK[lvl]) sink[lvl](...args);
1020
+ };
1021
+ return { error: gate("error"), warn: gate("warn"), info: gate("info"), debug: gate("debug") };
1022
+ }
1023
+
1173
1024
  // src/storage/web.ts
1174
1025
  var LOG_PREFIX = "[PollarClient:storage]";
1175
1026
  function createMemoryAdapter() {
@@ -1193,7 +1044,7 @@ function createLocalStorageAdapter(options = {}) {
1193
1044
  function degrade(reason, error) {
1194
1045
  if (degraded) return;
1195
1046
  degraded = true;
1196
- console.warn(`${LOG_PREFIX} localStorage unavailable (${reason}); degrading to in-memory storage`);
1047
+ (options.logger ?? console).warn(`${LOG_PREFIX} localStorage unavailable (${reason}); degrading to in-memory storage`);
1197
1048
  options.onDegrade?.(reason, error);
1198
1049
  }
1199
1050
  return {
@@ -1257,6 +1108,9 @@ function defaultStorage(options = {}) {
1257
1108
  return createLocalStorageAdapter(options);
1258
1109
  }
1259
1110
 
1111
+ // src/version.ts
1112
+ var POLLAR_CORE_VERSION = "0.9.0-rc.1" ;
1113
+
1260
1114
  // src/visibility/noop.ts
1261
1115
  function createNoopVisibilityProvider() {
1262
1116
  return {
@@ -1321,6 +1175,7 @@ var AUTH_ERROR_CODES = {
1321
1175
  WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
1322
1176
  WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
1323
1177
  WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
1178
+ PASSKEY_FAILED: "PASSKEY_FAILED",
1324
1179
  UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
1325
1180
  };
1326
1181
  var PollarFlowError = class extends Error {
@@ -1366,7 +1221,7 @@ var FreighterAdapter = class {
1366
1221
  if (!userInfo?.publicKey) {
1367
1222
  throw new Error("Failed to get user information from Freighter");
1368
1223
  }
1369
- return { address: userInfo.publicKey, publicKey: userInfo.publicKey };
1224
+ return { address: userInfo.publicKey };
1370
1225
  }
1371
1226
  async disconnect() {
1372
1227
  }
@@ -1404,6 +1259,19 @@ var FreighterAdapter = class {
1404
1259
  };
1405
1260
 
1406
1261
  // src/wallets/AlbedoAdapter.ts
1262
+ var PUBLIC_PASSPHRASE = "Public Global Stellar Network ; September 2015";
1263
+ var TESTNET_PASSPHRASE = "Test SDF Network ; September 2015";
1264
+ function albedoNetwork(options, fallback) {
1265
+ switch (options?.networkPassphrase) {
1266
+ case PUBLIC_PASSPHRASE:
1267
+ return "public";
1268
+ case TESTNET_PASSPHRASE:
1269
+ return "testnet";
1270
+ }
1271
+ if (options?.network === "public" || options?.network === "mainnet") return "public";
1272
+ if (options?.network === "testnet") return "testnet";
1273
+ return fallback;
1274
+ }
1407
1275
  function openAlbedoPopup(url) {
1408
1276
  const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
1409
1277
  if (!popup) {
@@ -1442,7 +1310,13 @@ function waitForAlbedoResult() {
1442
1310
  });
1443
1311
  }
1444
1312
  var AlbedoAdapter = class {
1445
- constructor() {
1313
+ /**
1314
+ * Network used for `connect` and `signAuthEntry` (which carry no per-call
1315
+ * network) and as the fallback for `signTransaction`. Defaults to `'testnet'`
1316
+ * to preserve the previous behavior when constructed with no argument.
1317
+ */
1318
+ constructor(network = "testnet") {
1319
+ this.network = network;
1446
1320
  this.type = "albedo" /* ALBEDO */;
1447
1321
  }
1448
1322
  async isAvailable() {
@@ -1452,7 +1326,7 @@ var AlbedoAdapter = class {
1452
1326
  const url = new URL("https://albedo.link");
1453
1327
  url.searchParams.set("intent", "public-key");
1454
1328
  url.searchParams.set("app_name", "Pollar");
1455
- url.searchParams.set("network", "testnet");
1329
+ url.searchParams.set("network", this.network);
1456
1330
  url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1457
1331
  url.searchParams.set("origin", window.location.origin);
1458
1332
  openAlbedoPopup(url.toString());
@@ -1460,7 +1334,7 @@ var AlbedoAdapter = class {
1460
1334
  if (!result.pubkey) {
1461
1335
  throw new Error("Albedo connection rejected");
1462
1336
  }
1463
- return { address: result.pubkey, publicKey: result.pubkey };
1337
+ return { address: result.pubkey };
1464
1338
  }
1465
1339
  async disconnect() {
1466
1340
  }
@@ -1470,12 +1344,12 @@ var AlbedoAdapter = class {
1470
1344
  async getNetwork() {
1471
1345
  throw new Error("Albedo does not expose network");
1472
1346
  }
1473
- async signTransaction(xdr, _options) {
1347
+ async signTransaction(xdr, options) {
1474
1348
  const url = new URL("https://albedo.link");
1475
1349
  url.searchParams.set("intent", "tx");
1476
1350
  url.searchParams.set("xdr", xdr);
1477
1351
  url.searchParams.set("app_name", "Pollar");
1478
- url.searchParams.set("network", "testnet");
1352
+ url.searchParams.set("network", albedoNetwork(options, this.network));
1479
1353
  url.searchParams.set("callback", window.location.href);
1480
1354
  url.searchParams.set("origin", window.location.origin);
1481
1355
  window.location.href = url.toString();
@@ -1488,7 +1362,7 @@ var AlbedoAdapter = class {
1488
1362
  url.searchParams.set("intent", "sign-auth-entry");
1489
1363
  url.searchParams.set("xdr", entryXdr);
1490
1364
  url.searchParams.set("app_name", "Pollar");
1491
- url.searchParams.set("network", "testnet");
1365
+ url.searchParams.set("network", this.network);
1492
1366
  url.searchParams.set("callback", window.location.href);
1493
1367
  url.searchParams.set("origin", window.location.origin);
1494
1368
  window.location.href = url.toString();
@@ -1519,84 +1393,98 @@ function isBoundedString(v, max, allowEmpty = false) {
1519
1393
  if (!allowEmpty && v.length === 0) return false;
1520
1394
  return v.length <= max;
1521
1395
  }
1522
- function isValidSession(value) {
1396
+ function isValidSession(value, logger = console) {
1523
1397
  if (typeof value !== "object" || value === null) {
1524
- console.warn("[PollarClient:session] Invalid session \u2014 value is not an object");
1398
+ logger.debug("[PollarClient:session] Invalid session \u2014 value is not an object");
1525
1399
  return false;
1526
1400
  }
1527
1401
  const s = value;
1528
1402
  if (!isBoundedString(s["clientSessionId"], MAX_CLIENT_SESSION_ID)) {
1529
- console.warn("[PollarClient:session] Invalid session \u2014 clientSessionId missing/empty/too long");
1403
+ logger.debug("[PollarClient:session] Invalid session \u2014 clientSessionId missing/empty/too long");
1530
1404
  return false;
1531
1405
  }
1532
1406
  if (s["userId"] !== null && !isBoundedString(s["userId"], MAX_USER_ID)) {
1533
- console.warn("[PollarClient:session] Invalid session \u2014 userId must be string|null");
1407
+ logger.debug("[PollarClient:session] Invalid session \u2014 userId must be string|null");
1534
1408
  return false;
1535
1409
  }
1536
1410
  if (!isBoundedString(s["status"], MAX_STATUS)) {
1537
- console.warn("[PollarClient:session] Invalid session \u2014 status must be string");
1411
+ logger.debug("[PollarClient:session] Invalid session \u2014 status must be string");
1538
1412
  return false;
1539
1413
  }
1540
1414
  const token = s["token"];
1541
1415
  if (typeof token !== "object" || token === null) {
1542
- console.warn("[PollarClient:session] Invalid session \u2014 token missing or not an object");
1416
+ logger.debug("[PollarClient:session] Invalid session \u2014 token missing or not an object");
1543
1417
  return false;
1544
1418
  }
1545
1419
  const t = token;
1546
1420
  if (!isBoundedString(t["accessToken"], MAX_ACCESS_TOKEN)) {
1547
- console.warn("[PollarClient:session] Invalid session \u2014 token.accessToken missing/empty/too long");
1421
+ logger.debug("[PollarClient:session] Invalid session \u2014 token.accessToken missing/empty/too long");
1548
1422
  return false;
1549
1423
  }
1550
1424
  if (!isBoundedString(t["refreshToken"], MAX_REFRESH_TOKEN)) {
1551
- console.warn("[PollarClient:session] Invalid session \u2014 token.refreshToken missing/empty/too long");
1425
+ logger.debug("[PollarClient:session] Invalid session \u2014 token.refreshToken missing/empty/too long");
1552
1426
  return false;
1553
1427
  }
1554
1428
  if (typeof t["expiresAt"] !== "number" || !Number.isFinite(t["expiresAt"])) {
1555
- console.warn("[PollarClient:session] Invalid session \u2014 token.expiresAt must be a finite number");
1429
+ logger.debug("[PollarClient:session] Invalid session \u2014 token.expiresAt must be a finite number");
1556
1430
  return false;
1557
1431
  }
1558
1432
  const user = s["user"];
1559
1433
  if (typeof user !== "object" || user === null) {
1560
- console.warn("[PollarClient:session] Invalid session \u2014 user missing or not an object");
1434
+ logger.debug("[PollarClient:session] Invalid session \u2014 user missing or not an object");
1561
1435
  return false;
1562
1436
  }
1563
1437
  const u = user;
1564
1438
  if (u["id"] !== void 0 && !isBoundedString(u["id"], MAX_USER_ID)) {
1565
- console.warn("[PollarClient:session] Invalid session \u2014 user.id must be string if present");
1439
+ logger.debug("[PollarClient:session] Invalid session \u2014 user.id must be string if present");
1566
1440
  return false;
1567
1441
  }
1568
1442
  if (typeof u["ready"] !== "boolean") {
1569
- console.warn("[PollarClient:session] Invalid session \u2014 user.ready must be boolean");
1443
+ logger.debug("[PollarClient:session] Invalid session \u2014 user.ready must be boolean");
1570
1444
  return false;
1571
1445
  }
1572
1446
  const wallet = s["wallet"];
1573
1447
  if (typeof wallet !== "object" || wallet === null) {
1574
- console.warn("[PollarClient:session] Invalid session \u2014 wallet missing or not an object");
1448
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet missing or not an object");
1575
1449
  return false;
1576
1450
  }
1577
1451
  const w = wallet;
1578
- if (w["publicKey"] !== null && !isBoundedString(w["publicKey"], MAX_WALLET_PUBLIC_KEY)) {
1579
- console.warn("[PollarClient:session] Invalid session \u2014 wallet.publicKey must be string|null");
1452
+ if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
1453
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
1454
+ return false;
1455
+ }
1456
+ if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
1457
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
1580
1458
  return false;
1581
1459
  }
1582
1460
  if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
1583
- console.warn("[PollarClient:session] Invalid session \u2014 wallet.existsOnStellar must be boolean if present");
1461
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.existsOnStellar must be boolean if present");
1584
1462
  return false;
1585
1463
  }
1586
1464
  if (w["createdAt"] !== void 0 && (typeof w["createdAt"] !== "number" || !Number.isFinite(w["createdAt"]))) {
1587
- console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
1465
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
1466
+ return false;
1467
+ }
1468
+ if (w["linkedAt"] !== void 0 && (typeof w["linkedAt"] !== "number" || !Number.isFinite(w["linkedAt"]))) {
1469
+ logger.debug("[PollarClient:session] Invalid session \u2014 wallet.linkedAt must be a finite number if present");
1588
1470
  return false;
1589
1471
  }
1590
1472
  return true;
1591
1473
  }
1592
- async function readStorage(storage, apiKeyHash) {
1474
+ async function readStorage(storage, apiKeyHash, logger = console) {
1593
1475
  const raw = await storage.get(sessionStorageKey(apiKeyHash));
1594
1476
  if (!raw) return null;
1595
1477
  try {
1596
1478
  const session = JSON.parse(raw);
1597
- if (!isValidSession(session)) {
1479
+ if (typeof session === "object" && session !== null) {
1480
+ const w = session.wallet;
1481
+ if (w && w["address"] == null && typeof w["publicKey"] === "string") {
1482
+ w["address"] = w["publicKey"];
1483
+ }
1484
+ }
1485
+ if (!isValidSession(session, logger)) {
1598
1486
  await storage.remove(sessionStorageKey(apiKeyHash));
1599
- console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
1487
+ logger.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
1600
1488
  return null;
1601
1489
  }
1602
1490
  if (session.token.expiresAt * 1e3 < Date.now()) {
@@ -1604,7 +1492,7 @@ async function readStorage(storage, apiKeyHash) {
1604
1492
  }
1605
1493
  return session;
1606
1494
  } catch (error) {
1607
- console.error("[PollarClient:session] Failed to parse session from storage", error);
1495
+ logger.error("[PollarClient:session] Failed to parse session from storage", error);
1608
1496
  await storage.remove(sessionStorageKey(apiKeyHash));
1609
1497
  return null;
1610
1498
  }
@@ -1666,7 +1554,7 @@ function abortableDelay(ms, signal) {
1666
1554
  });
1667
1555
  }
1668
1556
  var MAX_BACKOFF_MS = 5e3;
1669
- async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200, signal) {
1557
+ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200, signal, logger = console) {
1670
1558
  let backoff = retryDelayMs;
1671
1559
  const sleep = async (ms) => {
1672
1560
  if (ms <= 0) return;
@@ -1684,7 +1572,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1684
1572
  }));
1685
1573
  } catch (e) {
1686
1574
  if (e instanceof Error && e.name === "AbortError") throw e;
1687
- console.warn(e);
1575
+ logger.debug("[PollarClient:stream] session-status request failed; will retry", e);
1688
1576
  }
1689
1577
  if (error || !data) {
1690
1578
  await sleep(backoff);
@@ -1724,7 +1612,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1724
1612
  } catch (e) {
1725
1613
  if (e instanceof Error && e.name === "AbortError") throw e;
1726
1614
  if (e instanceof SessionStatusError) throw e;
1727
- console.warn(e);
1615
+ logger.debug("[PollarClient:stream] session-status stream read failed; will retry", e);
1728
1616
  } finally {
1729
1617
  reader.releaseLock();
1730
1618
  }
@@ -1734,7 +1622,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1734
1622
  if (delay) await sleep(delay);
1735
1623
  }
1736
1624
  }
1737
- async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal) {
1625
+ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500, signal, logger = console) {
1738
1626
  const url = `${baseUrl}/auth/session/status/${encodeURIComponent(clientSessionId)}/poll`;
1739
1627
  let backoff = intervalMs;
1740
1628
  const sleep = async (ms) => {
@@ -1752,7 +1640,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
1752
1640
  envelope = await response.json().catch(() => null);
1753
1641
  } catch (e) {
1754
1642
  if (e instanceof Error && e.name === "AbortError") throw e;
1755
- console.warn(e);
1643
+ logger.debug("[PollarClient:stream] session-status poll failed; will retry", e);
1756
1644
  }
1757
1645
  if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
1758
1646
  throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
@@ -1769,13 +1657,13 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
1769
1657
  }
1770
1658
  }
1771
1659
  function waitForSessionReady(args) {
1772
- const { api, baseUrl, clientSessionId, check, useStreaming, retryDelayMs, signal } = args;
1773
- return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal);
1660
+ const { api, baseUrl, clientSessionId, check, useStreaming, retryDelayMs, signal, logger = console } = args;
1661
+ return useStreaming ? streamUntilFound(api, clientSessionId, check, retryDelayMs ?? 200, signal, logger) : pollUntilFound(baseUrl, clientSessionId, check, retryDelayMs ?? 500, signal, logger);
1774
1662
  }
1775
1663
 
1776
1664
  // src/client/auth/authenticate.ts
1777
1665
  async function authenticate(clientSessionId, deps, expectedWallet) {
1778
- const { api, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
1666
+ const { api, logger, basePath, useStreaming, signal, setAuthState, storeSession, clearSession } = deps;
1779
1667
  setAuthState({ step: "authenticating" });
1780
1668
  try {
1781
1669
  await waitForSessionReady({
@@ -1784,7 +1672,8 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1784
1672
  clientSessionId,
1785
1673
  check: (data2) => data2?.status === "READY",
1786
1674
  useStreaming,
1787
- signal
1675
+ signal,
1676
+ logger
1788
1677
  });
1789
1678
  } catch (err) {
1790
1679
  if (err instanceof SessionStatusError) {
@@ -1809,7 +1698,7 @@ async function authenticate(clientSessionId, deps, expectedWallet) {
1809
1698
  },
1810
1699
  signal
1811
1700
  });
1812
- if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content)) {
1701
+ if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content, logger)) {
1813
1702
  if (expectedWallet && data.content.data.providers.wallet?.address !== expectedWallet) {
1814
1703
  setAuthState({
1815
1704
  step: "error",
@@ -1956,6 +1845,55 @@ async function loginOAuth(provider, deps) {
1956
1845
  await authenticate(clientSessionId, deps);
1957
1846
  }
1958
1847
 
1848
+ // src/client/auth/passkeyFlow.ts
1849
+ async function loginSmartWallet(deps) {
1850
+ const { api, signal, setAuthState, passkey } = deps;
1851
+ if (!passkey) {
1852
+ setAuthState({
1853
+ step: "error",
1854
+ previousStep: "creating_session",
1855
+ message: "Passkey support is not configured",
1856
+ errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
1857
+ });
1858
+ return;
1859
+ }
1860
+ const clientSessionId = await createAuthSession(deps);
1861
+ if (!clientSessionId) return;
1862
+ try {
1863
+ const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
1864
+ body: { clientSessionId },
1865
+ signal
1866
+ });
1867
+ const challenge = challengeData?.content?.challenge;
1868
+ if (!challengeData?.success || !challenge) {
1869
+ return failPasskey(setAuthState, "Failed to start passkey");
1870
+ }
1871
+ setAuthState({ step: "creating_passkey" });
1872
+ const ceremony = await passkey({ challenge });
1873
+ const response = ceremony.response;
1874
+ if (ceremony.kind === "register") {
1875
+ setAuthState({ step: "deploying_smart_account" });
1876
+ const { data } = await api.POST("/auth/passkey/register", {
1877
+ body: { clientSessionId, response },
1878
+ signal
1879
+ });
1880
+ if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
1881
+ } else {
1882
+ const { data } = await api.POST("/auth/passkey/login", {
1883
+ body: { clientSessionId, response },
1884
+ signal
1885
+ });
1886
+ if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
1887
+ }
1888
+ } catch {
1889
+ return failPasskey(setAuthState, "Passkey login failed");
1890
+ }
1891
+ await authenticate(clientSessionId, deps);
1892
+ }
1893
+ function failPasskey(setAuthState, message) {
1894
+ setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
1895
+ }
1896
+
1959
1897
  // src/client/auth/walletFlow.ts
1960
1898
  function withSignal(promise, signal) {
1961
1899
  return Promise.race([
@@ -1982,12 +1920,12 @@ async function loginWallet(type, deps) {
1982
1920
  setAuthState({ step: "wallet_not_installed", walletType: type });
1983
1921
  return;
1984
1922
  }
1985
- const { publicKey } = await withSignal(adapter.connect(), signal);
1986
- connectedWallet = publicKey;
1923
+ const { address } = await withSignal(adapter.connect(), signal);
1924
+ connectedWallet = address;
1987
1925
  deps.storeWalletAdapter(adapter, type);
1988
1926
  setAuthState({ step: "authenticating_wallet" });
1989
1927
  const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
1990
- body: { clientSessionId, walletAddress: publicKey },
1928
+ body: { clientSessionId, walletAddress: address },
1991
1929
  signal
1992
1930
  });
1993
1931
  if (walletError || !walletData?.success) {
@@ -2052,8 +1990,12 @@ var PollarClient = class {
2052
1990
  this._transactionStateListeners = /* @__PURE__ */ new Set();
2053
1991
  this._txHistoryState = { step: "idle" };
2054
1992
  this._txHistoryStateListeners = /* @__PURE__ */ new Set();
1993
+ this._sessionsState = { step: "idle" };
1994
+ this._sessionsStateListeners = /* @__PURE__ */ new Set();
2055
1995
  this._walletBalanceState = { step: "idle" };
2056
1996
  this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
1997
+ this._enabledAssetsState = { step: "idle" };
1998
+ this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
2057
1999
  this._authState = { step: "idle" };
2058
2000
  this._authStateListeners = /* @__PURE__ */ new Set();
2059
2001
  this._networkState = { step: "idle" };
@@ -2069,10 +2011,14 @@ var PollarClient = class {
2069
2011
  this._storageDegradeListeners = /* @__PURE__ */ new Set();
2070
2012
  this._walletAdapter = null;
2071
2013
  this._loginController = null;
2014
+ /** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
2015
+ this._resumeController = null;
2072
2016
  this.apiKey = config.apiKey;
2073
2017
  this.id = randomUUID();
2074
2018
  this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
2019
+ this._log = createLogger(config.logLevel ?? "info", config.logger);
2075
2020
  this._storage = config.storage ?? defaultStorage({
2021
+ logger: this._log,
2076
2022
  onDegrade: (reason, error) => {
2077
2023
  config.onStorageDegrade?.(reason, error);
2078
2024
  this._dispatchStorageDegrade(reason, error);
@@ -2081,6 +2027,8 @@ var PollarClient = class {
2081
2027
  this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
2082
2028
  this._walletAdapterResolver = config.walletAdapter ?? null;
2083
2029
  this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
2030
+ this._passkey = config.passkey ?? null;
2031
+ this._passkeySign = config.passkeySign ?? null;
2084
2032
  this._deviceLabel = config.deviceLabel;
2085
2033
  this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
2086
2034
  this._maxIdleMs = config.maxIdleMs;
@@ -2094,7 +2042,9 @@ var PollarClient = class {
2094
2042
  this._initialized = Promise.resolve();
2095
2043
  return;
2096
2044
  }
2097
- console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`);
2045
+ this._log.info(
2046
+ `[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
2047
+ );
2098
2048
  this._initialized = this._initialize();
2099
2049
  }
2100
2050
  /**
@@ -2119,7 +2069,7 @@ var PollarClient = class {
2119
2069
  const sessionKey = sessionStorageKey(this._apiKeyHash);
2120
2070
  const handler = (e) => {
2121
2071
  if (e.key === sessionKey) {
2122
- this._restoreSession().catch((err) => console.error("[PollarClient] Cross-tab restore failed", err));
2072
+ this._restoreSession().catch((err) => this._log.error("[PollarClient] Cross-tab restore failed", err));
2123
2073
  }
2124
2074
  };
2125
2075
  window.addEventListener("storage", handler);
@@ -2128,11 +2078,15 @@ var PollarClient = class {
2128
2078
  try {
2129
2079
  await this._keyManager.init();
2130
2080
  } catch (err) {
2131
- console.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
2081
+ this._log.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
2132
2082
  }
2133
2083
  await this._restoreSession();
2134
2084
  this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
2135
- if (visible) void this._maybeProactiveRefresh();
2085
+ if (!visible) return;
2086
+ void this._maybeProactiveRefresh();
2087
+ if (this._authState.step === "authenticated" && !this._authState.verified) {
2088
+ void this._resume();
2089
+ }
2136
2090
  });
2137
2091
  }
2138
2092
  /** Detach the cross-tab storage listener and abort any in-flight login. */
@@ -2143,6 +2097,8 @@ var PollarClient = class {
2143
2097
  }
2144
2098
  this._loginController?.abort();
2145
2099
  this._loginController = null;
2100
+ this._resumeController?.abort();
2101
+ this._resumeController = null;
2146
2102
  this._clearRefreshTimer();
2147
2103
  if (this._visibilityUnsubscribe) {
2148
2104
  this._visibilityUnsubscribe();
@@ -2157,11 +2113,13 @@ var PollarClient = class {
2157
2113
  request.headers.set("x-pollar-api-key", self.apiKey);
2158
2114
  self._lastRequestAt = Date.now();
2159
2115
  await self._initialized;
2160
- if (request.body !== null) {
2116
+ const cacheMethod = request.method.toUpperCase();
2117
+ const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
2118
+ if (cacheBodyAllowed && request.body != null) {
2161
2119
  try {
2162
2120
  self._requestBodyCache.set(request, await request.clone().arrayBuffer());
2163
2121
  } catch (err) {
2164
- console.warn("[PollarClient] Could not snapshot request body for retry", err);
2122
+ this._log.warn("[PollarClient] Could not snapshot request body for retry", err);
2165
2123
  }
2166
2124
  }
2167
2125
  const isRefresh = request.url.includes("/auth/refresh");
@@ -2220,7 +2178,7 @@ var PollarClient = class {
2220
2178
  this._keyManager
2221
2179
  );
2222
2180
  } catch (err) {
2223
- console.warn("[PollarClient] DPoP proof build failed", err);
2181
+ this._log.warn("[PollarClient] DPoP proof build failed", err);
2224
2182
  return null;
2225
2183
  }
2226
2184
  }
@@ -2244,7 +2202,9 @@ var PollarClient = class {
2244
2202
  }
2245
2203
  }
2246
2204
  }
2247
- const cachedBody = this._requestBodyCache.get(originalRequest);
2205
+ const retryMethod = originalRequest.method.toUpperCase();
2206
+ const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
2207
+ const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
2248
2208
  const retried = new Request(originalRequest.url, {
2249
2209
  method: originalRequest.method,
2250
2210
  headers,
@@ -2272,7 +2232,7 @@ var PollarClient = class {
2272
2232
  async _doRefresh() {
2273
2233
  const refreshToken = this._session?.token?.refreshToken;
2274
2234
  if (!refreshToken) {
2275
- console.warn("[PollarClient] Refresh skipped: no refresh token in session");
2235
+ this._log.warn("[PollarClient] Refresh skipped: no refresh token in session");
2276
2236
  await this._clearSession();
2277
2237
  throw new Error("No refresh token available");
2278
2238
  }
@@ -2283,24 +2243,31 @@ var PollarClient = class {
2283
2243
  data = response.data;
2284
2244
  error = response.error;
2285
2245
  } catch (err) {
2286
- console.error("[PollarClient] /auth/refresh request threw", err);
2246
+ this._log.error("[PollarClient] /auth/refresh request threw", err);
2287
2247
  await this._clearSession();
2288
2248
  throw err;
2289
2249
  }
2290
2250
  if (error || !data) {
2291
- console.warn("[PollarClient] /auth/refresh returned error", { error });
2251
+ this._log.error("[PollarClient] /auth/refresh returned error", { error });
2292
2252
  await this._clearSession();
2293
2253
  throw new Error("Refresh failed");
2294
2254
  }
2295
2255
  const successData = data;
2296
2256
  if (!successData.success || !successData.content?.token) {
2297
- console.warn("[PollarClient] /auth/refresh response malformed", successData);
2257
+ this._log.error("[PollarClient] /auth/refresh response malformed", {
2258
+ success: successData.success,
2259
+ hasToken: !!successData.content?.token
2260
+ });
2298
2261
  await this._clearSession();
2299
2262
  throw new Error("Refresh response malformed");
2300
2263
  }
2301
2264
  const newToken = successData.content.token;
2302
2265
  if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
2303
- console.warn("[PollarClient] /auth/refresh token shape invalid", newToken);
2266
+ this._log.error("[PollarClient] /auth/refresh token shape invalid", {
2267
+ accessToken: typeof newToken.accessToken,
2268
+ refreshToken: typeof newToken.refreshToken,
2269
+ expiresAt: typeof newToken.expiresAt
2270
+ });
2304
2271
  await this._clearSession();
2305
2272
  throw new Error("Refresh response token shape invalid");
2306
2273
  }
@@ -2308,9 +2275,9 @@ var PollarClient = class {
2308
2275
  try {
2309
2276
  this._session = { ...this._session, token: newToken };
2310
2277
  await writeStorage(this._storage, this.apiKeyHash, this._session);
2311
- console.info("[PollarClient] Tokens refreshed");
2278
+ this._log.info("[PollarClient] Tokens refreshed");
2312
2279
  } catch (err) {
2313
- console.error("[PollarClient] Failed to persist refreshed session", err);
2280
+ this._log.error("[PollarClient] Failed to persist refreshed session", err);
2314
2281
  }
2315
2282
  this._scheduleNextRefresh();
2316
2283
  }
@@ -2364,7 +2331,7 @@ var PollarClient = class {
2364
2331
  try {
2365
2332
  await this.refresh();
2366
2333
  } catch (err) {
2367
- console.warn("[PollarClient] Proactive refresh failed; session cleared", err);
2334
+ this._log.warn("[PollarClient] Proactive refresh failed; session cleared", err);
2368
2335
  }
2369
2336
  }
2370
2337
  _clearRefreshTimer() {
@@ -2410,7 +2377,7 @@ var PollarClient = class {
2410
2377
  try {
2411
2378
  cb(reason, error);
2412
2379
  } catch (err) {
2413
- console.error("[PollarClient] onStorageDegrade listener threw", err);
2380
+ this._log.error("[PollarClient] onStorageDegrade listener threw", err);
2414
2381
  }
2415
2382
  }
2416
2383
  }
@@ -2494,6 +2461,19 @@ var PollarClient = class {
2494
2461
  const controller = this._newController();
2495
2462
  loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2496
2463
  }
2464
+ /**
2465
+ * "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
2466
+ * user, creates a sponsored smart-account C-address. Requires the `passkey`
2467
+ * ceremony to be configured (e.g. via `@pollar/react`).
2468
+ */
2469
+ loginSmartWallet() {
2470
+ if (!isClientRuntime) {
2471
+ warnServerSide("loginSmartWallet");
2472
+ return;
2473
+ }
2474
+ const controller = this._newController();
2475
+ loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2476
+ }
2497
2477
  // ─── Cancel ───────────────────────────────────────────────────────────────
2498
2478
  cancelLogin() {
2499
2479
  this._loginController?.abort();
@@ -2517,20 +2497,20 @@ var PollarClient = class {
2517
2497
  warnServerSide("logout");
2518
2498
  return;
2519
2499
  }
2520
- console.info("[PollarClient] Logout requested", { everywhere: !!options.everywhere });
2500
+ this._log.info("[PollarClient] Logout requested", { everywhere: !!options.everywhere });
2521
2501
  if (this._session?.token?.accessToken) {
2522
2502
  try {
2523
2503
  await this._api.POST("/auth/logout", {
2524
2504
  body: options.everywhere ? { everywhere: true } : {}
2525
2505
  });
2526
2506
  } catch (err) {
2527
- console.warn("[PollarClient] Server logout failed (continuing with local clear)", err);
2507
+ this._log.warn("[PollarClient] Server logout failed (continuing with local clear)", err);
2528
2508
  }
2529
2509
  }
2530
2510
  try {
2531
2511
  await this._clearSession();
2532
2512
  } catch (err) {
2533
- console.warn("[PollarClient] Local logout cleanup failed", err);
2513
+ this._log.warn("[PollarClient] Local logout cleanup failed", err);
2534
2514
  }
2535
2515
  }
2536
2516
  /** Convenience: revoke every active session for this user (all devices). */
@@ -2556,6 +2536,29 @@ var PollarClient = class {
2556
2536
  }
2557
2537
  return data.content.sessions;
2558
2538
  }
2539
+ getSessionsState() {
2540
+ return this._sessionsState;
2541
+ }
2542
+ onSessionsStateChange(cb) {
2543
+ this._sessionsStateListeners.add(cb);
2544
+ cb(this._sessionsState);
2545
+ return () => this._sessionsStateListeners.delete(cb);
2546
+ }
2547
+ /**
2548
+ * Fire-and-forget variant of {@link listSessions} that drives the observable
2549
+ * `SessionsState` store instead of returning the array. UI layers subscribe
2550
+ * via `onSessionsStateChange` and stay pure readers — mirrors `fetchTxHistory`.
2551
+ */
2552
+ async fetchSessions() {
2553
+ this._setSessionsState({ step: "loading" });
2554
+ try {
2555
+ const sessions = await this.listSessions();
2556
+ this._setSessionsState({ step: "loaded", sessions });
2557
+ } catch (err) {
2558
+ const message = err instanceof Error ? err.message : "Failed to load sessions";
2559
+ this._setSessionsState({ step: "error", message });
2560
+ }
2561
+ }
2559
2562
  /**
2560
2563
  * Revoke a specific refresh-token family (a single device session). Use
2561
2564
  * `listSessions` to enumerate the familyIds. Revoking the current session
@@ -2583,6 +2586,14 @@ var PollarClient = class {
2583
2586
  getNetworkState() {
2584
2587
  return this._networkState;
2585
2588
  }
2589
+ /**
2590
+ * The client's level-gated logger (built from `logLevel` / `logger`). Exposed
2591
+ * so the runtime layer (`@pollar/react`) can route its own logs through the
2592
+ * same level and sink instead of calling `console` directly.
2593
+ */
2594
+ getLogger() {
2595
+ return this._log;
2596
+ }
2586
2597
  setNetwork(network) {
2587
2598
  this._setNetworkState({ step: "connected", network });
2588
2599
  }
@@ -2632,16 +2643,19 @@ var PollarClient = class {
2632
2643
  cb(this._walletBalanceState);
2633
2644
  return () => this._walletBalanceStateListeners.delete(cb);
2634
2645
  }
2635
- async refreshBalance(publicKey) {
2636
- const pk = publicKey ?? this._session?.wallet?.publicKey;
2637
- if (!pk) {
2646
+ /**
2647
+ * Refreshes the balances of the authenticated user's OWN wallet. The wallet
2648
+ * and network are resolved server-side from the session — no arguments. Drives
2649
+ * `walletBalanceState`. For an arbitrary wallet, use {@link getWalletBalance}.
2650
+ */
2651
+ async refreshBalance() {
2652
+ if (!this._session?.wallet?.address) {
2638
2653
  this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
2639
2654
  return;
2640
2655
  }
2641
2656
  this._setWalletBalanceState({ step: "loading" });
2642
2657
  try {
2643
- const network = this.getNetwork();
2644
- const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
2658
+ const { data, error } = await this._api.GET("/wallet/balance");
2645
2659
  if (!error && data?.success && data.content) {
2646
2660
  this._setWalletBalanceState({ step: "loaded", data: data.content });
2647
2661
  } else {
@@ -2651,6 +2665,53 @@ var PollarClient = class {
2651
2665
  this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
2652
2666
  }
2653
2667
  }
2668
+ /**
2669
+ * General-purpose balance lookup for ANY wallet on ANY network — not scoped
2670
+ * to this application. Enumerates the account's real on-chain holdings via
2671
+ * Horizon (server-side) and returns the data directly (no reactive state).
2672
+ * `network` defaults to the client's current network.
2673
+ */
2674
+ async getWalletBalance(publicKey, network) {
2675
+ const { data, error } = await this._api.GET("/wallet/{publicKey}/balance", {
2676
+ params: { path: { publicKey }, query: { network: network ?? this.getNetwork() } }
2677
+ });
2678
+ if (error || !data?.success || !data.content) {
2679
+ throw new Error("[PollarClient] Failed to load wallet balance");
2680
+ }
2681
+ return data.content;
2682
+ }
2683
+ // ─── Enabled assets ───────────────────────────────────────────────────────
2684
+ getEnabledAssetsState() {
2685
+ return this._enabledAssetsState;
2686
+ }
2687
+ onEnabledAssetsStateChange(cb) {
2688
+ this._enabledAssetsStateListeners.add(cb);
2689
+ cb(this._enabledAssetsState);
2690
+ return () => this._enabledAssetsStateListeners.delete(cb);
2691
+ }
2692
+ /**
2693
+ * Loads the application's enabled assets paired with the authenticated
2694
+ * wallet's on-chain trustline state — so the SDK knows which trustlines still
2695
+ * need to be added. Wallet and network are resolved server-side from the
2696
+ * session. Drives `enabledAssetsState`; mirrors {@link refreshBalance}.
2697
+ */
2698
+ async refreshAssets() {
2699
+ if (!this._session?.wallet?.address) {
2700
+ this._setEnabledAssetsState({ step: "error", message: "No wallet connected" });
2701
+ return;
2702
+ }
2703
+ this._setEnabledAssetsState({ step: "loading" });
2704
+ try {
2705
+ const { data, error } = await this._api.GET("/wallet/assets");
2706
+ if (!error && data?.success && data.content) {
2707
+ this._setEnabledAssetsState({ step: "loaded", data: data.content });
2708
+ } else {
2709
+ this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
2710
+ }
2711
+ } catch {
2712
+ this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
2713
+ }
2714
+ }
2654
2715
  // ─── Transactions ─────────────────────────────────────────────────────────
2655
2716
  /**
2656
2717
  * Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
@@ -2658,14 +2719,14 @@ var PollarClient = class {
2658
2719
  * inspect the result without subscribing to state changes.
2659
2720
  */
2660
2721
  async buildTx(operation, params, options) {
2661
- if (!this._session?.wallet?.publicKey) {
2722
+ if (!this._session?.wallet?.address) {
2662
2723
  const details = "No wallet connected";
2663
2724
  this._setTransactionState({ step: "error", phase: "building", details });
2664
2725
  return { status: "error", details };
2665
2726
  }
2666
2727
  const body = {
2667
2728
  network: this.getNetwork(),
2668
- publicKey: this._session.wallet.publicKey,
2729
+ address: this._session.wallet.address,
2669
2730
  operation,
2670
2731
  params,
2671
2732
  options: options ?? {}
@@ -2681,7 +2742,7 @@ var PollarClient = class {
2681
2742
  this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
2682
2743
  return { status: "error", ...details && { details } };
2683
2744
  } catch (err) {
2684
- console.error("[PollarClient] buildTx failed", err);
2745
+ this._log.error("[PollarClient] buildTx failed", err);
2685
2746
  this._setTransactionState({ step: "error", phase: "building" });
2686
2747
  return { status: "error" };
2687
2748
  }
@@ -2706,7 +2767,7 @@ var PollarClient = class {
2706
2767
  const buildData = this._currentBuildData();
2707
2768
  this._setTransactionState({ step: "signing", ...buildData && { buildData } });
2708
2769
  if (this._walletAdapter) {
2709
- const accountToSign = this._session?.wallet?.publicKey;
2770
+ const accountToSign = this._session?.wallet?.address;
2710
2771
  const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
2711
2772
  try {
2712
2773
  const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
@@ -2727,10 +2788,10 @@ var PollarClient = class {
2727
2788
  return { status: "error", ...details && { details } };
2728
2789
  }
2729
2790
  }
2730
- const publicKey = this._session?.wallet?.publicKey ?? "";
2791
+ const address = this._session?.wallet?.address ?? "";
2731
2792
  try {
2732
2793
  const { data, error } = await this._api.POST("/tx/sign", {
2733
- body: { network: this.getNetwork(), publicKey, unsignedXdr }
2794
+ body: { network: this.getNetwork(), address, unsignedXdr }
2734
2795
  });
2735
2796
  if (!error && data?.success && data.content?.signedXdr) {
2736
2797
  const { signedXdr, idempotencyKey } = data.content;
@@ -2783,12 +2844,12 @@ var PollarClient = class {
2783
2844
  const buildData = this._currentBuildData();
2784
2845
  const outcomeExtra = buildData ? { buildData } : {};
2785
2846
  this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
2786
- const publicKey = this._session?.wallet?.publicKey ?? "";
2847
+ const address = this._session?.wallet?.address ?? "";
2787
2848
  try {
2788
2849
  const { data, error } = await this._api.POST("/tx/submit", {
2789
2850
  body: {
2790
2851
  network: this.getNetwork(),
2791
- publicKey,
2852
+ address,
2792
2853
  signedXdr,
2793
2854
  ...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
2794
2855
  }
@@ -2848,6 +2909,19 @@ var PollarClient = class {
2848
2909
  * `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
2849
2910
  */
2850
2911
  async signAndSubmitTx(unsignedXdr) {
2912
+ if (this._session?.wallet?.type === "smart") {
2913
+ const buildData2 = this._currentBuildData();
2914
+ if (!buildData2?.smart) {
2915
+ const details = "no prepared smart transaction; call buildTx first";
2916
+ this._setTransactionState({ step: "error", phase: "signing", details });
2917
+ return { status: "error", details };
2918
+ }
2919
+ return this._signSubmitSmart(buildData2);
2920
+ }
2921
+ if (!unsignedXdr) {
2922
+ this._setTransactionState({ step: "error", phase: "signing", details: "missing unsigned transaction" });
2923
+ return { status: "error", details: "missing unsigned transaction" };
2924
+ }
2851
2925
  if (this._walletAdapter) {
2852
2926
  const signed = await this.signTx(unsignedXdr);
2853
2927
  if (signed.status === "error") {
@@ -2865,7 +2939,7 @@ var PollarClient = class {
2865
2939
  this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
2866
2940
  const body = {
2867
2941
  network: this.getNetwork(),
2868
- publicKey: this._session?.wallet?.publicKey ?? "",
2942
+ address: this._session?.wallet?.address ?? "",
2869
2943
  unsignedXdr
2870
2944
  };
2871
2945
  try {
@@ -2934,14 +3008,20 @@ var PollarClient = class {
2934
3008
  * `signTx`, and `submitTx` separately instead.
2935
3009
  */
2936
3010
  async buildAndSignAndSubmitTx(operation, params, options) {
3011
+ if (this._session?.wallet?.type === "smart") {
3012
+ return this._runSmartTx(operation, params, options);
3013
+ }
2937
3014
  if (this._walletAdapter) {
2938
3015
  const built = await this.buildTx(operation, params, options);
2939
3016
  if (built.status === "error") {
2940
3017
  return { status: "error", ...built.details && { details: built.details } };
2941
3018
  }
3019
+ if (!built.buildData.unsignedXdr) {
3020
+ return { status: "error", details: "build returned no unsigned transaction" };
3021
+ }
2942
3022
  return this.signAndSubmitTx(built.buildData.unsignedXdr);
2943
3023
  }
2944
- if (!this._session?.wallet?.publicKey) {
3024
+ if (!this._session?.wallet?.address) {
2945
3025
  this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
2946
3026
  return { status: "error", details: "No wallet connected" };
2947
3027
  }
@@ -2950,7 +3030,7 @@ var PollarClient = class {
2950
3030
  const { data, error } = await this._api.POST("/tx/build-sign-submit", {
2951
3031
  body: {
2952
3032
  network: this.getNetwork(),
2953
- publicKey: this._session.wallet.publicKey,
3033
+ address: this._session.wallet.address,
2954
3034
  operation,
2955
3035
  params,
2956
3036
  options: options ?? {}
@@ -2994,6 +3074,113 @@ var PollarClient = class {
2994
3074
  async runTx(operation, params, options) {
2995
3075
  return this.buildAndSignAndSubmitTx(operation, params, options);
2996
3076
  }
3077
+ /**
3078
+ * Smart-wallet (passkey / C-address) transaction: build (server prepares the
3079
+ * SAC transfer + returns the auth digest) → sign the digest with the passkey
3080
+ * → submit (server assembles the signed auth entry and broadcasts; the
3081
+ * sponsor pays the fee). State machine: building → built → signing →
3082
+ * submitting → success.
3083
+ */
3084
+ async _runSmartTx(operation, params, options) {
3085
+ const address = this._session?.wallet?.address;
3086
+ if (!address) {
3087
+ this._setTransactionState({ step: "error", phase: "building", details: "No wallet connected" });
3088
+ return { status: "error", details: "No wallet connected" };
3089
+ }
3090
+ if (!this._passkeySign) {
3091
+ const details = "Passkey signer not configured";
3092
+ this._setTransactionState({ step: "error", phase: "signing", details });
3093
+ return { status: "error", details };
3094
+ }
3095
+ this._setTransactionState({ step: "building" });
3096
+ let buildData;
3097
+ try {
3098
+ const body = {
3099
+ network: this.getNetwork(),
3100
+ address,
3101
+ operation,
3102
+ params,
3103
+ options: options ?? {}
3104
+ };
3105
+ const { data, error } = await this._api.POST("/tx/build", { body });
3106
+ if (error || !data?.success || !data.content?.smart) {
3107
+ const details = error?.details ?? "Failed to build transaction";
3108
+ this._setTransactionState({ step: "error", phase: "building", details });
3109
+ return { status: "error", details };
3110
+ }
3111
+ buildData = data.content;
3112
+ } catch (err) {
3113
+ const details = err instanceof Error ? err.message : void 0;
3114
+ this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
3115
+ return { status: "error", ...details && { details } };
3116
+ }
3117
+ this._setTransactionState({ step: "built", buildData });
3118
+ return this._signSubmitSmart(buildData);
3119
+ }
3120
+ /**
3121
+ * Steps 2–3 of the smart-wallet flow: sign the prepared auth digest with the
3122
+ * passkey, then submit. Shared by `_runSmartTx` (atomic) and `signAndSubmitTx`
3123
+ * (split flow, when a smart build is already on the state machine).
3124
+ */
3125
+ async _signSubmitSmart(buildData) {
3126
+ const address = this._session?.wallet?.address;
3127
+ const smart = buildData.smart;
3128
+ if (!address || !smart) {
3129
+ const details = "no prepared smart transaction";
3130
+ this._setTransactionState({ step: "error", phase: "signing", buildData, details });
3131
+ return { status: "error", buildData, details };
3132
+ }
3133
+ if (!this._passkeySign) {
3134
+ const details = "Passkey signer not configured";
3135
+ this._setTransactionState({ step: "error", phase: "signing", buildData, details });
3136
+ return { status: "error", buildData, details };
3137
+ }
3138
+ this._setTransactionState({ step: "signing", buildData });
3139
+ let assertion;
3140
+ try {
3141
+ assertion = await this._passkeySign({ credentialId: smart.credentialId, challenge: smart.digest });
3142
+ } catch (err) {
3143
+ const details = err instanceof Error ? err.message : void 0;
3144
+ this._setTransactionState({ step: "error", phase: "signing", buildData, ...details && { details } });
3145
+ return { status: "error", buildData, ...details && { details } };
3146
+ }
3147
+ this._setTransactionState({ step: "submitting", buildData });
3148
+ const outcomeExtra = { buildData };
3149
+ try {
3150
+ const { data, error } = await this._api.POST("/tx/submit", {
3151
+ body: {
3152
+ network: this.getNetwork(),
3153
+ address,
3154
+ smart: { entryXdr: smart.entryXdr, funcXdr: smart.funcXdr, assertion }
3155
+ }
3156
+ });
3157
+ if (!error && data?.success && data.content) {
3158
+ const { hash, status: backendStatus, resultCode } = data.content;
3159
+ if (backendStatus === "SUCCESS") {
3160
+ this._setTransactionState({ step: "success", hash, buildData });
3161
+ return { status: "success", hash, ...outcomeExtra };
3162
+ }
3163
+ if (backendStatus === "PENDING") {
3164
+ this._setTransactionState({ step: "submitted", hash, buildData });
3165
+ return { status: "pending", hash, ...outcomeExtra };
3166
+ }
3167
+ this._setTransactionState({
3168
+ step: "error",
3169
+ phase: "submitting",
3170
+ buildData,
3171
+ ...resultCode && { details: resultCode }
3172
+ });
3173
+ return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
3174
+ }
3175
+ const details = error?.details;
3176
+ this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
3177
+ return { status: "error", ...outcomeExtra, ...details && { details } };
3178
+ } catch (err) {
3179
+ const details = err instanceof Error ? err.message : void 0;
3180
+ this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
3181
+ return { status: "error", ...outcomeExtra, ...details && { details } };
3182
+ }
3183
+ }
2997
3184
  // ─── App config ───────────────────────────────────────────────────────────
2998
3185
  async getAppConfig() {
2999
3186
  try {
@@ -3047,10 +3234,18 @@ var PollarClient = class {
3047
3234
  this._txHistoryState = next;
3048
3235
  for (const cb of this._txHistoryStateListeners) cb(next);
3049
3236
  }
3237
+ _setSessionsState(next) {
3238
+ this._sessionsState = next;
3239
+ for (const cb of this._sessionsStateListeners) cb(next);
3240
+ }
3050
3241
  _setWalletBalanceState(next) {
3051
3242
  this._walletBalanceState = next;
3052
3243
  for (const cb of this._walletBalanceStateListeners) cb(next);
3053
3244
  }
3245
+ _setEnabledAssetsState(next) {
3246
+ this._enabledAssetsState = next;
3247
+ for (const cb of this._enabledAssetsStateListeners) cb(next);
3248
+ }
3054
3249
  // ─── Private ──────────────────────────────────────────────────────────────
3055
3250
  _newController() {
3056
3251
  this._loginController?.abort();
@@ -3060,6 +3255,7 @@ var PollarClient = class {
3060
3255
  _flowDeps(signal) {
3061
3256
  return {
3062
3257
  api: this._api,
3258
+ logger: this._log,
3063
3259
  basePath: this.basePath,
3064
3260
  // SSE status streaming works on web; React Native's `fetch` has no
3065
3261
  // readable `response.body`, so those clients poll the non-streaming
@@ -3075,6 +3271,7 @@ var PollarClient = class {
3075
3271
  this._walletAdapter = adapter;
3076
3272
  await writeWalletType(this._storage, this.apiKeyHash, id);
3077
3273
  },
3274
+ ...this._passkey ? { passkey: this._passkey } : {},
3078
3275
  ...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
3079
3276
  };
3080
3277
  }
@@ -3104,19 +3301,19 @@ var PollarClient = class {
3104
3301
  }
3105
3302
  }
3106
3303
  if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
3107
- if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
3304
+ if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
3108
3305
  throw new Error(
3109
3306
  `[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
3110
3307
  );
3111
3308
  }
3112
3309
  _handleFlowError(error) {
3113
3310
  if (error instanceof Error && error.name === "AbortError") {
3114
- console.info("[PollarClient] Login cancelled");
3311
+ this._log.debug("[PollarClient] Login cancelled");
3115
3312
  this._setAuthState({ step: "idle" });
3116
3313
  return;
3117
3314
  }
3118
3315
  if (error instanceof Error && error.code === AUTH_ERROR_CODES.WALLET_RESOLVER_TIMEOUT) {
3119
- console.error("[PollarClient]", error.message);
3316
+ this._log.error("[PollarClient]", error.message);
3120
3317
  this._setAuthState({
3121
3318
  step: "error",
3122
3319
  previousStep: this._authState.step,
@@ -3125,7 +3322,7 @@ var PollarClient = class {
3125
3322
  });
3126
3323
  return;
3127
3324
  }
3128
- console.error("[PollarClient] Unexpected error in auth flow", error);
3325
+ this._log.error("[PollarClient] Unexpected error in auth flow", error);
3129
3326
  this._setAuthState({
3130
3327
  step: "error",
3131
3328
  previousStep: this._authState.step,
@@ -3134,32 +3331,77 @@ var PollarClient = class {
3134
3331
  });
3135
3332
  }
3136
3333
  async _restoreSession() {
3137
- this._session = await readStorage(this._storage, this.apiKeyHash);
3334
+ this._session = await readStorage(this._storage, this.apiKeyHash, this._log);
3138
3335
  if (this._session) {
3139
3336
  const storedType = await readWalletType(this._storage, this.apiKeyHash);
3140
3337
  if (storedType) {
3141
3338
  try {
3142
3339
  this._walletAdapter = await this._resolveWalletAdapter(storedType);
3143
3340
  } catch (err) {
3144
- console.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
3341
+ this._log.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
3145
3342
  }
3146
3343
  }
3147
- console.info("[PollarClient] Session restored from storage");
3148
- this._setAuthState({ step: "authenticated", session: this._session });
3344
+ this._log.info("[PollarClient] Session restored from storage");
3345
+ this._setAuthState({ step: "authenticated", session: this._session, verified: false });
3149
3346
  this._scheduleNextRefresh();
3347
+ void this._resume();
3150
3348
  } else {
3151
- console.info("[PollarClient] No session in storage");
3349
+ this._log.info("[PollarClient] No session in storage");
3350
+ }
3351
+ }
3352
+ /**
3353
+ * Validate the restored session against the server and repopulate the
3354
+ * in-memory profile (PII is never persisted, so it's null after a cold
3355
+ * reload). Goes through the normal authed client, so it coalesces with any
3356
+ * in-flight refresh (onRequest awaits `_refreshPromise`) and, being a GET,
3357
+ * is auto-retried after a 401-triggered refresh.
3358
+ *
3359
+ * - 200 → store profile, mark the session `verified`.
3360
+ * - 401 → the refresh-on-401 path already ran; if the family was
3361
+ * revoked, refresh failed and `_clearSession()` took us to
3362
+ * idle. Nothing to do here — don't double-handle.
3363
+ * - network error → stay optimistic (do NOT log out); revalidated later on
3364
+ * `visibilitychange` or first use.
3365
+ */
3366
+ async _resume() {
3367
+ if (!this._session) return;
3368
+ this._resumeController?.abort();
3369
+ const controller = new AbortController();
3370
+ this._resumeController = controller;
3371
+ try {
3372
+ const { data, error } = await this._api.GET("/auth/session/resume", { signal: controller.signal });
3373
+ if (error || !data) return;
3374
+ const content = data.content;
3375
+ if (!content || !this._session) return;
3376
+ this._profile = { ...content };
3377
+ this._setAuthState({ step: "authenticated", session: this._session, verified: true });
3378
+ } catch (err) {
3379
+ if (err?.name === "AbortError") return;
3380
+ this._log.warn("[PollarClient] resume failed (network); will retry", err);
3381
+ } finally {
3382
+ if (this._resumeController === controller) this._resumeController = null;
3152
3383
  }
3153
3384
  }
3154
3385
  async _storeSession(session) {
3155
- console.info("[PollarClient] Session stored");
3386
+ this._log.info("[PollarClient] Session stored");
3387
+ const w = session.wallet;
3156
3388
  const persisted = {
3157
3389
  clientSessionId: session.clientSessionId,
3158
3390
  userId: session.userId ?? null,
3159
3391
  status: session.status,
3160
3392
  token: session.token,
3161
3393
  user: session.user,
3162
- wallet: session.wallet
3394
+ // The wire response still carries the legacy `publicKey` alias (kept for
3395
+ // older SDKs); the persisted session standardizes on `address` only.
3396
+ wallet: {
3397
+ type: w.type,
3398
+ address: w.address ?? w.publicKey ?? null,
3399
+ ...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
3400
+ ...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
3401
+ ...w.linkedAt !== void 0 ? { linkedAt: w.linkedAt } : {},
3402
+ ...w.network !== void 0 ? { network: w.network } : {},
3403
+ ...w.deployTxHash !== void 0 ? { deployTxHash: w.deployTxHash } : {}
3404
+ }
3163
3405
  };
3164
3406
  this._session = persisted;
3165
3407
  if (session.data) {
@@ -3172,11 +3414,11 @@ var PollarClient = class {
3172
3414
  };
3173
3415
  }
3174
3416
  await writeStorage(this._storage, this.apiKeyHash, persisted);
3175
- this._setAuthState({ step: "authenticated", session: persisted });
3417
+ this._setAuthState({ step: "authenticated", session: persisted, verified: true });
3176
3418
  this._scheduleNextRefresh();
3177
3419
  }
3178
3420
  async _clearSession() {
3179
- console.info("[PollarClient] Session cleared");
3421
+ this._log.info("[PollarClient] Session cleared");
3180
3422
  this._clearRefreshTimer();
3181
3423
  this._session = null;
3182
3424
  this._profile = null;
@@ -3185,7 +3427,7 @@ var PollarClient = class {
3185
3427
  try {
3186
3428
  await this._keyManager.reset();
3187
3429
  } catch (err) {
3188
- console.warn("[PollarClient] KeyManager reset failed during clearSession", err);
3430
+ this._log.warn("[PollarClient] KeyManager reset failed during clearSession", err);
3189
3431
  }
3190
3432
  await removeStorage(this._storage, this.apiKeyHash);
3191
3433
  this._transactionState = null;
@@ -3197,17 +3439,17 @@ var PollarClient = class {
3197
3439
  _setNetworkState(next) {
3198
3440
  this._networkState = next;
3199
3441
  const label = next.step === "connected" ? next.network : next.step;
3200
- console.info(`[PollarClient] network:${label}`);
3442
+ this._log.debug(`[PollarClient] network:${label}`);
3201
3443
  for (const cb of this._networkStateListeners) cb(next);
3202
3444
  }
3203
3445
  _setAuthState(next) {
3204
3446
  this._authState = next;
3205
- console.info(`[PollarClient] auth:${next.step}`);
3447
+ this._log.debug(`[PollarClient] auth:${next.step}`);
3206
3448
  for (const cb of this._authStateListeners) cb(next);
3207
3449
  }
3208
3450
  _setTransactionState(next) {
3209
3451
  this._transactionState = next;
3210
- console.info(`[PollarClient] transaction:${next.step}`);
3452
+ this._log.debug(`[PollarClient] transaction:${next.step}`);
3211
3453
  for (const cb of this._transactionStateListeners) cb(next);
3212
3454
  }
3213
3455
  /**
@@ -3224,6 +3466,195 @@ var PollarClient = class {
3224
3466
  }
3225
3467
  };
3226
3468
 
3469
+ // src/keys/web-crypto.ts
3470
+ var DB_NAME = "pollar-keys";
3471
+ var DB_VERSION = 1;
3472
+ var STORE_NAME = "keys";
3473
+ function openDb() {
3474
+ return new Promise((resolve, reject) => {
3475
+ if (typeof indexedDB === "undefined") {
3476
+ reject(new Error("[PollarClient:keys] IndexedDB not available"));
3477
+ return;
3478
+ }
3479
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
3480
+ req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB open failed"));
3481
+ req.onsuccess = () => resolve(req.result);
3482
+ req.onupgradeneeded = () => {
3483
+ const db = req.result;
3484
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
3485
+ db.createObjectStore(STORE_NAME);
3486
+ }
3487
+ };
3488
+ });
3489
+ }
3490
+ function awaitTx(req) {
3491
+ return new Promise((resolve, reject) => {
3492
+ req.onsuccess = () => resolve(req.result);
3493
+ req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB request failed"));
3494
+ });
3495
+ }
3496
+ async function dbGet(key) {
3497
+ const db = await openDb();
3498
+ try {
3499
+ const tx = db.transaction(STORE_NAME, "readonly");
3500
+ const result = await awaitTx(tx.objectStore(STORE_NAME).get(key));
3501
+ return result;
3502
+ } finally {
3503
+ db.close();
3504
+ }
3505
+ }
3506
+ async function dbPut(key, value) {
3507
+ const db = await openDb();
3508
+ try {
3509
+ const tx = db.transaction(STORE_NAME, "readwrite");
3510
+ await awaitTx(tx.objectStore(STORE_NAME).put(value, key));
3511
+ } finally {
3512
+ db.close();
3513
+ }
3514
+ }
3515
+ async function dbDelete(key) {
3516
+ const db = await openDb();
3517
+ try {
3518
+ const tx = db.transaction(STORE_NAME, "readwrite");
3519
+ await awaitTx(tx.objectStore(STORE_NAME).delete(key));
3520
+ } finally {
3521
+ db.close();
3522
+ }
3523
+ }
3524
+ function isCryptoKeyPair(v) {
3525
+ if (typeof v !== "object" || v === null) return false;
3526
+ const obj = v;
3527
+ return obj.privateKey !== void 0 && obj.publicKey !== void 0;
3528
+ }
3529
+ var WebCryptoKeyManager = class {
3530
+ constructor(apiKey) {
3531
+ this.apiKeyHash = null;
3532
+ this.keyPair = null;
3533
+ this.publicJwk = null;
3534
+ this.thumbprint = null;
3535
+ /**
3536
+ * Cached in-flight init. Lets `init()` be called concurrently (or implicitly
3537
+ * from `getPublicJwk` / `sign`) without doing the work twice. Cleared on
3538
+ * failure so callers can retry, and cleared on `reset()`.
3539
+ */
3540
+ this._initPromise = null;
3541
+ if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
3542
+ throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
3543
+ }
3544
+ this.apiKey = apiKey;
3545
+ }
3546
+ /**
3547
+ * Idempotent and safe under concurrency. The first call kicks off the real
3548
+ * init; subsequent (and concurrent) calls return the same in-flight promise.
3549
+ * Other methods (`getPublicJwk`, `getThumbprint`, `sign`) auto-await this so
3550
+ * the manager is self-healing if `init()` was never explicitly invoked.
3551
+ */
3552
+ async init() {
3553
+ if (this.keyPair) return;
3554
+ if (!this._initPromise) {
3555
+ this._initPromise = this._doInit().catch((err) => {
3556
+ this._initPromise = null;
3557
+ throw err;
3558
+ });
3559
+ }
3560
+ return this._initPromise;
3561
+ }
3562
+ async _doInit() {
3563
+ if (!this.apiKeyHash) {
3564
+ this.apiKeyHash = await hashApiKey(this.apiKey);
3565
+ }
3566
+ let pair;
3567
+ try {
3568
+ pair = await dbGet(this.apiKeyHash);
3569
+ if (pair && !isCryptoKeyPair(pair)) pair = void 0;
3570
+ } catch {
3571
+ pair = void 0;
3572
+ }
3573
+ if (!pair) {
3574
+ pair = await globalThis.crypto.subtle.generateKey(
3575
+ { name: "ECDSA", namedCurve: "P-256" },
3576
+ // false → private key non-extractable; per W3C ECDSA spec the public
3577
+ // key is always extractable regardless of this flag.
3578
+ false,
3579
+ ["sign", "verify"]
3580
+ );
3581
+ try {
3582
+ await dbPut(this.apiKeyHash, pair);
3583
+ } catch {
3584
+ }
3585
+ }
3586
+ this.keyPair = pair;
3587
+ this.publicJwk = await this._exportPublicJwk(pair.publicKey);
3588
+ this.thumbprint = await computeJwkThumbprint(this.publicJwk);
3589
+ }
3590
+ /**
3591
+ * Derive the public JWK from a `CryptoKey`. Prefers the `'raw'` export (the
3592
+ * 65-byte uncompressed point `0x04 || X(32) || Y(32)`) and base64url-encodes
3593
+ * the coordinates ourselves — that sidesteps polyfills whose `exportKey('jwk')`
3594
+ * emits non-base64url `x`/`y` (standard base64, `=` padding, or — as seen with
3595
+ * `react-native-quick-crypto` — a stray `.`). Real browsers and most polyfills
3596
+ * support `'raw'` for public EC keys.
3597
+ *
3598
+ * Falls back to the `'jwk'` export (normalized via `canonicalEcJwk`) if `'raw'`
3599
+ * is unsupported or returns an unexpected shape, so this can't regress on a
3600
+ * runtime that only implements the JWK path. Both routes yield identical
3601
+ * coordinate bytes, so the `cnf.jkt` thumbprint is unchanged either way.
3602
+ */
3603
+ async _exportPublicJwk(publicKey) {
3604
+ try {
3605
+ const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey("raw", publicKey));
3606
+ if (raw.length !== 65 || raw[0] !== 4) {
3607
+ throw new Error(`[PollarClient:keys] Unexpected raw EC point (len=${raw.length}, tag=${raw[0]})`);
3608
+ }
3609
+ return {
3610
+ kty: "EC",
3611
+ crv: "P-256",
3612
+ x: base64urlEncode(raw.slice(1, 33)),
3613
+ y: base64urlEncode(raw.slice(33, 65))
3614
+ };
3615
+ } catch {
3616
+ const jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
3617
+ return canonicalEcJwk(jwk);
3618
+ }
3619
+ }
3620
+ async reset() {
3621
+ try {
3622
+ if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
3623
+ } catch {
3624
+ }
3625
+ this.keyPair = null;
3626
+ this.publicJwk = null;
3627
+ this.thumbprint = null;
3628
+ this._initPromise = null;
3629
+ }
3630
+ async getPublicJwk() {
3631
+ if (!this.publicJwk) await this.init();
3632
+ if (!this.publicJwk) {
3633
+ throw new Error("[PollarClient:keys] Keypair initialization failed; getPublicJwk unavailable");
3634
+ }
3635
+ return { kty: this.publicJwk.kty, crv: this.publicJwk.crv, x: this.publicJwk.x, y: this.publicJwk.y };
3636
+ }
3637
+ async getThumbprint() {
3638
+ if (!this.thumbprint) await this.init();
3639
+ if (!this.thumbprint) {
3640
+ throw new Error("[PollarClient:keys] Keypair initialization failed; getThumbprint unavailable");
3641
+ }
3642
+ return this.thumbprint;
3643
+ }
3644
+ async sign(payload) {
3645
+ if (!this.keyPair) await this.init();
3646
+ if (!this.keyPair) {
3647
+ throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
3648
+ }
3649
+ const sig = await globalThis.crypto.subtle.sign(
3650
+ { name: "ECDSA", hash: "SHA-256" },
3651
+ this.keyPair.privateKey,
3652
+ payload
3653
+ );
3654
+ return new Uint8Array(sig);
3655
+ }
3656
+ };
3657
+
3227
3658
  // src/stellar/StellarClient.ts
3228
3659
  var HORIZON_URLS = {
3229
3660
  mainnet: "https://horizon.stellar.org",
@@ -3254,10 +3685,6 @@ var StellarClient = class {
3254
3685
 
3255
3686
  // src/index.rn.ts
3256
3687
  _setDefaultKeyManagerFactory((storage, apiKey) => {
3257
- const subtle = globalThis.crypto?.subtle;
3258
- if (subtle && typeof subtle.generateKey === "function" && typeof subtle.sign === "function") {
3259
- return new WebCryptoKeyManager(apiKey);
3260
- }
3261
3688
  return new NobleKeyManager(storage, apiKey);
3262
3689
  });
3263
3690
 
@@ -3265,6 +3692,7 @@ exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
3265
3692
  exports.AlbedoAdapter = AlbedoAdapter;
3266
3693
  exports.FreighterAdapter = FreighterAdapter;
3267
3694
  exports.NobleKeyManager = NobleKeyManager;
3695
+ exports.POLLAR_CORE_VERSION = POLLAR_CORE_VERSION;
3268
3696
  exports.PollarClient = PollarClient;
3269
3697
  exports.StellarClient = StellarClient;
3270
3698
  exports.WalletType = WalletType;
@@ -3274,6 +3702,7 @@ exports.canonicalEcJwk = canonicalEcJwk;
3274
3702
  exports.claimDistributionRule = claimDistributionRule;
3275
3703
  exports.computeJwkThumbprint = computeJwkThumbprint;
3276
3704
  exports.createLocalStorageAdapter = createLocalStorageAdapter;
3705
+ exports.createLogger = createLogger;
3277
3706
  exports.createMemoryAdapter = createMemoryAdapter;
3278
3707
  exports.createOffRamp = createOffRamp;
3279
3708
  exports.createOnRamp = createOnRamp;