@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/README.md +35 -0
- package/dist/adapters/react-native-appstate.d.mts +1 -1
- package/dist/adapters/react-native-appstate.d.ts +1 -1
- package/dist/index.d.mts +996 -96
- package/dist/index.d.ts +996 -96
- package/dist/index.js +546 -112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +545 -113
- package/dist/index.mjs.map +1 -1
- package/dist/index.rn.d.mts +2 -2
- package/dist/index.rn.d.ts +2 -2
- package/dist/index.rn.js +691 -262
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +690 -263
- package/dist/index.rn.mjs.map +1 -1
- package/dist/{types-84G_htcn.d.mts → types-Dyky8g0p.d.mts} +5 -12
- package/dist/{types-84G_htcn.d.ts → types-Dyky8g0p.d.ts} +5 -12
- package/package.json +1 -1
package/dist/index.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
|
|
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
|
-
|
|
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",
|
|
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
|
|
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,
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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["
|
|
1579
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
1986
|
-
connectedWallet =
|
|
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:
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2278
|
+
this._log.info("[PollarClient] Tokens refreshed");
|
|
2312
2279
|
} catch (err) {
|
|
2313
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
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
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
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
|
|
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(),
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3341
|
+
this._log.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
|
|
3145
3342
|
}
|
|
3146
3343
|
}
|
|
3147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|