@pollar/core 0.8.1 → 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/README.md +24 -16
- package/dist/adapters/expo-secure-store.d.mts +11 -1
- package/dist/adapters/expo-secure-store.d.ts +11 -1
- package/dist/adapters/expo-secure-store.js +11 -3
- package/dist/adapters/expo-secure-store.js.map +1 -1
- package/dist/adapters/expo-secure-store.mjs +11 -4
- package/dist/adapters/expo-secure-store.mjs.map +1 -1
- 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 +471 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +471 -61
- 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 +621 -215
- package/dist/index.rn.js.map +1 -1
- package/dist/index.rn.mjs +618 -213
- 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 +2 -1
package/dist/index.rn.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { p256 } from '@noble/curves/
|
|
1
|
+
import { p256 } from '@noble/curves/nist';
|
|
2
|
+
import { sha256 as sha256$1 } from '@noble/hashes/sha2';
|
|
2
3
|
|
|
3
4
|
var __create = Object.create;
|
|
4
5
|
var __defProp = Object.defineProperty;
|
|
@@ -184,11 +185,8 @@ function defaultKeyManager(storage, apiKey) {
|
|
|
184
185
|
}
|
|
185
186
|
return _factory(storage, apiKey);
|
|
186
187
|
}
|
|
187
|
-
|
|
188
|
-
// src/lib/sha256.ts
|
|
189
188
|
async function sha256(data) {
|
|
190
|
-
|
|
191
|
-
return new Uint8Array(buf);
|
|
189
|
+
return sha256$1(data);
|
|
192
190
|
}
|
|
193
191
|
|
|
194
192
|
// src/lib/api-key-hash.ts
|
|
@@ -267,11 +265,14 @@ async function computeJwkThumbprint(jwk) {
|
|
|
267
265
|
const digest = await sha256(new TextEncoder().encode(canonical));
|
|
268
266
|
return base64urlEncode(digest);
|
|
269
267
|
}
|
|
268
|
+
function toBase64url(value) {
|
|
269
|
+
return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/[^A-Za-z0-9_-]/g, "");
|
|
270
|
+
}
|
|
270
271
|
function canonicalEcJwk(jwk) {
|
|
271
272
|
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
|
|
272
273
|
throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
|
|
273
274
|
}
|
|
274
|
-
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) };
|
|
275
276
|
}
|
|
276
277
|
|
|
277
278
|
// src/keys/noble.ts
|
|
@@ -377,167 +378,6 @@ var NobleKeyManager = class {
|
|
|
377
378
|
}
|
|
378
379
|
};
|
|
379
380
|
|
|
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
381
|
// ../../node_modules/openapi-fetch/dist/index.mjs
|
|
542
382
|
var PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
543
383
|
var supportsRequestInitExt = () => {
|
|
@@ -1257,6 +1097,9 @@ function defaultStorage(options = {}) {
|
|
|
1257
1097
|
return createLocalStorageAdapter(options);
|
|
1258
1098
|
}
|
|
1259
1099
|
|
|
1100
|
+
// src/version.ts
|
|
1101
|
+
var POLLAR_CORE_VERSION = "0.9.0-rc.0" ;
|
|
1102
|
+
|
|
1260
1103
|
// src/visibility/noop.ts
|
|
1261
1104
|
function createNoopVisibilityProvider() {
|
|
1262
1105
|
return {
|
|
@@ -1321,6 +1164,7 @@ var AUTH_ERROR_CODES = {
|
|
|
1321
1164
|
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1322
1165
|
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1323
1166
|
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1167
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1324
1168
|
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1325
1169
|
};
|
|
1326
1170
|
var PollarFlowError = class extends Error {
|
|
@@ -1366,7 +1210,7 @@ var FreighterAdapter = class {
|
|
|
1366
1210
|
if (!userInfo?.publicKey) {
|
|
1367
1211
|
throw new Error("Failed to get user information from Freighter");
|
|
1368
1212
|
}
|
|
1369
|
-
return { address: userInfo.publicKey
|
|
1213
|
+
return { address: userInfo.publicKey };
|
|
1370
1214
|
}
|
|
1371
1215
|
async disconnect() {
|
|
1372
1216
|
}
|
|
@@ -1404,6 +1248,19 @@ var FreighterAdapter = class {
|
|
|
1404
1248
|
};
|
|
1405
1249
|
|
|
1406
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
|
+
}
|
|
1407
1264
|
function openAlbedoPopup(url) {
|
|
1408
1265
|
const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
|
|
1409
1266
|
if (!popup) {
|
|
@@ -1442,7 +1299,13 @@ function waitForAlbedoResult() {
|
|
|
1442
1299
|
});
|
|
1443
1300
|
}
|
|
1444
1301
|
var AlbedoAdapter = class {
|
|
1445
|
-
|
|
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;
|
|
1446
1309
|
this.type = "albedo" /* ALBEDO */;
|
|
1447
1310
|
}
|
|
1448
1311
|
async isAvailable() {
|
|
@@ -1452,7 +1315,7 @@ var AlbedoAdapter = class {
|
|
|
1452
1315
|
const url = new URL("https://albedo.link");
|
|
1453
1316
|
url.searchParams.set("intent", "public-key");
|
|
1454
1317
|
url.searchParams.set("app_name", "Pollar");
|
|
1455
|
-
url.searchParams.set("network",
|
|
1318
|
+
url.searchParams.set("network", this.network);
|
|
1456
1319
|
url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
|
|
1457
1320
|
url.searchParams.set("origin", window.location.origin);
|
|
1458
1321
|
openAlbedoPopup(url.toString());
|
|
@@ -1460,7 +1323,7 @@ var AlbedoAdapter = class {
|
|
|
1460
1323
|
if (!result.pubkey) {
|
|
1461
1324
|
throw new Error("Albedo connection rejected");
|
|
1462
1325
|
}
|
|
1463
|
-
return { address: result.pubkey
|
|
1326
|
+
return { address: result.pubkey };
|
|
1464
1327
|
}
|
|
1465
1328
|
async disconnect() {
|
|
1466
1329
|
}
|
|
@@ -1470,12 +1333,12 @@ var AlbedoAdapter = class {
|
|
|
1470
1333
|
async getNetwork() {
|
|
1471
1334
|
throw new Error("Albedo does not expose network");
|
|
1472
1335
|
}
|
|
1473
|
-
async signTransaction(xdr,
|
|
1336
|
+
async signTransaction(xdr, options) {
|
|
1474
1337
|
const url = new URL("https://albedo.link");
|
|
1475
1338
|
url.searchParams.set("intent", "tx");
|
|
1476
1339
|
url.searchParams.set("xdr", xdr);
|
|
1477
1340
|
url.searchParams.set("app_name", "Pollar");
|
|
1478
|
-
url.searchParams.set("network",
|
|
1341
|
+
url.searchParams.set("network", albedoNetwork(options, this.network));
|
|
1479
1342
|
url.searchParams.set("callback", window.location.href);
|
|
1480
1343
|
url.searchParams.set("origin", window.location.origin);
|
|
1481
1344
|
window.location.href = url.toString();
|
|
@@ -1488,7 +1351,7 @@ var AlbedoAdapter = class {
|
|
|
1488
1351
|
url.searchParams.set("intent", "sign-auth-entry");
|
|
1489
1352
|
url.searchParams.set("xdr", entryXdr);
|
|
1490
1353
|
url.searchParams.set("app_name", "Pollar");
|
|
1491
|
-
url.searchParams.set("network",
|
|
1354
|
+
url.searchParams.set("network", this.network);
|
|
1492
1355
|
url.searchParams.set("callback", window.location.href);
|
|
1493
1356
|
url.searchParams.set("origin", window.location.origin);
|
|
1494
1357
|
window.location.href = url.toString();
|
|
@@ -1575,8 +1438,12 @@ function isValidSession(value) {
|
|
|
1575
1438
|
return false;
|
|
1576
1439
|
}
|
|
1577
1440
|
const w = wallet;
|
|
1578
|
-
if (w["
|
|
1579
|
-
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");
|
|
1580
1447
|
return false;
|
|
1581
1448
|
}
|
|
1582
1449
|
if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
|
|
@@ -1587,6 +1454,10 @@ function isValidSession(value) {
|
|
|
1587
1454
|
console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
|
|
1588
1455
|
return false;
|
|
1589
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
|
+
}
|
|
1590
1461
|
return true;
|
|
1591
1462
|
}
|
|
1592
1463
|
async function readStorage(storage, apiKeyHash) {
|
|
@@ -1594,6 +1465,12 @@ async function readStorage(storage, apiKeyHash) {
|
|
|
1594
1465
|
if (!raw) return null;
|
|
1595
1466
|
try {
|
|
1596
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
|
+
}
|
|
1597
1474
|
if (!isValidSession(session)) {
|
|
1598
1475
|
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1599
1476
|
console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
|
|
@@ -1684,7 +1561,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1684
1561
|
}));
|
|
1685
1562
|
} catch (e) {
|
|
1686
1563
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1687
|
-
console.warn(e);
|
|
1564
|
+
console.warn("[PollarClient:stream] session-status request failed; will retry", e);
|
|
1688
1565
|
}
|
|
1689
1566
|
if (error || !data) {
|
|
1690
1567
|
await sleep(backoff);
|
|
@@ -1724,7 +1601,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1724
1601
|
} catch (e) {
|
|
1725
1602
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1726
1603
|
if (e instanceof SessionStatusError) throw e;
|
|
1727
|
-
console.warn(e);
|
|
1604
|
+
console.warn("[PollarClient:stream] session-status stream read failed; will retry", e);
|
|
1728
1605
|
} finally {
|
|
1729
1606
|
reader.releaseLock();
|
|
1730
1607
|
}
|
|
@@ -1752,7 +1629,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
|
|
|
1752
1629
|
envelope = await response.json().catch(() => null);
|
|
1753
1630
|
} catch (e) {
|
|
1754
1631
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1755
|
-
console.warn(e);
|
|
1632
|
+
console.warn("[PollarClient:stream] session-status poll failed; will retry", e);
|
|
1756
1633
|
}
|
|
1757
1634
|
if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
|
|
1758
1635
|
throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
|
|
@@ -1956,6 +1833,55 @@ async function loginOAuth(provider, deps) {
|
|
|
1956
1833
|
await authenticate(clientSessionId, deps);
|
|
1957
1834
|
}
|
|
1958
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
|
+
|
|
1959
1885
|
// src/client/auth/walletFlow.ts
|
|
1960
1886
|
function withSignal(promise, signal) {
|
|
1961
1887
|
return Promise.race([
|
|
@@ -1982,12 +1908,12 @@ async function loginWallet(type, deps) {
|
|
|
1982
1908
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
1983
1909
|
return;
|
|
1984
1910
|
}
|
|
1985
|
-
const {
|
|
1986
|
-
connectedWallet =
|
|
1911
|
+
const { address } = await withSignal(adapter.connect(), signal);
|
|
1912
|
+
connectedWallet = address;
|
|
1987
1913
|
deps.storeWalletAdapter(adapter, type);
|
|
1988
1914
|
setAuthState({ step: "authenticating_wallet" });
|
|
1989
1915
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
|
|
1990
|
-
body: { clientSessionId, walletAddress:
|
|
1916
|
+
body: { clientSessionId, walletAddress: address },
|
|
1991
1917
|
signal
|
|
1992
1918
|
});
|
|
1993
1919
|
if (walletError || !walletData?.success) {
|
|
@@ -2052,8 +1978,12 @@ var PollarClient = class {
|
|
|
2052
1978
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
2053
1979
|
this._txHistoryState = { step: "idle" };
|
|
2054
1980
|
this._txHistoryStateListeners = /* @__PURE__ */ new Set();
|
|
1981
|
+
this._sessionsState = { step: "idle" };
|
|
1982
|
+
this._sessionsStateListeners = /* @__PURE__ */ new Set();
|
|
2055
1983
|
this._walletBalanceState = { step: "idle" };
|
|
2056
1984
|
this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
|
|
1985
|
+
this._enabledAssetsState = { step: "idle" };
|
|
1986
|
+
this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
|
|
2057
1987
|
this._authState = { step: "idle" };
|
|
2058
1988
|
this._authStateListeners = /* @__PURE__ */ new Set();
|
|
2059
1989
|
this._networkState = { step: "idle" };
|
|
@@ -2069,6 +1999,8 @@ var PollarClient = class {
|
|
|
2069
1999
|
this._storageDegradeListeners = /* @__PURE__ */ new Set();
|
|
2070
2000
|
this._walletAdapter = null;
|
|
2071
2001
|
this._loginController = null;
|
|
2002
|
+
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2003
|
+
this._resumeController = null;
|
|
2072
2004
|
this.apiKey = config.apiKey;
|
|
2073
2005
|
this.id = randomUUID();
|
|
2074
2006
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
@@ -2081,6 +2013,8 @@ var PollarClient = class {
|
|
|
2081
2013
|
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
2082
2014
|
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
2083
2015
|
this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
|
|
2016
|
+
this._passkey = config.passkey ?? null;
|
|
2017
|
+
this._passkeySign = config.passkeySign ?? null;
|
|
2084
2018
|
this._deviceLabel = config.deviceLabel;
|
|
2085
2019
|
this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
|
|
2086
2020
|
this._maxIdleMs = config.maxIdleMs;
|
|
@@ -2094,7 +2028,9 @@ var PollarClient = class {
|
|
|
2094
2028
|
this._initialized = Promise.resolve();
|
|
2095
2029
|
return;
|
|
2096
2030
|
}
|
|
2097
|
-
console.info(
|
|
2031
|
+
console.info(
|
|
2032
|
+
`[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
|
|
2033
|
+
);
|
|
2098
2034
|
this._initialized = this._initialize();
|
|
2099
2035
|
}
|
|
2100
2036
|
/**
|
|
@@ -2132,7 +2068,11 @@ var PollarClient = class {
|
|
|
2132
2068
|
}
|
|
2133
2069
|
await this._restoreSession();
|
|
2134
2070
|
this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
|
|
2135
|
-
if (visible)
|
|
2071
|
+
if (!visible) return;
|
|
2072
|
+
void this._maybeProactiveRefresh();
|
|
2073
|
+
if (this._authState.step === "authenticated" && !this._authState.verified) {
|
|
2074
|
+
void this._resume();
|
|
2075
|
+
}
|
|
2136
2076
|
});
|
|
2137
2077
|
}
|
|
2138
2078
|
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
@@ -2143,6 +2083,8 @@ var PollarClient = class {
|
|
|
2143
2083
|
}
|
|
2144
2084
|
this._loginController?.abort();
|
|
2145
2085
|
this._loginController = null;
|
|
2086
|
+
this._resumeController?.abort();
|
|
2087
|
+
this._resumeController = null;
|
|
2146
2088
|
this._clearRefreshTimer();
|
|
2147
2089
|
if (this._visibilityUnsubscribe) {
|
|
2148
2090
|
this._visibilityUnsubscribe();
|
|
@@ -2157,7 +2099,9 @@ var PollarClient = class {
|
|
|
2157
2099
|
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
2158
2100
|
self._lastRequestAt = Date.now();
|
|
2159
2101
|
await self._initialized;
|
|
2160
|
-
|
|
2102
|
+
const cacheMethod = request.method.toUpperCase();
|
|
2103
|
+
const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
|
|
2104
|
+
if (cacheBodyAllowed && request.body != null) {
|
|
2161
2105
|
try {
|
|
2162
2106
|
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
2163
2107
|
} catch (err) {
|
|
@@ -2244,7 +2188,9 @@ var PollarClient = class {
|
|
|
2244
2188
|
}
|
|
2245
2189
|
}
|
|
2246
2190
|
}
|
|
2247
|
-
const
|
|
2191
|
+
const retryMethod = originalRequest.method.toUpperCase();
|
|
2192
|
+
const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
|
|
2193
|
+
const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
|
|
2248
2194
|
const retried = new Request(originalRequest.url, {
|
|
2249
2195
|
method: originalRequest.method,
|
|
2250
2196
|
headers,
|
|
@@ -2288,19 +2234,26 @@ var PollarClient = class {
|
|
|
2288
2234
|
throw err;
|
|
2289
2235
|
}
|
|
2290
2236
|
if (error || !data) {
|
|
2291
|
-
console.
|
|
2237
|
+
console.error("[PollarClient] /auth/refresh returned error", { error });
|
|
2292
2238
|
await this._clearSession();
|
|
2293
2239
|
throw new Error("Refresh failed");
|
|
2294
2240
|
}
|
|
2295
2241
|
const successData = data;
|
|
2296
2242
|
if (!successData.success || !successData.content?.token) {
|
|
2297
|
-
console.
|
|
2243
|
+
console.error("[PollarClient] /auth/refresh response malformed", {
|
|
2244
|
+
success: successData.success,
|
|
2245
|
+
hasToken: !!successData.content?.token
|
|
2246
|
+
});
|
|
2298
2247
|
await this._clearSession();
|
|
2299
2248
|
throw new Error("Refresh response malformed");
|
|
2300
2249
|
}
|
|
2301
2250
|
const newToken = successData.content.token;
|
|
2302
2251
|
if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
|
|
2303
|
-
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
|
+
});
|
|
2304
2257
|
await this._clearSession();
|
|
2305
2258
|
throw new Error("Refresh response token shape invalid");
|
|
2306
2259
|
}
|
|
@@ -2494,6 +2447,19 @@ var PollarClient = class {
|
|
|
2494
2447
|
const controller = this._newController();
|
|
2495
2448
|
loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2496
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
|
+
}
|
|
2497
2463
|
// ─── Cancel ───────────────────────────────────────────────────────────────
|
|
2498
2464
|
cancelLogin() {
|
|
2499
2465
|
this._loginController?.abort();
|
|
@@ -2556,6 +2522,29 @@ var PollarClient = class {
|
|
|
2556
2522
|
}
|
|
2557
2523
|
return data.content.sessions;
|
|
2558
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
|
+
}
|
|
2559
2548
|
/**
|
|
2560
2549
|
* Revoke a specific refresh-token family (a single device session). Use
|
|
2561
2550
|
* `listSessions` to enumerate the familyIds. Revoking the current session
|
|
@@ -2632,16 +2621,19 @@ var PollarClient = class {
|
|
|
2632
2621
|
cb(this._walletBalanceState);
|
|
2633
2622
|
return () => this._walletBalanceStateListeners.delete(cb);
|
|
2634
2623
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
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) {
|
|
2638
2631
|
this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
|
|
2639
2632
|
return;
|
|
2640
2633
|
}
|
|
2641
2634
|
this._setWalletBalanceState({ step: "loading" });
|
|
2642
2635
|
try {
|
|
2643
|
-
const
|
|
2644
|
-
const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
|
|
2636
|
+
const { data, error } = await this._api.GET("/wallet/balance");
|
|
2645
2637
|
if (!error && data?.success && data.content) {
|
|
2646
2638
|
this._setWalletBalanceState({ step: "loaded", data: data.content });
|
|
2647
2639
|
} else {
|
|
@@ -2651,6 +2643,53 @@ var PollarClient = class {
|
|
|
2651
2643
|
this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
|
|
2652
2644
|
}
|
|
2653
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
|
+
}
|
|
2654
2693
|
// ─── Transactions ─────────────────────────────────────────────────────────
|
|
2655
2694
|
/**
|
|
2656
2695
|
* Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
|
|
@@ -2658,14 +2697,14 @@ var PollarClient = class {
|
|
|
2658
2697
|
* inspect the result without subscribing to state changes.
|
|
2659
2698
|
*/
|
|
2660
2699
|
async buildTx(operation, params, options) {
|
|
2661
|
-
if (!this._session?.wallet?.
|
|
2700
|
+
if (!this._session?.wallet?.address) {
|
|
2662
2701
|
const details = "No wallet connected";
|
|
2663
2702
|
this._setTransactionState({ step: "error", phase: "building", details });
|
|
2664
2703
|
return { status: "error", details };
|
|
2665
2704
|
}
|
|
2666
2705
|
const body = {
|
|
2667
2706
|
network: this.getNetwork(),
|
|
2668
|
-
|
|
2707
|
+
address: this._session.wallet.address,
|
|
2669
2708
|
operation,
|
|
2670
2709
|
params,
|
|
2671
2710
|
options: options ?? {}
|
|
@@ -2706,7 +2745,7 @@ var PollarClient = class {
|
|
|
2706
2745
|
const buildData = this._currentBuildData();
|
|
2707
2746
|
this._setTransactionState({ step: "signing", ...buildData && { buildData } });
|
|
2708
2747
|
if (this._walletAdapter) {
|
|
2709
|
-
const accountToSign = this._session?.wallet?.
|
|
2748
|
+
const accountToSign = this._session?.wallet?.address;
|
|
2710
2749
|
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2711
2750
|
try {
|
|
2712
2751
|
const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
|
|
@@ -2727,10 +2766,10 @@ var PollarClient = class {
|
|
|
2727
2766
|
return { status: "error", ...details && { details } };
|
|
2728
2767
|
}
|
|
2729
2768
|
}
|
|
2730
|
-
const
|
|
2769
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2731
2770
|
try {
|
|
2732
2771
|
const { data, error } = await this._api.POST("/tx/sign", {
|
|
2733
|
-
body: { network: this.getNetwork(),
|
|
2772
|
+
body: { network: this.getNetwork(), address, unsignedXdr }
|
|
2734
2773
|
});
|
|
2735
2774
|
if (!error && data?.success && data.content?.signedXdr) {
|
|
2736
2775
|
const { signedXdr, idempotencyKey } = data.content;
|
|
@@ -2783,12 +2822,12 @@ var PollarClient = class {
|
|
|
2783
2822
|
const buildData = this._currentBuildData();
|
|
2784
2823
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
2785
2824
|
this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
|
|
2786
|
-
const
|
|
2825
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2787
2826
|
try {
|
|
2788
2827
|
const { data, error } = await this._api.POST("/tx/submit", {
|
|
2789
2828
|
body: {
|
|
2790
2829
|
network: this.getNetwork(),
|
|
2791
|
-
|
|
2830
|
+
address,
|
|
2792
2831
|
signedXdr,
|
|
2793
2832
|
...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
|
|
2794
2833
|
}
|
|
@@ -2848,6 +2887,19 @@ var PollarClient = class {
|
|
|
2848
2887
|
* `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
|
|
2849
2888
|
*/
|
|
2850
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
|
+
}
|
|
2851
2903
|
if (this._walletAdapter) {
|
|
2852
2904
|
const signed = await this.signTx(unsignedXdr);
|
|
2853
2905
|
if (signed.status === "error") {
|
|
@@ -2865,7 +2917,7 @@ var PollarClient = class {
|
|
|
2865
2917
|
this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
|
|
2866
2918
|
const body = {
|
|
2867
2919
|
network: this.getNetwork(),
|
|
2868
|
-
|
|
2920
|
+
address: this._session?.wallet?.address ?? "",
|
|
2869
2921
|
unsignedXdr
|
|
2870
2922
|
};
|
|
2871
2923
|
try {
|
|
@@ -2934,14 +2986,20 @@ var PollarClient = class {
|
|
|
2934
2986
|
* `signTx`, and `submitTx` separately instead.
|
|
2935
2987
|
*/
|
|
2936
2988
|
async buildAndSignAndSubmitTx(operation, params, options) {
|
|
2989
|
+
if (this._session?.wallet?.type === "smart") {
|
|
2990
|
+
return this._runSmartTx(operation, params, options);
|
|
2991
|
+
}
|
|
2937
2992
|
if (this._walletAdapter) {
|
|
2938
2993
|
const built = await this.buildTx(operation, params, options);
|
|
2939
2994
|
if (built.status === "error") {
|
|
2940
2995
|
return { status: "error", ...built.details && { details: built.details } };
|
|
2941
2996
|
}
|
|
2997
|
+
if (!built.buildData.unsignedXdr) {
|
|
2998
|
+
return { status: "error", details: "build returned no unsigned transaction" };
|
|
2999
|
+
}
|
|
2942
3000
|
return this.signAndSubmitTx(built.buildData.unsignedXdr);
|
|
2943
3001
|
}
|
|
2944
|
-
if (!this._session?.wallet?.
|
|
3002
|
+
if (!this._session?.wallet?.address) {
|
|
2945
3003
|
this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
|
|
2946
3004
|
return { status: "error", details: "No wallet connected" };
|
|
2947
3005
|
}
|
|
@@ -2950,7 +3008,7 @@ var PollarClient = class {
|
|
|
2950
3008
|
const { data, error } = await this._api.POST("/tx/build-sign-submit", {
|
|
2951
3009
|
body: {
|
|
2952
3010
|
network: this.getNetwork(),
|
|
2953
|
-
|
|
3011
|
+
address: this._session.wallet.address,
|
|
2954
3012
|
operation,
|
|
2955
3013
|
params,
|
|
2956
3014
|
options: options ?? {}
|
|
@@ -2994,6 +3052,113 @@ var PollarClient = class {
|
|
|
2994
3052
|
async runTx(operation, params, options) {
|
|
2995
3053
|
return this.buildAndSignAndSubmitTx(operation, params, options);
|
|
2996
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
|
+
}
|
|
2997
3162
|
// ─── App config ───────────────────────────────────────────────────────────
|
|
2998
3163
|
async getAppConfig() {
|
|
2999
3164
|
try {
|
|
@@ -3047,10 +3212,18 @@ var PollarClient = class {
|
|
|
3047
3212
|
this._txHistoryState = next;
|
|
3048
3213
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
3049
3214
|
}
|
|
3215
|
+
_setSessionsState(next) {
|
|
3216
|
+
this._sessionsState = next;
|
|
3217
|
+
for (const cb of this._sessionsStateListeners) cb(next);
|
|
3218
|
+
}
|
|
3050
3219
|
_setWalletBalanceState(next) {
|
|
3051
3220
|
this._walletBalanceState = next;
|
|
3052
3221
|
for (const cb of this._walletBalanceStateListeners) cb(next);
|
|
3053
3222
|
}
|
|
3223
|
+
_setEnabledAssetsState(next) {
|
|
3224
|
+
this._enabledAssetsState = next;
|
|
3225
|
+
for (const cb of this._enabledAssetsStateListeners) cb(next);
|
|
3226
|
+
}
|
|
3054
3227
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
3055
3228
|
_newController() {
|
|
3056
3229
|
this._loginController?.abort();
|
|
@@ -3075,6 +3248,7 @@ var PollarClient = class {
|
|
|
3075
3248
|
this._walletAdapter = adapter;
|
|
3076
3249
|
await writeWalletType(this._storage, this.apiKeyHash, id);
|
|
3077
3250
|
},
|
|
3251
|
+
...this._passkey ? { passkey: this._passkey } : {},
|
|
3078
3252
|
...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
|
|
3079
3253
|
};
|
|
3080
3254
|
}
|
|
@@ -3104,7 +3278,7 @@ var PollarClient = class {
|
|
|
3104
3278
|
}
|
|
3105
3279
|
}
|
|
3106
3280
|
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
3107
|
-
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
3281
|
+
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
|
|
3108
3282
|
throw new Error(
|
|
3109
3283
|
`[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
|
|
3110
3284
|
);
|
|
@@ -3145,21 +3319,66 @@ var PollarClient = class {
|
|
|
3145
3319
|
}
|
|
3146
3320
|
}
|
|
3147
3321
|
console.info("[PollarClient] Session restored from storage");
|
|
3148
|
-
this._setAuthState({ step: "authenticated", session: this._session });
|
|
3322
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: false });
|
|
3149
3323
|
this._scheduleNextRefresh();
|
|
3324
|
+
void this._resume();
|
|
3150
3325
|
} else {
|
|
3151
3326
|
console.info("[PollarClient] No session in storage");
|
|
3152
3327
|
}
|
|
3153
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
|
+
}
|
|
3154
3362
|
async _storeSession(session) {
|
|
3155
3363
|
console.info("[PollarClient] Session stored");
|
|
3364
|
+
const w = session.wallet;
|
|
3156
3365
|
const persisted = {
|
|
3157
3366
|
clientSessionId: session.clientSessionId,
|
|
3158
3367
|
userId: session.userId ?? null,
|
|
3159
3368
|
status: session.status,
|
|
3160
3369
|
token: session.token,
|
|
3161
3370
|
user: session.user,
|
|
3162
|
-
|
|
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
|
+
}
|
|
3163
3382
|
};
|
|
3164
3383
|
this._session = persisted;
|
|
3165
3384
|
if (session.data) {
|
|
@@ -3172,7 +3391,7 @@ var PollarClient = class {
|
|
|
3172
3391
|
};
|
|
3173
3392
|
}
|
|
3174
3393
|
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
3175
|
-
this._setAuthState({ step: "authenticated", session: persisted });
|
|
3394
|
+
this._setAuthState({ step: "authenticated", session: persisted, verified: true });
|
|
3176
3395
|
this._scheduleNextRefresh();
|
|
3177
3396
|
}
|
|
3178
3397
|
async _clearSession() {
|
|
@@ -3224,6 +3443,196 @@ var PollarClient = class {
|
|
|
3224
3443
|
}
|
|
3225
3444
|
};
|
|
3226
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
|
+
|
|
3227
3636
|
// src/stellar/StellarClient.ts
|
|
3228
3637
|
var HORIZON_URLS = {
|
|
3229
3638
|
mainnet: "https://horizon.stellar.org",
|
|
@@ -3254,13 +3663,9 @@ var StellarClient = class {
|
|
|
3254
3663
|
|
|
3255
3664
|
// src/index.rn.ts
|
|
3256
3665
|
_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
3666
|
return new NobleKeyManager(storage, apiKey);
|
|
3262
3667
|
});
|
|
3263
3668
|
|
|
3264
|
-
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 };
|
|
3265
3670
|
//# sourceMappingURL=index.rn.mjs.map
|
|
3266
3671
|
//# sourceMappingURL=index.rn.mjs.map
|