@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.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
|
|
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
|
-
|
|
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",
|
|
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
|
|
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,
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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["
|
|
1577
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
1984
|
-
connectedWallet =
|
|
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:
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2276
|
+
this._log.info("[PollarClient] Tokens refreshed");
|
|
2310
2277
|
} catch (err) {
|
|
2311
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
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
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
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
|
|
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(),
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3339
|
+
this._log.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
|
|
3143
3340
|
}
|
|
3144
3341
|
}
|
|
3145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|