@pollar/core 0.8.2 → 0.9.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/react-native-appstate.d.mts +1 -1
- package/dist/adapters/react-native-appstate.d.ts +1 -1
- package/dist/index.d.mts +947 -100
- package/dist/index.d.ts +947 -100
- package/dist/index.js +469 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +469 -58
- 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 +615 -207
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +615 -208
- 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
|
|
@@ -377,167 +380,6 @@ var NobleKeyManager = class {
|
|
|
377
380
|
}
|
|
378
381
|
};
|
|
379
382
|
|
|
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
383
|
// ../../node_modules/openapi-fetch/dist/index.mjs
|
|
542
384
|
var PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
543
385
|
var supportsRequestInitExt = () => {
|
|
@@ -1257,6 +1099,9 @@ function defaultStorage(options = {}) {
|
|
|
1257
1099
|
return createLocalStorageAdapter(options);
|
|
1258
1100
|
}
|
|
1259
1101
|
|
|
1102
|
+
// src/version.ts
|
|
1103
|
+
var POLLAR_CORE_VERSION = "0.9.0-rc.0" ;
|
|
1104
|
+
|
|
1260
1105
|
// src/visibility/noop.ts
|
|
1261
1106
|
function createNoopVisibilityProvider() {
|
|
1262
1107
|
return {
|
|
@@ -1321,6 +1166,7 @@ var AUTH_ERROR_CODES = {
|
|
|
1321
1166
|
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1322
1167
|
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1323
1168
|
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1169
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1324
1170
|
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1325
1171
|
};
|
|
1326
1172
|
var PollarFlowError = class extends Error {
|
|
@@ -1366,7 +1212,7 @@ var FreighterAdapter = class {
|
|
|
1366
1212
|
if (!userInfo?.publicKey) {
|
|
1367
1213
|
throw new Error("Failed to get user information from Freighter");
|
|
1368
1214
|
}
|
|
1369
|
-
return { address: userInfo.publicKey
|
|
1215
|
+
return { address: userInfo.publicKey };
|
|
1370
1216
|
}
|
|
1371
1217
|
async disconnect() {
|
|
1372
1218
|
}
|
|
@@ -1404,6 +1250,19 @@ var FreighterAdapter = class {
|
|
|
1404
1250
|
};
|
|
1405
1251
|
|
|
1406
1252
|
// src/wallets/AlbedoAdapter.ts
|
|
1253
|
+
var PUBLIC_PASSPHRASE = "Public Global Stellar Network ; September 2015";
|
|
1254
|
+
var TESTNET_PASSPHRASE = "Test SDF Network ; September 2015";
|
|
1255
|
+
function albedoNetwork(options, fallback) {
|
|
1256
|
+
switch (options?.networkPassphrase) {
|
|
1257
|
+
case PUBLIC_PASSPHRASE:
|
|
1258
|
+
return "public";
|
|
1259
|
+
case TESTNET_PASSPHRASE:
|
|
1260
|
+
return "testnet";
|
|
1261
|
+
}
|
|
1262
|
+
if (options?.network === "public" || options?.network === "mainnet") return "public";
|
|
1263
|
+
if (options?.network === "testnet") return "testnet";
|
|
1264
|
+
return fallback;
|
|
1265
|
+
}
|
|
1407
1266
|
function openAlbedoPopup(url) {
|
|
1408
1267
|
const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
|
|
1409
1268
|
if (!popup) {
|
|
@@ -1442,7 +1301,13 @@ function waitForAlbedoResult() {
|
|
|
1442
1301
|
});
|
|
1443
1302
|
}
|
|
1444
1303
|
var AlbedoAdapter = class {
|
|
1445
|
-
|
|
1304
|
+
/**
|
|
1305
|
+
* Network used for `connect` and `signAuthEntry` (which carry no per-call
|
|
1306
|
+
* network) and as the fallback for `signTransaction`. Defaults to `'testnet'`
|
|
1307
|
+
* to preserve the previous behavior when constructed with no argument.
|
|
1308
|
+
*/
|
|
1309
|
+
constructor(network = "testnet") {
|
|
1310
|
+
this.network = network;
|
|
1446
1311
|
this.type = "albedo" /* ALBEDO */;
|
|
1447
1312
|
}
|
|
1448
1313
|
async isAvailable() {
|
|
@@ -1452,7 +1317,7 @@ var AlbedoAdapter = class {
|
|
|
1452
1317
|
const url = new URL("https://albedo.link");
|
|
1453
1318
|
url.searchParams.set("intent", "public-key");
|
|
1454
1319
|
url.searchParams.set("app_name", "Pollar");
|
|
1455
|
-
url.searchParams.set("network",
|
|
1320
|
+
url.searchParams.set("network", this.network);
|
|
1456
1321
|
url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
|
|
1457
1322
|
url.searchParams.set("origin", window.location.origin);
|
|
1458
1323
|
openAlbedoPopup(url.toString());
|
|
@@ -1460,7 +1325,7 @@ var AlbedoAdapter = class {
|
|
|
1460
1325
|
if (!result.pubkey) {
|
|
1461
1326
|
throw new Error("Albedo connection rejected");
|
|
1462
1327
|
}
|
|
1463
|
-
return { address: result.pubkey
|
|
1328
|
+
return { address: result.pubkey };
|
|
1464
1329
|
}
|
|
1465
1330
|
async disconnect() {
|
|
1466
1331
|
}
|
|
@@ -1470,12 +1335,12 @@ var AlbedoAdapter = class {
|
|
|
1470
1335
|
async getNetwork() {
|
|
1471
1336
|
throw new Error("Albedo does not expose network");
|
|
1472
1337
|
}
|
|
1473
|
-
async signTransaction(xdr,
|
|
1338
|
+
async signTransaction(xdr, options) {
|
|
1474
1339
|
const url = new URL("https://albedo.link");
|
|
1475
1340
|
url.searchParams.set("intent", "tx");
|
|
1476
1341
|
url.searchParams.set("xdr", xdr);
|
|
1477
1342
|
url.searchParams.set("app_name", "Pollar");
|
|
1478
|
-
url.searchParams.set("network",
|
|
1343
|
+
url.searchParams.set("network", albedoNetwork(options, this.network));
|
|
1479
1344
|
url.searchParams.set("callback", window.location.href);
|
|
1480
1345
|
url.searchParams.set("origin", window.location.origin);
|
|
1481
1346
|
window.location.href = url.toString();
|
|
@@ -1488,7 +1353,7 @@ var AlbedoAdapter = class {
|
|
|
1488
1353
|
url.searchParams.set("intent", "sign-auth-entry");
|
|
1489
1354
|
url.searchParams.set("xdr", entryXdr);
|
|
1490
1355
|
url.searchParams.set("app_name", "Pollar");
|
|
1491
|
-
url.searchParams.set("network",
|
|
1356
|
+
url.searchParams.set("network", this.network);
|
|
1492
1357
|
url.searchParams.set("callback", window.location.href);
|
|
1493
1358
|
url.searchParams.set("origin", window.location.origin);
|
|
1494
1359
|
window.location.href = url.toString();
|
|
@@ -1575,8 +1440,12 @@ function isValidSession(value) {
|
|
|
1575
1440
|
return false;
|
|
1576
1441
|
}
|
|
1577
1442
|
const w = wallet;
|
|
1578
|
-
if (w["
|
|
1579
|
-
console.warn("[PollarClient:session] Invalid session \u2014 wallet.
|
|
1443
|
+
if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
|
|
1444
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
|
|
1445
|
+
return false;
|
|
1446
|
+
}
|
|
1447
|
+
if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
|
|
1448
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
|
|
1580
1449
|
return false;
|
|
1581
1450
|
}
|
|
1582
1451
|
if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
|
|
@@ -1587,6 +1456,10 @@ function isValidSession(value) {
|
|
|
1587
1456
|
console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
|
|
1588
1457
|
return false;
|
|
1589
1458
|
}
|
|
1459
|
+
if (w["linkedAt"] !== void 0 && (typeof w["linkedAt"] !== "number" || !Number.isFinite(w["linkedAt"]))) {
|
|
1460
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.linkedAt must be a finite number if present");
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1590
1463
|
return true;
|
|
1591
1464
|
}
|
|
1592
1465
|
async function readStorage(storage, apiKeyHash) {
|
|
@@ -1594,6 +1467,12 @@ async function readStorage(storage, apiKeyHash) {
|
|
|
1594
1467
|
if (!raw) return null;
|
|
1595
1468
|
try {
|
|
1596
1469
|
const session = JSON.parse(raw);
|
|
1470
|
+
if (typeof session === "object" && session !== null) {
|
|
1471
|
+
const w = session.wallet;
|
|
1472
|
+
if (w && w["address"] == null && typeof w["publicKey"] === "string") {
|
|
1473
|
+
w["address"] = w["publicKey"];
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1597
1476
|
if (!isValidSession(session)) {
|
|
1598
1477
|
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1599
1478
|
console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
|
|
@@ -1684,7 +1563,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1684
1563
|
}));
|
|
1685
1564
|
} catch (e) {
|
|
1686
1565
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1687
|
-
console.warn(e);
|
|
1566
|
+
console.warn("[PollarClient:stream] session-status request failed; will retry", e);
|
|
1688
1567
|
}
|
|
1689
1568
|
if (error || !data) {
|
|
1690
1569
|
await sleep(backoff);
|
|
@@ -1724,7 +1603,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1724
1603
|
} catch (e) {
|
|
1725
1604
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1726
1605
|
if (e instanceof SessionStatusError) throw e;
|
|
1727
|
-
console.warn(e);
|
|
1606
|
+
console.warn("[PollarClient:stream] session-status stream read failed; will retry", e);
|
|
1728
1607
|
} finally {
|
|
1729
1608
|
reader.releaseLock();
|
|
1730
1609
|
}
|
|
@@ -1752,7 +1631,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
|
|
|
1752
1631
|
envelope = await response.json().catch(() => null);
|
|
1753
1632
|
} catch (e) {
|
|
1754
1633
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1755
|
-
console.warn(e);
|
|
1634
|
+
console.warn("[PollarClient:stream] session-status poll failed; will retry", e);
|
|
1756
1635
|
}
|
|
1757
1636
|
if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
|
|
1758
1637
|
throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
|
|
@@ -1956,6 +1835,55 @@ async function loginOAuth(provider, deps) {
|
|
|
1956
1835
|
await authenticate(clientSessionId, deps);
|
|
1957
1836
|
}
|
|
1958
1837
|
|
|
1838
|
+
// src/client/auth/passkeyFlow.ts
|
|
1839
|
+
async function loginSmartWallet(deps) {
|
|
1840
|
+
const { api, signal, setAuthState, passkey } = deps;
|
|
1841
|
+
if (!passkey) {
|
|
1842
|
+
setAuthState({
|
|
1843
|
+
step: "error",
|
|
1844
|
+
previousStep: "creating_session",
|
|
1845
|
+
message: "Passkey support is not configured",
|
|
1846
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1847
|
+
});
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
const clientSessionId = await createAuthSession(deps);
|
|
1851
|
+
if (!clientSessionId) return;
|
|
1852
|
+
try {
|
|
1853
|
+
const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
|
|
1854
|
+
body: { clientSessionId },
|
|
1855
|
+
signal
|
|
1856
|
+
});
|
|
1857
|
+
const challenge = challengeData?.content?.challenge;
|
|
1858
|
+
if (!challengeData?.success || !challenge) {
|
|
1859
|
+
return failPasskey(setAuthState, "Failed to start passkey");
|
|
1860
|
+
}
|
|
1861
|
+
setAuthState({ step: "creating_passkey" });
|
|
1862
|
+
const ceremony = await passkey({ challenge });
|
|
1863
|
+
const response = ceremony.response;
|
|
1864
|
+
if (ceremony.kind === "register") {
|
|
1865
|
+
setAuthState({ step: "deploying_smart_account" });
|
|
1866
|
+
const { data } = await api.POST("/auth/passkey/register", {
|
|
1867
|
+
body: { clientSessionId, response },
|
|
1868
|
+
signal
|
|
1869
|
+
});
|
|
1870
|
+
if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
|
|
1871
|
+
} else {
|
|
1872
|
+
const { data } = await api.POST("/auth/passkey/login", {
|
|
1873
|
+
body: { clientSessionId, response },
|
|
1874
|
+
signal
|
|
1875
|
+
});
|
|
1876
|
+
if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
|
|
1877
|
+
}
|
|
1878
|
+
} catch {
|
|
1879
|
+
return failPasskey(setAuthState, "Passkey login failed");
|
|
1880
|
+
}
|
|
1881
|
+
await authenticate(clientSessionId, deps);
|
|
1882
|
+
}
|
|
1883
|
+
function failPasskey(setAuthState, message) {
|
|
1884
|
+
setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1959
1887
|
// src/client/auth/walletFlow.ts
|
|
1960
1888
|
function withSignal(promise, signal) {
|
|
1961
1889
|
return Promise.race([
|
|
@@ -1982,12 +1910,12 @@ async function loginWallet(type, deps) {
|
|
|
1982
1910
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
1983
1911
|
return;
|
|
1984
1912
|
}
|
|
1985
|
-
const {
|
|
1986
|
-
connectedWallet =
|
|
1913
|
+
const { address } = await withSignal(adapter.connect(), signal);
|
|
1914
|
+
connectedWallet = address;
|
|
1987
1915
|
deps.storeWalletAdapter(adapter, type);
|
|
1988
1916
|
setAuthState({ step: "authenticating_wallet" });
|
|
1989
1917
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
|
|
1990
|
-
body: { clientSessionId, walletAddress:
|
|
1918
|
+
body: { clientSessionId, walletAddress: address },
|
|
1991
1919
|
signal
|
|
1992
1920
|
});
|
|
1993
1921
|
if (walletError || !walletData?.success) {
|
|
@@ -2052,8 +1980,12 @@ var PollarClient = class {
|
|
|
2052
1980
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
2053
1981
|
this._txHistoryState = { step: "idle" };
|
|
2054
1982
|
this._txHistoryStateListeners = /* @__PURE__ */ new Set();
|
|
1983
|
+
this._sessionsState = { step: "idle" };
|
|
1984
|
+
this._sessionsStateListeners = /* @__PURE__ */ new Set();
|
|
2055
1985
|
this._walletBalanceState = { step: "idle" };
|
|
2056
1986
|
this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
|
|
1987
|
+
this._enabledAssetsState = { step: "idle" };
|
|
1988
|
+
this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
|
|
2057
1989
|
this._authState = { step: "idle" };
|
|
2058
1990
|
this._authStateListeners = /* @__PURE__ */ new Set();
|
|
2059
1991
|
this._networkState = { step: "idle" };
|
|
@@ -2069,6 +2001,8 @@ var PollarClient = class {
|
|
|
2069
2001
|
this._storageDegradeListeners = /* @__PURE__ */ new Set();
|
|
2070
2002
|
this._walletAdapter = null;
|
|
2071
2003
|
this._loginController = null;
|
|
2004
|
+
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2005
|
+
this._resumeController = null;
|
|
2072
2006
|
this.apiKey = config.apiKey;
|
|
2073
2007
|
this.id = randomUUID();
|
|
2074
2008
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
@@ -2081,6 +2015,8 @@ var PollarClient = class {
|
|
|
2081
2015
|
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
2082
2016
|
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
2083
2017
|
this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
|
|
2018
|
+
this._passkey = config.passkey ?? null;
|
|
2019
|
+
this._passkeySign = config.passkeySign ?? null;
|
|
2084
2020
|
this._deviceLabel = config.deviceLabel;
|
|
2085
2021
|
this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
|
|
2086
2022
|
this._maxIdleMs = config.maxIdleMs;
|
|
@@ -2094,7 +2030,9 @@ var PollarClient = class {
|
|
|
2094
2030
|
this._initialized = Promise.resolve();
|
|
2095
2031
|
return;
|
|
2096
2032
|
}
|
|
2097
|
-
console.info(
|
|
2033
|
+
console.info(
|
|
2034
|
+
`[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
|
|
2035
|
+
);
|
|
2098
2036
|
this._initialized = this._initialize();
|
|
2099
2037
|
}
|
|
2100
2038
|
/**
|
|
@@ -2132,7 +2070,11 @@ var PollarClient = class {
|
|
|
2132
2070
|
}
|
|
2133
2071
|
await this._restoreSession();
|
|
2134
2072
|
this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
|
|
2135
|
-
if (visible)
|
|
2073
|
+
if (!visible) return;
|
|
2074
|
+
void this._maybeProactiveRefresh();
|
|
2075
|
+
if (this._authState.step === "authenticated" && !this._authState.verified) {
|
|
2076
|
+
void this._resume();
|
|
2077
|
+
}
|
|
2136
2078
|
});
|
|
2137
2079
|
}
|
|
2138
2080
|
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
@@ -2143,6 +2085,8 @@ var PollarClient = class {
|
|
|
2143
2085
|
}
|
|
2144
2086
|
this._loginController?.abort();
|
|
2145
2087
|
this._loginController = null;
|
|
2088
|
+
this._resumeController?.abort();
|
|
2089
|
+
this._resumeController = null;
|
|
2146
2090
|
this._clearRefreshTimer();
|
|
2147
2091
|
if (this._visibilityUnsubscribe) {
|
|
2148
2092
|
this._visibilityUnsubscribe();
|
|
@@ -2157,7 +2101,9 @@ var PollarClient = class {
|
|
|
2157
2101
|
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
2158
2102
|
self._lastRequestAt = Date.now();
|
|
2159
2103
|
await self._initialized;
|
|
2160
|
-
|
|
2104
|
+
const cacheMethod = request.method.toUpperCase();
|
|
2105
|
+
const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
|
|
2106
|
+
if (cacheBodyAllowed && request.body != null) {
|
|
2161
2107
|
try {
|
|
2162
2108
|
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
2163
2109
|
} catch (err) {
|
|
@@ -2244,7 +2190,9 @@ var PollarClient = class {
|
|
|
2244
2190
|
}
|
|
2245
2191
|
}
|
|
2246
2192
|
}
|
|
2247
|
-
const
|
|
2193
|
+
const retryMethod = originalRequest.method.toUpperCase();
|
|
2194
|
+
const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
|
|
2195
|
+
const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
|
|
2248
2196
|
const retried = new Request(originalRequest.url, {
|
|
2249
2197
|
method: originalRequest.method,
|
|
2250
2198
|
headers,
|
|
@@ -2288,19 +2236,26 @@ var PollarClient = class {
|
|
|
2288
2236
|
throw err;
|
|
2289
2237
|
}
|
|
2290
2238
|
if (error || !data) {
|
|
2291
|
-
console.
|
|
2239
|
+
console.error("[PollarClient] /auth/refresh returned error", { error });
|
|
2292
2240
|
await this._clearSession();
|
|
2293
2241
|
throw new Error("Refresh failed");
|
|
2294
2242
|
}
|
|
2295
2243
|
const successData = data;
|
|
2296
2244
|
if (!successData.success || !successData.content?.token) {
|
|
2297
|
-
console.
|
|
2245
|
+
console.error("[PollarClient] /auth/refresh response malformed", {
|
|
2246
|
+
success: successData.success,
|
|
2247
|
+
hasToken: !!successData.content?.token
|
|
2248
|
+
});
|
|
2298
2249
|
await this._clearSession();
|
|
2299
2250
|
throw new Error("Refresh response malformed");
|
|
2300
2251
|
}
|
|
2301
2252
|
const newToken = successData.content.token;
|
|
2302
2253
|
if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
|
|
2303
|
-
console.
|
|
2254
|
+
console.error("[PollarClient] /auth/refresh token shape invalid", {
|
|
2255
|
+
accessToken: typeof newToken.accessToken,
|
|
2256
|
+
refreshToken: typeof newToken.refreshToken,
|
|
2257
|
+
expiresAt: typeof newToken.expiresAt
|
|
2258
|
+
});
|
|
2304
2259
|
await this._clearSession();
|
|
2305
2260
|
throw new Error("Refresh response token shape invalid");
|
|
2306
2261
|
}
|
|
@@ -2494,6 +2449,19 @@ var PollarClient = class {
|
|
|
2494
2449
|
const controller = this._newController();
|
|
2495
2450
|
loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2496
2451
|
}
|
|
2452
|
+
/**
|
|
2453
|
+
* "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
|
|
2454
|
+
* user, creates a sponsored smart-account C-address. Requires the `passkey`
|
|
2455
|
+
* ceremony to be configured (e.g. via `@pollar/react`).
|
|
2456
|
+
*/
|
|
2457
|
+
loginSmartWallet() {
|
|
2458
|
+
if (!isClientRuntime) {
|
|
2459
|
+
warnServerSide("loginSmartWallet");
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
const controller = this._newController();
|
|
2463
|
+
loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2464
|
+
}
|
|
2497
2465
|
// ─── Cancel ───────────────────────────────────────────────────────────────
|
|
2498
2466
|
cancelLogin() {
|
|
2499
2467
|
this._loginController?.abort();
|
|
@@ -2556,6 +2524,29 @@ var PollarClient = class {
|
|
|
2556
2524
|
}
|
|
2557
2525
|
return data.content.sessions;
|
|
2558
2526
|
}
|
|
2527
|
+
getSessionsState() {
|
|
2528
|
+
return this._sessionsState;
|
|
2529
|
+
}
|
|
2530
|
+
onSessionsStateChange(cb) {
|
|
2531
|
+
this._sessionsStateListeners.add(cb);
|
|
2532
|
+
cb(this._sessionsState);
|
|
2533
|
+
return () => this._sessionsStateListeners.delete(cb);
|
|
2534
|
+
}
|
|
2535
|
+
/**
|
|
2536
|
+
* Fire-and-forget variant of {@link listSessions} that drives the observable
|
|
2537
|
+
* `SessionsState` store instead of returning the array. UI layers subscribe
|
|
2538
|
+
* via `onSessionsStateChange` and stay pure readers — mirrors `fetchTxHistory`.
|
|
2539
|
+
*/
|
|
2540
|
+
async fetchSessions() {
|
|
2541
|
+
this._setSessionsState({ step: "loading" });
|
|
2542
|
+
try {
|
|
2543
|
+
const sessions = await this.listSessions();
|
|
2544
|
+
this._setSessionsState({ step: "loaded", sessions });
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
const message = err instanceof Error ? err.message : "Failed to load sessions";
|
|
2547
|
+
this._setSessionsState({ step: "error", message });
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2559
2550
|
/**
|
|
2560
2551
|
* Revoke a specific refresh-token family (a single device session). Use
|
|
2561
2552
|
* `listSessions` to enumerate the familyIds. Revoking the current session
|
|
@@ -2632,16 +2623,19 @@ var PollarClient = class {
|
|
|
2632
2623
|
cb(this._walletBalanceState);
|
|
2633
2624
|
return () => this._walletBalanceStateListeners.delete(cb);
|
|
2634
2625
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2626
|
+
/**
|
|
2627
|
+
* Refreshes the balances of the authenticated user's OWN wallet. The wallet
|
|
2628
|
+
* and network are resolved server-side from the session — no arguments. Drives
|
|
2629
|
+
* `walletBalanceState`. For an arbitrary wallet, use {@link getWalletBalance}.
|
|
2630
|
+
*/
|
|
2631
|
+
async refreshBalance() {
|
|
2632
|
+
if (!this._session?.wallet?.address) {
|
|
2638
2633
|
this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
|
|
2639
2634
|
return;
|
|
2640
2635
|
}
|
|
2641
2636
|
this._setWalletBalanceState({ step: "loading" });
|
|
2642
2637
|
try {
|
|
2643
|
-
const
|
|
2644
|
-
const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
|
|
2638
|
+
const { data, error } = await this._api.GET("/wallet/balance");
|
|
2645
2639
|
if (!error && data?.success && data.content) {
|
|
2646
2640
|
this._setWalletBalanceState({ step: "loaded", data: data.content });
|
|
2647
2641
|
} else {
|
|
@@ -2651,6 +2645,53 @@ var PollarClient = class {
|
|
|
2651
2645
|
this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
|
|
2652
2646
|
}
|
|
2653
2647
|
}
|
|
2648
|
+
/**
|
|
2649
|
+
* General-purpose balance lookup for ANY wallet on ANY network — not scoped
|
|
2650
|
+
* to this application. Enumerates the account's real on-chain holdings via
|
|
2651
|
+
* Horizon (server-side) and returns the data directly (no reactive state).
|
|
2652
|
+
* `network` defaults to the client's current network.
|
|
2653
|
+
*/
|
|
2654
|
+
async getWalletBalance(publicKey, network) {
|
|
2655
|
+
const { data, error } = await this._api.GET("/wallet/{publicKey}/balance", {
|
|
2656
|
+
params: { path: { publicKey }, query: { network: network ?? this.getNetwork() } }
|
|
2657
|
+
});
|
|
2658
|
+
if (error || !data?.success || !data.content) {
|
|
2659
|
+
throw new Error("[PollarClient] Failed to load wallet balance");
|
|
2660
|
+
}
|
|
2661
|
+
return data.content;
|
|
2662
|
+
}
|
|
2663
|
+
// ─── Enabled assets ───────────────────────────────────────────────────────
|
|
2664
|
+
getEnabledAssetsState() {
|
|
2665
|
+
return this._enabledAssetsState;
|
|
2666
|
+
}
|
|
2667
|
+
onEnabledAssetsStateChange(cb) {
|
|
2668
|
+
this._enabledAssetsStateListeners.add(cb);
|
|
2669
|
+
cb(this._enabledAssetsState);
|
|
2670
|
+
return () => this._enabledAssetsStateListeners.delete(cb);
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Loads the application's enabled assets paired with the authenticated
|
|
2674
|
+
* wallet's on-chain trustline state — so the SDK knows which trustlines still
|
|
2675
|
+
* need to be added. Wallet and network are resolved server-side from the
|
|
2676
|
+
* session. Drives `enabledAssetsState`; mirrors {@link refreshBalance}.
|
|
2677
|
+
*/
|
|
2678
|
+
async refreshAssets() {
|
|
2679
|
+
if (!this._session?.wallet?.address) {
|
|
2680
|
+
this._setEnabledAssetsState({ step: "error", message: "No wallet connected" });
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
this._setEnabledAssetsState({ step: "loading" });
|
|
2684
|
+
try {
|
|
2685
|
+
const { data, error } = await this._api.GET("/wallet/assets");
|
|
2686
|
+
if (!error && data?.success && data.content) {
|
|
2687
|
+
this._setEnabledAssetsState({ step: "loaded", data: data.content });
|
|
2688
|
+
} else {
|
|
2689
|
+
this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
|
|
2690
|
+
}
|
|
2691
|
+
} catch {
|
|
2692
|
+
this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2654
2695
|
// ─── Transactions ─────────────────────────────────────────────────────────
|
|
2655
2696
|
/**
|
|
2656
2697
|
* Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
|
|
@@ -2658,14 +2699,14 @@ var PollarClient = class {
|
|
|
2658
2699
|
* inspect the result without subscribing to state changes.
|
|
2659
2700
|
*/
|
|
2660
2701
|
async buildTx(operation, params, options) {
|
|
2661
|
-
if (!this._session?.wallet?.
|
|
2702
|
+
if (!this._session?.wallet?.address) {
|
|
2662
2703
|
const details = "No wallet connected";
|
|
2663
2704
|
this._setTransactionState({ step: "error", phase: "building", details });
|
|
2664
2705
|
return { status: "error", details };
|
|
2665
2706
|
}
|
|
2666
2707
|
const body = {
|
|
2667
2708
|
network: this.getNetwork(),
|
|
2668
|
-
|
|
2709
|
+
address: this._session.wallet.address,
|
|
2669
2710
|
operation,
|
|
2670
2711
|
params,
|
|
2671
2712
|
options: options ?? {}
|
|
@@ -2706,7 +2747,7 @@ var PollarClient = class {
|
|
|
2706
2747
|
const buildData = this._currentBuildData();
|
|
2707
2748
|
this._setTransactionState({ step: "signing", ...buildData && { buildData } });
|
|
2708
2749
|
if (this._walletAdapter) {
|
|
2709
|
-
const accountToSign = this._session?.wallet?.
|
|
2750
|
+
const accountToSign = this._session?.wallet?.address;
|
|
2710
2751
|
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2711
2752
|
try {
|
|
2712
2753
|
const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
|
|
@@ -2727,10 +2768,10 @@ var PollarClient = class {
|
|
|
2727
2768
|
return { status: "error", ...details && { details } };
|
|
2728
2769
|
}
|
|
2729
2770
|
}
|
|
2730
|
-
const
|
|
2771
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2731
2772
|
try {
|
|
2732
2773
|
const { data, error } = await this._api.POST("/tx/sign", {
|
|
2733
|
-
body: { network: this.getNetwork(),
|
|
2774
|
+
body: { network: this.getNetwork(), address, unsignedXdr }
|
|
2734
2775
|
});
|
|
2735
2776
|
if (!error && data?.success && data.content?.signedXdr) {
|
|
2736
2777
|
const { signedXdr, idempotencyKey } = data.content;
|
|
@@ -2783,12 +2824,12 @@ var PollarClient = class {
|
|
|
2783
2824
|
const buildData = this._currentBuildData();
|
|
2784
2825
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
2785
2826
|
this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
|
|
2786
|
-
const
|
|
2827
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2787
2828
|
try {
|
|
2788
2829
|
const { data, error } = await this._api.POST("/tx/submit", {
|
|
2789
2830
|
body: {
|
|
2790
2831
|
network: this.getNetwork(),
|
|
2791
|
-
|
|
2832
|
+
address,
|
|
2792
2833
|
signedXdr,
|
|
2793
2834
|
...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
|
|
2794
2835
|
}
|
|
@@ -2848,6 +2889,19 @@ var PollarClient = class {
|
|
|
2848
2889
|
* `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
|
|
2849
2890
|
*/
|
|
2850
2891
|
async signAndSubmitTx(unsignedXdr) {
|
|
2892
|
+
if (this._session?.wallet?.type === "smart") {
|
|
2893
|
+
const buildData2 = this._currentBuildData();
|
|
2894
|
+
if (!buildData2?.smart) {
|
|
2895
|
+
const details = "no prepared smart transaction; call buildTx first";
|
|
2896
|
+
this._setTransactionState({ step: "error", phase: "signing", details });
|
|
2897
|
+
return { status: "error", details };
|
|
2898
|
+
}
|
|
2899
|
+
return this._signSubmitSmart(buildData2);
|
|
2900
|
+
}
|
|
2901
|
+
if (!unsignedXdr) {
|
|
2902
|
+
this._setTransactionState({ step: "error", phase: "signing", details: "missing unsigned transaction" });
|
|
2903
|
+
return { status: "error", details: "missing unsigned transaction" };
|
|
2904
|
+
}
|
|
2851
2905
|
if (this._walletAdapter) {
|
|
2852
2906
|
const signed = await this.signTx(unsignedXdr);
|
|
2853
2907
|
if (signed.status === "error") {
|
|
@@ -2865,7 +2919,7 @@ var PollarClient = class {
|
|
|
2865
2919
|
this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
|
|
2866
2920
|
const body = {
|
|
2867
2921
|
network: this.getNetwork(),
|
|
2868
|
-
|
|
2922
|
+
address: this._session?.wallet?.address ?? "",
|
|
2869
2923
|
unsignedXdr
|
|
2870
2924
|
};
|
|
2871
2925
|
try {
|
|
@@ -2934,14 +2988,20 @@ var PollarClient = class {
|
|
|
2934
2988
|
* `signTx`, and `submitTx` separately instead.
|
|
2935
2989
|
*/
|
|
2936
2990
|
async buildAndSignAndSubmitTx(operation, params, options) {
|
|
2991
|
+
if (this._session?.wallet?.type === "smart") {
|
|
2992
|
+
return this._runSmartTx(operation, params, options);
|
|
2993
|
+
}
|
|
2937
2994
|
if (this._walletAdapter) {
|
|
2938
2995
|
const built = await this.buildTx(operation, params, options);
|
|
2939
2996
|
if (built.status === "error") {
|
|
2940
2997
|
return { status: "error", ...built.details && { details: built.details } };
|
|
2941
2998
|
}
|
|
2999
|
+
if (!built.buildData.unsignedXdr) {
|
|
3000
|
+
return { status: "error", details: "build returned no unsigned transaction" };
|
|
3001
|
+
}
|
|
2942
3002
|
return this.signAndSubmitTx(built.buildData.unsignedXdr);
|
|
2943
3003
|
}
|
|
2944
|
-
if (!this._session?.wallet?.
|
|
3004
|
+
if (!this._session?.wallet?.address) {
|
|
2945
3005
|
this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
|
|
2946
3006
|
return { status: "error", details: "No wallet connected" };
|
|
2947
3007
|
}
|
|
@@ -2950,7 +3010,7 @@ var PollarClient = class {
|
|
|
2950
3010
|
const { data, error } = await this._api.POST("/tx/build-sign-submit", {
|
|
2951
3011
|
body: {
|
|
2952
3012
|
network: this.getNetwork(),
|
|
2953
|
-
|
|
3013
|
+
address: this._session.wallet.address,
|
|
2954
3014
|
operation,
|
|
2955
3015
|
params,
|
|
2956
3016
|
options: options ?? {}
|
|
@@ -2994,6 +3054,113 @@ var PollarClient = class {
|
|
|
2994
3054
|
async runTx(operation, params, options) {
|
|
2995
3055
|
return this.buildAndSignAndSubmitTx(operation, params, options);
|
|
2996
3056
|
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Smart-wallet (passkey / C-address) transaction: build (server prepares the
|
|
3059
|
+
* SAC transfer + returns the auth digest) → sign the digest with the passkey
|
|
3060
|
+
* → submit (server assembles the signed auth entry and broadcasts; the
|
|
3061
|
+
* sponsor pays the fee). State machine: building → built → signing →
|
|
3062
|
+
* submitting → success.
|
|
3063
|
+
*/
|
|
3064
|
+
async _runSmartTx(operation, params, options) {
|
|
3065
|
+
const address = this._session?.wallet?.address;
|
|
3066
|
+
if (!address) {
|
|
3067
|
+
this._setTransactionState({ step: "error", phase: "building", details: "No wallet connected" });
|
|
3068
|
+
return { status: "error", details: "No wallet connected" };
|
|
3069
|
+
}
|
|
3070
|
+
if (!this._passkeySign) {
|
|
3071
|
+
const details = "Passkey signer not configured";
|
|
3072
|
+
this._setTransactionState({ step: "error", phase: "signing", details });
|
|
3073
|
+
return { status: "error", details };
|
|
3074
|
+
}
|
|
3075
|
+
this._setTransactionState({ step: "building" });
|
|
3076
|
+
let buildData;
|
|
3077
|
+
try {
|
|
3078
|
+
const body = {
|
|
3079
|
+
network: this.getNetwork(),
|
|
3080
|
+
address,
|
|
3081
|
+
operation,
|
|
3082
|
+
params,
|
|
3083
|
+
options: options ?? {}
|
|
3084
|
+
};
|
|
3085
|
+
const { data, error } = await this._api.POST("/tx/build", { body });
|
|
3086
|
+
if (error || !data?.success || !data.content?.smart) {
|
|
3087
|
+
const details = error?.details ?? "Failed to build transaction";
|
|
3088
|
+
this._setTransactionState({ step: "error", phase: "building", details });
|
|
3089
|
+
return { status: "error", details };
|
|
3090
|
+
}
|
|
3091
|
+
buildData = data.content;
|
|
3092
|
+
} catch (err) {
|
|
3093
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3094
|
+
this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
|
|
3095
|
+
return { status: "error", ...details && { details } };
|
|
3096
|
+
}
|
|
3097
|
+
this._setTransactionState({ step: "built", buildData });
|
|
3098
|
+
return this._signSubmitSmart(buildData);
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Steps 2–3 of the smart-wallet flow: sign the prepared auth digest with the
|
|
3102
|
+
* passkey, then submit. Shared by `_runSmartTx` (atomic) and `signAndSubmitTx`
|
|
3103
|
+
* (split flow, when a smart build is already on the state machine).
|
|
3104
|
+
*/
|
|
3105
|
+
async _signSubmitSmart(buildData) {
|
|
3106
|
+
const address = this._session?.wallet?.address;
|
|
3107
|
+
const smart = buildData.smart;
|
|
3108
|
+
if (!address || !smart) {
|
|
3109
|
+
const details = "no prepared smart transaction";
|
|
3110
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, details });
|
|
3111
|
+
return { status: "error", buildData, details };
|
|
3112
|
+
}
|
|
3113
|
+
if (!this._passkeySign) {
|
|
3114
|
+
const details = "Passkey signer not configured";
|
|
3115
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, details });
|
|
3116
|
+
return { status: "error", buildData, details };
|
|
3117
|
+
}
|
|
3118
|
+
this._setTransactionState({ step: "signing", buildData });
|
|
3119
|
+
let assertion;
|
|
3120
|
+
try {
|
|
3121
|
+
assertion = await this._passkeySign({ credentialId: smart.credentialId, challenge: smart.digest });
|
|
3122
|
+
} catch (err) {
|
|
3123
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3124
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, ...details && { details } });
|
|
3125
|
+
return { status: "error", buildData, ...details && { details } };
|
|
3126
|
+
}
|
|
3127
|
+
this._setTransactionState({ step: "submitting", buildData });
|
|
3128
|
+
const outcomeExtra = { buildData };
|
|
3129
|
+
try {
|
|
3130
|
+
const { data, error } = await this._api.POST("/tx/submit", {
|
|
3131
|
+
body: {
|
|
3132
|
+
network: this.getNetwork(),
|
|
3133
|
+
address,
|
|
3134
|
+
smart: { entryXdr: smart.entryXdr, funcXdr: smart.funcXdr, assertion }
|
|
3135
|
+
}
|
|
3136
|
+
});
|
|
3137
|
+
if (!error && data?.success && data.content) {
|
|
3138
|
+
const { hash, status: backendStatus, resultCode } = data.content;
|
|
3139
|
+
if (backendStatus === "SUCCESS") {
|
|
3140
|
+
this._setTransactionState({ step: "success", hash, buildData });
|
|
3141
|
+
return { status: "success", hash, ...outcomeExtra };
|
|
3142
|
+
}
|
|
3143
|
+
if (backendStatus === "PENDING") {
|
|
3144
|
+
this._setTransactionState({ step: "submitted", hash, buildData });
|
|
3145
|
+
return { status: "pending", hash, ...outcomeExtra };
|
|
3146
|
+
}
|
|
3147
|
+
this._setTransactionState({
|
|
3148
|
+
step: "error",
|
|
3149
|
+
phase: "submitting",
|
|
3150
|
+
buildData,
|
|
3151
|
+
...resultCode && { details: resultCode }
|
|
3152
|
+
});
|
|
3153
|
+
return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
|
|
3154
|
+
}
|
|
3155
|
+
const details = error?.details;
|
|
3156
|
+
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
3157
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
3158
|
+
} catch (err) {
|
|
3159
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3160
|
+
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
3161
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
2997
3164
|
// ─── App config ───────────────────────────────────────────────────────────
|
|
2998
3165
|
async getAppConfig() {
|
|
2999
3166
|
try {
|
|
@@ -3047,10 +3214,18 @@ var PollarClient = class {
|
|
|
3047
3214
|
this._txHistoryState = next;
|
|
3048
3215
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
3049
3216
|
}
|
|
3217
|
+
_setSessionsState(next) {
|
|
3218
|
+
this._sessionsState = next;
|
|
3219
|
+
for (const cb of this._sessionsStateListeners) cb(next);
|
|
3220
|
+
}
|
|
3050
3221
|
_setWalletBalanceState(next) {
|
|
3051
3222
|
this._walletBalanceState = next;
|
|
3052
3223
|
for (const cb of this._walletBalanceStateListeners) cb(next);
|
|
3053
3224
|
}
|
|
3225
|
+
_setEnabledAssetsState(next) {
|
|
3226
|
+
this._enabledAssetsState = next;
|
|
3227
|
+
for (const cb of this._enabledAssetsStateListeners) cb(next);
|
|
3228
|
+
}
|
|
3054
3229
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
3055
3230
|
_newController() {
|
|
3056
3231
|
this._loginController?.abort();
|
|
@@ -3075,6 +3250,7 @@ var PollarClient = class {
|
|
|
3075
3250
|
this._walletAdapter = adapter;
|
|
3076
3251
|
await writeWalletType(this._storage, this.apiKeyHash, id);
|
|
3077
3252
|
},
|
|
3253
|
+
...this._passkey ? { passkey: this._passkey } : {},
|
|
3078
3254
|
...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
|
|
3079
3255
|
};
|
|
3080
3256
|
}
|
|
@@ -3104,7 +3280,7 @@ var PollarClient = class {
|
|
|
3104
3280
|
}
|
|
3105
3281
|
}
|
|
3106
3282
|
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
3107
|
-
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
3283
|
+
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
|
|
3108
3284
|
throw new Error(
|
|
3109
3285
|
`[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
|
|
3110
3286
|
);
|
|
@@ -3145,21 +3321,66 @@ var PollarClient = class {
|
|
|
3145
3321
|
}
|
|
3146
3322
|
}
|
|
3147
3323
|
console.info("[PollarClient] Session restored from storage");
|
|
3148
|
-
this._setAuthState({ step: "authenticated", session: this._session });
|
|
3324
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: false });
|
|
3149
3325
|
this._scheduleNextRefresh();
|
|
3326
|
+
void this._resume();
|
|
3150
3327
|
} else {
|
|
3151
3328
|
console.info("[PollarClient] No session in storage");
|
|
3152
3329
|
}
|
|
3153
3330
|
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Validate the restored session against the server and repopulate the
|
|
3333
|
+
* in-memory profile (PII is never persisted, so it's null after a cold
|
|
3334
|
+
* reload). Goes through the normal authed client, so it coalesces with any
|
|
3335
|
+
* in-flight refresh (onRequest awaits `_refreshPromise`) and, being a GET,
|
|
3336
|
+
* is auto-retried after a 401-triggered refresh.
|
|
3337
|
+
*
|
|
3338
|
+
* - 200 → store profile, mark the session `verified`.
|
|
3339
|
+
* - 401 → the refresh-on-401 path already ran; if the family was
|
|
3340
|
+
* revoked, refresh failed and `_clearSession()` took us to
|
|
3341
|
+
* idle. Nothing to do here — don't double-handle.
|
|
3342
|
+
* - network error → stay optimistic (do NOT log out); revalidated later on
|
|
3343
|
+
* `visibilitychange` or first use.
|
|
3344
|
+
*/
|
|
3345
|
+
async _resume() {
|
|
3346
|
+
if (!this._session) return;
|
|
3347
|
+
this._resumeController?.abort();
|
|
3348
|
+
const controller = new AbortController();
|
|
3349
|
+
this._resumeController = controller;
|
|
3350
|
+
try {
|
|
3351
|
+
const { data, error } = await this._api.GET("/auth/session/resume", { signal: controller.signal });
|
|
3352
|
+
if (error || !data) return;
|
|
3353
|
+
const content = data.content;
|
|
3354
|
+
if (!content || !this._session) return;
|
|
3355
|
+
this._profile = { ...content };
|
|
3356
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: true });
|
|
3357
|
+
} catch (err) {
|
|
3358
|
+
if (err?.name === "AbortError") return;
|
|
3359
|
+
console.warn("[PollarClient] resume failed (network); will retry", err);
|
|
3360
|
+
} finally {
|
|
3361
|
+
if (this._resumeController === controller) this._resumeController = null;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3154
3364
|
async _storeSession(session) {
|
|
3155
3365
|
console.info("[PollarClient] Session stored");
|
|
3366
|
+
const w = session.wallet;
|
|
3156
3367
|
const persisted = {
|
|
3157
3368
|
clientSessionId: session.clientSessionId,
|
|
3158
3369
|
userId: session.userId ?? null,
|
|
3159
3370
|
status: session.status,
|
|
3160
3371
|
token: session.token,
|
|
3161
3372
|
user: session.user,
|
|
3162
|
-
|
|
3373
|
+
// The wire response still carries the legacy `publicKey` alias (kept for
|
|
3374
|
+
// older SDKs); the persisted session standardizes on `address` only.
|
|
3375
|
+
wallet: {
|
|
3376
|
+
type: w.type,
|
|
3377
|
+
address: w.address ?? w.publicKey ?? null,
|
|
3378
|
+
...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
|
|
3379
|
+
...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
|
|
3380
|
+
...w.linkedAt !== void 0 ? { linkedAt: w.linkedAt } : {},
|
|
3381
|
+
...w.network !== void 0 ? { network: w.network } : {},
|
|
3382
|
+
...w.deployTxHash !== void 0 ? { deployTxHash: w.deployTxHash } : {}
|
|
3383
|
+
}
|
|
3163
3384
|
};
|
|
3164
3385
|
this._session = persisted;
|
|
3165
3386
|
if (session.data) {
|
|
@@ -3172,7 +3393,7 @@ var PollarClient = class {
|
|
|
3172
3393
|
};
|
|
3173
3394
|
}
|
|
3174
3395
|
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
3175
|
-
this._setAuthState({ step: "authenticated", session: persisted });
|
|
3396
|
+
this._setAuthState({ step: "authenticated", session: persisted, verified: true });
|
|
3176
3397
|
this._scheduleNextRefresh();
|
|
3177
3398
|
}
|
|
3178
3399
|
async _clearSession() {
|
|
@@ -3224,6 +3445,196 @@ var PollarClient = class {
|
|
|
3224
3445
|
}
|
|
3225
3446
|
};
|
|
3226
3447
|
|
|
3448
|
+
// src/keys/web-crypto.ts
|
|
3449
|
+
var DB_NAME = "pollar-keys";
|
|
3450
|
+
var DB_VERSION = 1;
|
|
3451
|
+
var STORE_NAME = "keys";
|
|
3452
|
+
function openDb() {
|
|
3453
|
+
return new Promise((resolve, reject) => {
|
|
3454
|
+
if (typeof indexedDB === "undefined") {
|
|
3455
|
+
reject(new Error("[PollarClient:keys] IndexedDB not available"));
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
3459
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB open failed"));
|
|
3460
|
+
req.onsuccess = () => resolve(req.result);
|
|
3461
|
+
req.onupgradeneeded = () => {
|
|
3462
|
+
const db = req.result;
|
|
3463
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
3464
|
+
db.createObjectStore(STORE_NAME);
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
});
|
|
3468
|
+
}
|
|
3469
|
+
function awaitTx(req) {
|
|
3470
|
+
return new Promise((resolve, reject) => {
|
|
3471
|
+
req.onsuccess = () => resolve(req.result);
|
|
3472
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB request failed"));
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
async function dbGet(key) {
|
|
3476
|
+
const db = await openDb();
|
|
3477
|
+
try {
|
|
3478
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
3479
|
+
const result = await awaitTx(tx.objectStore(STORE_NAME).get(key));
|
|
3480
|
+
return result;
|
|
3481
|
+
} finally {
|
|
3482
|
+
db.close();
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
async function dbPut(key, value) {
|
|
3486
|
+
const db = await openDb();
|
|
3487
|
+
try {
|
|
3488
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
3489
|
+
await awaitTx(tx.objectStore(STORE_NAME).put(value, key));
|
|
3490
|
+
} finally {
|
|
3491
|
+
db.close();
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
async function dbDelete(key) {
|
|
3495
|
+
const db = await openDb();
|
|
3496
|
+
try {
|
|
3497
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
3498
|
+
await awaitTx(tx.objectStore(STORE_NAME).delete(key));
|
|
3499
|
+
} finally {
|
|
3500
|
+
db.close();
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
function isCryptoKeyPair(v) {
|
|
3504
|
+
if (typeof v !== "object" || v === null) return false;
|
|
3505
|
+
const obj = v;
|
|
3506
|
+
return obj.privateKey !== void 0 && obj.publicKey !== void 0;
|
|
3507
|
+
}
|
|
3508
|
+
var WebCryptoKeyManager = class {
|
|
3509
|
+
constructor(apiKey) {
|
|
3510
|
+
this.apiKeyHash = null;
|
|
3511
|
+
this.keyPair = null;
|
|
3512
|
+
this.publicJwk = null;
|
|
3513
|
+
this.thumbprint = null;
|
|
3514
|
+
/**
|
|
3515
|
+
* Cached in-flight init. Lets `init()` be called concurrently (or implicitly
|
|
3516
|
+
* from `getPublicJwk` / `sign`) without doing the work twice. Cleared on
|
|
3517
|
+
* failure so callers can retry, and cleared on `reset()`.
|
|
3518
|
+
*/
|
|
3519
|
+
this._initPromise = null;
|
|
3520
|
+
if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
|
|
3521
|
+
throw new Error("[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost).");
|
|
3522
|
+
}
|
|
3523
|
+
this.apiKey = apiKey;
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* Idempotent and safe under concurrency. The first call kicks off the real
|
|
3527
|
+
* init; subsequent (and concurrent) calls return the same in-flight promise.
|
|
3528
|
+
* Other methods (`getPublicJwk`, `getThumbprint`, `sign`) auto-await this so
|
|
3529
|
+
* the manager is self-healing if `init()` was never explicitly invoked.
|
|
3530
|
+
*/
|
|
3531
|
+
async init() {
|
|
3532
|
+
if (this.keyPair) return;
|
|
3533
|
+
if (!this._initPromise) {
|
|
3534
|
+
this._initPromise = this._doInit().catch((err) => {
|
|
3535
|
+
console.error("[PollarClient:keys] WebCryptoKeyManager init failed", err);
|
|
3536
|
+
this._initPromise = null;
|
|
3537
|
+
throw err;
|
|
3538
|
+
});
|
|
3539
|
+
}
|
|
3540
|
+
return this._initPromise;
|
|
3541
|
+
}
|
|
3542
|
+
async _doInit() {
|
|
3543
|
+
if (!this.apiKeyHash) {
|
|
3544
|
+
this.apiKeyHash = await hashApiKey(this.apiKey);
|
|
3545
|
+
}
|
|
3546
|
+
let pair;
|
|
3547
|
+
try {
|
|
3548
|
+
pair = await dbGet(this.apiKeyHash);
|
|
3549
|
+
if (pair && !isCryptoKeyPair(pair)) pair = void 0;
|
|
3550
|
+
} catch {
|
|
3551
|
+
pair = void 0;
|
|
3552
|
+
}
|
|
3553
|
+
if (!pair) {
|
|
3554
|
+
pair = await globalThis.crypto.subtle.generateKey(
|
|
3555
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
3556
|
+
// false → private key non-extractable; per W3C ECDSA spec the public
|
|
3557
|
+
// key is always extractable regardless of this flag.
|
|
3558
|
+
false,
|
|
3559
|
+
["sign", "verify"]
|
|
3560
|
+
);
|
|
3561
|
+
try {
|
|
3562
|
+
await dbPut(this.apiKeyHash, pair);
|
|
3563
|
+
} catch {
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
this.keyPair = pair;
|
|
3567
|
+
this.publicJwk = await this._exportPublicJwk(pair.publicKey);
|
|
3568
|
+
this.thumbprint = await computeJwkThumbprint(this.publicJwk);
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Derive the public JWK from a `CryptoKey`. Prefers the `'raw'` export (the
|
|
3572
|
+
* 65-byte uncompressed point `0x04 || X(32) || Y(32)`) and base64url-encodes
|
|
3573
|
+
* the coordinates ourselves — that sidesteps polyfills whose `exportKey('jwk')`
|
|
3574
|
+
* emits non-base64url `x`/`y` (standard base64, `=` padding, or — as seen with
|
|
3575
|
+
* `react-native-quick-crypto` — a stray `.`). Real browsers and most polyfills
|
|
3576
|
+
* support `'raw'` for public EC keys.
|
|
3577
|
+
*
|
|
3578
|
+
* Falls back to the `'jwk'` export (normalized via `canonicalEcJwk`) if `'raw'`
|
|
3579
|
+
* is unsupported or returns an unexpected shape, so this can't regress on a
|
|
3580
|
+
* runtime that only implements the JWK path. Both routes yield identical
|
|
3581
|
+
* coordinate bytes, so the `cnf.jkt` thumbprint is unchanged either way.
|
|
3582
|
+
*/
|
|
3583
|
+
async _exportPublicJwk(publicKey) {
|
|
3584
|
+
try {
|
|
3585
|
+
const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey("raw", publicKey));
|
|
3586
|
+
if (raw.length !== 65 || raw[0] !== 4) {
|
|
3587
|
+
throw new Error(`[PollarClient:keys] Unexpected raw EC point (len=${raw.length}, tag=${raw[0]})`);
|
|
3588
|
+
}
|
|
3589
|
+
return {
|
|
3590
|
+
kty: "EC",
|
|
3591
|
+
crv: "P-256",
|
|
3592
|
+
x: base64urlEncode(raw.slice(1, 33)),
|
|
3593
|
+
y: base64urlEncode(raw.slice(33, 65))
|
|
3594
|
+
};
|
|
3595
|
+
} catch {
|
|
3596
|
+
const jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
|
|
3597
|
+
return canonicalEcJwk(jwk);
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
async reset() {
|
|
3601
|
+
try {
|
|
3602
|
+
if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
|
|
3603
|
+
} catch {
|
|
3604
|
+
}
|
|
3605
|
+
this.keyPair = null;
|
|
3606
|
+
this.publicJwk = null;
|
|
3607
|
+
this.thumbprint = null;
|
|
3608
|
+
this._initPromise = null;
|
|
3609
|
+
}
|
|
3610
|
+
async getPublicJwk() {
|
|
3611
|
+
if (!this.publicJwk) await this.init();
|
|
3612
|
+
if (!this.publicJwk) {
|
|
3613
|
+
throw new Error("[PollarClient:keys] Keypair initialization failed; getPublicJwk unavailable");
|
|
3614
|
+
}
|
|
3615
|
+
return { kty: this.publicJwk.kty, crv: this.publicJwk.crv, x: this.publicJwk.x, y: this.publicJwk.y };
|
|
3616
|
+
}
|
|
3617
|
+
async getThumbprint() {
|
|
3618
|
+
if (!this.thumbprint) await this.init();
|
|
3619
|
+
if (!this.thumbprint) {
|
|
3620
|
+
throw new Error("[PollarClient:keys] Keypair initialization failed; getThumbprint unavailable");
|
|
3621
|
+
}
|
|
3622
|
+
return this.thumbprint;
|
|
3623
|
+
}
|
|
3624
|
+
async sign(payload) {
|
|
3625
|
+
if (!this.keyPair) await this.init();
|
|
3626
|
+
if (!this.keyPair) {
|
|
3627
|
+
throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
|
|
3628
|
+
}
|
|
3629
|
+
const sig = await globalThis.crypto.subtle.sign(
|
|
3630
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
3631
|
+
this.keyPair.privateKey,
|
|
3632
|
+
payload
|
|
3633
|
+
);
|
|
3634
|
+
return new Uint8Array(sig);
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
|
|
3227
3638
|
// src/stellar/StellarClient.ts
|
|
3228
3639
|
var HORIZON_URLS = {
|
|
3229
3640
|
mainnet: "https://horizon.stellar.org",
|
|
@@ -3254,10 +3665,6 @@ var StellarClient = class {
|
|
|
3254
3665
|
|
|
3255
3666
|
// src/index.rn.ts
|
|
3256
3667
|
_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
3668
|
return new NobleKeyManager(storage, apiKey);
|
|
3262
3669
|
});
|
|
3263
3670
|
|
|
@@ -3265,6 +3672,7 @@ exports.AUTH_ERROR_CODES = AUTH_ERROR_CODES;
|
|
|
3265
3672
|
exports.AlbedoAdapter = AlbedoAdapter;
|
|
3266
3673
|
exports.FreighterAdapter = FreighterAdapter;
|
|
3267
3674
|
exports.NobleKeyManager = NobleKeyManager;
|
|
3675
|
+
exports.POLLAR_CORE_VERSION = POLLAR_CORE_VERSION;
|
|
3268
3676
|
exports.PollarClient = PollarClient;
|
|
3269
3677
|
exports.StellarClient = StellarClient;
|
|
3270
3678
|
exports.WalletType = WalletType;
|