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