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