@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.mjs
CHANGED
|
@@ -184,17 +184,6 @@ function defaultKeyManager(storage, apiKey) {
|
|
|
184
184
|
}
|
|
185
185
|
return _factory(storage, apiKey);
|
|
186
186
|
}
|
|
187
|
-
async function sha256(data) {
|
|
188
|
-
return sha256$1(data);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// src/lib/api-key-hash.ts
|
|
192
|
-
async function hashApiKey(apiKey) {
|
|
193
|
-
const digest = await sha256(new TextEncoder().encode(apiKey));
|
|
194
|
-
let hex = "";
|
|
195
|
-
for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
|
|
196
|
-
return hex;
|
|
197
|
-
}
|
|
198
187
|
|
|
199
188
|
// src/lib/base64url.ts
|
|
200
189
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
@@ -232,6 +221,17 @@ function base64urlEncode(bytes) {
|
|
|
232
221
|
function base64urlEncodeString(s) {
|
|
233
222
|
return base64urlEncode(new TextEncoder().encode(s));
|
|
234
223
|
}
|
|
224
|
+
async function sha256(data) {
|
|
225
|
+
return sha256$1(data);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/lib/api-key-hash.ts
|
|
229
|
+
async function hashApiKey(apiKey) {
|
|
230
|
+
const digest = await sha256(new TextEncoder().encode(apiKey));
|
|
231
|
+
let hex = "";
|
|
232
|
+
for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
|
|
233
|
+
return hex;
|
|
234
|
+
}
|
|
235
235
|
|
|
236
236
|
// src/keys/thumbprint.ts
|
|
237
237
|
async function computeJwkThumbprint(jwk) {
|
|
@@ -242,11 +242,14 @@ async function computeJwkThumbprint(jwk) {
|
|
|
242
242
|
const digest = await sha256(new TextEncoder().encode(canonical));
|
|
243
243
|
return base64urlEncode(digest);
|
|
244
244
|
}
|
|
245
|
+
function toBase64url(value) {
|
|
246
|
+
return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/[^A-Za-z0-9_-]/g, "");
|
|
247
|
+
}
|
|
245
248
|
function canonicalEcJwk(jwk) {
|
|
246
249
|
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
|
|
247
250
|
throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
|
|
248
251
|
}
|
|
249
|
-
return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
252
|
+
return { kty: "EC", crv: "P-256", x: toBase64url(jwk.x), y: toBase64url(jwk.y) };
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
// src/keys/web-crypto.ts
|
|
@@ -260,7 +263,7 @@ function openDb() {
|
|
|
260
263
|
return;
|
|
261
264
|
}
|
|
262
265
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
263
|
-
req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
|
|
266
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB open failed"));
|
|
264
267
|
req.onsuccess = () => resolve(req.result);
|
|
265
268
|
req.onupgradeneeded = () => {
|
|
266
269
|
const db = req.result;
|
|
@@ -273,7 +276,7 @@ function openDb() {
|
|
|
273
276
|
function awaitTx(req) {
|
|
274
277
|
return new Promise((resolve, reject) => {
|
|
275
278
|
req.onsuccess = () => resolve(req.result);
|
|
276
|
-
req.onerror = () => reject(req.error ?? new Error("IDB request failed"));
|
|
279
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB request failed"));
|
|
277
280
|
});
|
|
278
281
|
}
|
|
279
282
|
async function dbGet(key) {
|
|
@@ -368,10 +371,39 @@ var WebCryptoKeyManager = class {
|
|
|
368
371
|
}
|
|
369
372
|
}
|
|
370
373
|
this.keyPair = pair;
|
|
371
|
-
|
|
372
|
-
this.publicJwk = canonicalEcJwk(exported);
|
|
374
|
+
this.publicJwk = await this._exportPublicJwk(pair.publicKey);
|
|
373
375
|
this.thumbprint = await computeJwkThumbprint(this.publicJwk);
|
|
374
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Derive the public JWK from a `CryptoKey`. Prefers the `'raw'` export (the
|
|
379
|
+
* 65-byte uncompressed point `0x04 || X(32) || Y(32)`) and base64url-encodes
|
|
380
|
+
* the coordinates ourselves — that sidesteps polyfills whose `exportKey('jwk')`
|
|
381
|
+
* emits non-base64url `x`/`y` (standard base64, `=` padding, or — as seen with
|
|
382
|
+
* `react-native-quick-crypto` — a stray `.`). Real browsers and most polyfills
|
|
383
|
+
* support `'raw'` for public EC keys.
|
|
384
|
+
*
|
|
385
|
+
* Falls back to the `'jwk'` export (normalized via `canonicalEcJwk`) if `'raw'`
|
|
386
|
+
* is unsupported or returns an unexpected shape, so this can't regress on a
|
|
387
|
+
* runtime that only implements the JWK path. Both routes yield identical
|
|
388
|
+
* coordinate bytes, so the `cnf.jkt` thumbprint is unchanged either way.
|
|
389
|
+
*/
|
|
390
|
+
async _exportPublicJwk(publicKey) {
|
|
391
|
+
try {
|
|
392
|
+
const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey("raw", publicKey));
|
|
393
|
+
if (raw.length !== 65 || raw[0] !== 4) {
|
|
394
|
+
throw new Error(`[PollarClient:keys] Unexpected raw EC point (len=${raw.length}, tag=${raw[0]})`);
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
kty: "EC",
|
|
398
|
+
crv: "P-256",
|
|
399
|
+
x: base64urlEncode(raw.slice(1, 33)),
|
|
400
|
+
y: base64urlEncode(raw.slice(33, 65))
|
|
401
|
+
};
|
|
402
|
+
} catch {
|
|
403
|
+
const jwk = await globalThis.crypto.subtle.exportKey("jwk", publicKey);
|
|
404
|
+
return canonicalEcJwk(jwk);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
375
407
|
async reset() {
|
|
376
408
|
try {
|
|
377
409
|
if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
|
|
@@ -1129,6 +1161,9 @@ function defaultStorage(options = {}) {
|
|
|
1129
1161
|
return createLocalStorageAdapter(options);
|
|
1130
1162
|
}
|
|
1131
1163
|
|
|
1164
|
+
// src/version.ts
|
|
1165
|
+
var POLLAR_CORE_VERSION = "0.9.0-rc.0" ;
|
|
1166
|
+
|
|
1132
1167
|
// src/visibility/noop.ts
|
|
1133
1168
|
function createNoopVisibilityProvider() {
|
|
1134
1169
|
return {
|
|
@@ -1193,6 +1228,7 @@ var AUTH_ERROR_CODES = {
|
|
|
1193
1228
|
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1194
1229
|
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1195
1230
|
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1231
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1196
1232
|
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1197
1233
|
};
|
|
1198
1234
|
var PollarFlowError = class extends Error {
|
|
@@ -1238,7 +1274,7 @@ var FreighterAdapter = class {
|
|
|
1238
1274
|
if (!userInfo?.publicKey) {
|
|
1239
1275
|
throw new Error("Failed to get user information from Freighter");
|
|
1240
1276
|
}
|
|
1241
|
-
return { address: userInfo.publicKey
|
|
1277
|
+
return { address: userInfo.publicKey };
|
|
1242
1278
|
}
|
|
1243
1279
|
async disconnect() {
|
|
1244
1280
|
}
|
|
@@ -1276,6 +1312,19 @@ var FreighterAdapter = class {
|
|
|
1276
1312
|
};
|
|
1277
1313
|
|
|
1278
1314
|
// src/wallets/AlbedoAdapter.ts
|
|
1315
|
+
var PUBLIC_PASSPHRASE = "Public Global Stellar Network ; September 2015";
|
|
1316
|
+
var TESTNET_PASSPHRASE = "Test SDF Network ; September 2015";
|
|
1317
|
+
function albedoNetwork(options, fallback) {
|
|
1318
|
+
switch (options?.networkPassphrase) {
|
|
1319
|
+
case PUBLIC_PASSPHRASE:
|
|
1320
|
+
return "public";
|
|
1321
|
+
case TESTNET_PASSPHRASE:
|
|
1322
|
+
return "testnet";
|
|
1323
|
+
}
|
|
1324
|
+
if (options?.network === "public" || options?.network === "mainnet") return "public";
|
|
1325
|
+
if (options?.network === "testnet") return "testnet";
|
|
1326
|
+
return fallback;
|
|
1327
|
+
}
|
|
1279
1328
|
function openAlbedoPopup(url) {
|
|
1280
1329
|
const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
|
|
1281
1330
|
if (!popup) {
|
|
@@ -1314,7 +1363,13 @@ function waitForAlbedoResult() {
|
|
|
1314
1363
|
});
|
|
1315
1364
|
}
|
|
1316
1365
|
var AlbedoAdapter = class {
|
|
1317
|
-
|
|
1366
|
+
/**
|
|
1367
|
+
* Network used for `connect` and `signAuthEntry` (which carry no per-call
|
|
1368
|
+
* network) and as the fallback for `signTransaction`. Defaults to `'testnet'`
|
|
1369
|
+
* to preserve the previous behavior when constructed with no argument.
|
|
1370
|
+
*/
|
|
1371
|
+
constructor(network = "testnet") {
|
|
1372
|
+
this.network = network;
|
|
1318
1373
|
this.type = "albedo" /* ALBEDO */;
|
|
1319
1374
|
}
|
|
1320
1375
|
async isAvailable() {
|
|
@@ -1324,7 +1379,7 @@ var AlbedoAdapter = class {
|
|
|
1324
1379
|
const url = new URL("https://albedo.link");
|
|
1325
1380
|
url.searchParams.set("intent", "public-key");
|
|
1326
1381
|
url.searchParams.set("app_name", "Pollar");
|
|
1327
|
-
url.searchParams.set("network",
|
|
1382
|
+
url.searchParams.set("network", this.network);
|
|
1328
1383
|
url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
|
|
1329
1384
|
url.searchParams.set("origin", window.location.origin);
|
|
1330
1385
|
openAlbedoPopup(url.toString());
|
|
@@ -1332,7 +1387,7 @@ var AlbedoAdapter = class {
|
|
|
1332
1387
|
if (!result.pubkey) {
|
|
1333
1388
|
throw new Error("Albedo connection rejected");
|
|
1334
1389
|
}
|
|
1335
|
-
return { address: result.pubkey
|
|
1390
|
+
return { address: result.pubkey };
|
|
1336
1391
|
}
|
|
1337
1392
|
async disconnect() {
|
|
1338
1393
|
}
|
|
@@ -1342,12 +1397,12 @@ var AlbedoAdapter = class {
|
|
|
1342
1397
|
async getNetwork() {
|
|
1343
1398
|
throw new Error("Albedo does not expose network");
|
|
1344
1399
|
}
|
|
1345
|
-
async signTransaction(xdr,
|
|
1400
|
+
async signTransaction(xdr, options) {
|
|
1346
1401
|
const url = new URL("https://albedo.link");
|
|
1347
1402
|
url.searchParams.set("intent", "tx");
|
|
1348
1403
|
url.searchParams.set("xdr", xdr);
|
|
1349
1404
|
url.searchParams.set("app_name", "Pollar");
|
|
1350
|
-
url.searchParams.set("network",
|
|
1405
|
+
url.searchParams.set("network", albedoNetwork(options, this.network));
|
|
1351
1406
|
url.searchParams.set("callback", window.location.href);
|
|
1352
1407
|
url.searchParams.set("origin", window.location.origin);
|
|
1353
1408
|
window.location.href = url.toString();
|
|
@@ -1360,7 +1415,7 @@ var AlbedoAdapter = class {
|
|
|
1360
1415
|
url.searchParams.set("intent", "sign-auth-entry");
|
|
1361
1416
|
url.searchParams.set("xdr", entryXdr);
|
|
1362
1417
|
url.searchParams.set("app_name", "Pollar");
|
|
1363
|
-
url.searchParams.set("network",
|
|
1418
|
+
url.searchParams.set("network", this.network);
|
|
1364
1419
|
url.searchParams.set("callback", window.location.href);
|
|
1365
1420
|
url.searchParams.set("origin", window.location.origin);
|
|
1366
1421
|
window.location.href = url.toString();
|
|
@@ -1447,8 +1502,12 @@ function isValidSession(value) {
|
|
|
1447
1502
|
return false;
|
|
1448
1503
|
}
|
|
1449
1504
|
const w = wallet;
|
|
1450
|
-
if (w["
|
|
1451
|
-
console.warn("[PollarClient:session] Invalid session \u2014 wallet.
|
|
1505
|
+
if (w["type"] !== "custodial" && w["type"] !== "smart" && w["type"] !== "external") {
|
|
1506
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.type must be custodial|smart|external");
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
if (w["address"] !== null && !isBoundedString(w["address"], MAX_WALLET_PUBLIC_KEY)) {
|
|
1510
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.address must be string|null");
|
|
1452
1511
|
return false;
|
|
1453
1512
|
}
|
|
1454
1513
|
if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
|
|
@@ -1459,6 +1518,10 @@ function isValidSession(value) {
|
|
|
1459
1518
|
console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
|
|
1460
1519
|
return false;
|
|
1461
1520
|
}
|
|
1521
|
+
if (w["linkedAt"] !== void 0 && (typeof w["linkedAt"] !== "number" || !Number.isFinite(w["linkedAt"]))) {
|
|
1522
|
+
console.warn("[PollarClient:session] Invalid session \u2014 wallet.linkedAt must be a finite number if present");
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1462
1525
|
return true;
|
|
1463
1526
|
}
|
|
1464
1527
|
async function readStorage(storage, apiKeyHash) {
|
|
@@ -1466,6 +1529,12 @@ async function readStorage(storage, apiKeyHash) {
|
|
|
1466
1529
|
if (!raw) return null;
|
|
1467
1530
|
try {
|
|
1468
1531
|
const session = JSON.parse(raw);
|
|
1532
|
+
if (typeof session === "object" && session !== null) {
|
|
1533
|
+
const w = session.wallet;
|
|
1534
|
+
if (w && w["address"] == null && typeof w["publicKey"] === "string") {
|
|
1535
|
+
w["address"] = w["publicKey"];
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1469
1538
|
if (!isValidSession(session)) {
|
|
1470
1539
|
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1471
1540
|
console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
|
|
@@ -1556,7 +1625,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1556
1625
|
}));
|
|
1557
1626
|
} catch (e) {
|
|
1558
1627
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1559
|
-
console.warn(e);
|
|
1628
|
+
console.warn("[PollarClient:stream] session-status request failed; will retry", e);
|
|
1560
1629
|
}
|
|
1561
1630
|
if (error || !data) {
|
|
1562
1631
|
await sleep(backoff);
|
|
@@ -1596,7 +1665,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1596
1665
|
} catch (e) {
|
|
1597
1666
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1598
1667
|
if (e instanceof SessionStatusError) throw e;
|
|
1599
|
-
console.warn(e);
|
|
1668
|
+
console.warn("[PollarClient:stream] session-status stream read failed; will retry", e);
|
|
1600
1669
|
} finally {
|
|
1601
1670
|
reader.releaseLock();
|
|
1602
1671
|
}
|
|
@@ -1624,7 +1693,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
|
|
|
1624
1693
|
envelope = await response.json().catch(() => null);
|
|
1625
1694
|
} catch (e) {
|
|
1626
1695
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1627
|
-
console.warn(e);
|
|
1696
|
+
console.warn("[PollarClient:stream] session-status poll failed; will retry", e);
|
|
1628
1697
|
}
|
|
1629
1698
|
if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
|
|
1630
1699
|
throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
|
|
@@ -1828,6 +1897,55 @@ async function loginOAuth(provider, deps) {
|
|
|
1828
1897
|
await authenticate(clientSessionId, deps);
|
|
1829
1898
|
}
|
|
1830
1899
|
|
|
1900
|
+
// src/client/auth/passkeyFlow.ts
|
|
1901
|
+
async function loginSmartWallet(deps) {
|
|
1902
|
+
const { api, signal, setAuthState, passkey } = deps;
|
|
1903
|
+
if (!passkey) {
|
|
1904
|
+
setAuthState({
|
|
1905
|
+
step: "error",
|
|
1906
|
+
previousStep: "creating_session",
|
|
1907
|
+
message: "Passkey support is not configured",
|
|
1908
|
+
errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED
|
|
1909
|
+
});
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
const clientSessionId = await createAuthSession(deps);
|
|
1913
|
+
if (!clientSessionId) return;
|
|
1914
|
+
try {
|
|
1915
|
+
const { data: challengeData } = await api.POST("/auth/passkey/challenge", {
|
|
1916
|
+
body: { clientSessionId },
|
|
1917
|
+
signal
|
|
1918
|
+
});
|
|
1919
|
+
const challenge = challengeData?.content?.challenge;
|
|
1920
|
+
if (!challengeData?.success || !challenge) {
|
|
1921
|
+
return failPasskey(setAuthState, "Failed to start passkey");
|
|
1922
|
+
}
|
|
1923
|
+
setAuthState({ step: "creating_passkey" });
|
|
1924
|
+
const ceremony = await passkey({ challenge });
|
|
1925
|
+
const response = ceremony.response;
|
|
1926
|
+
if (ceremony.kind === "register") {
|
|
1927
|
+
setAuthState({ step: "deploying_smart_account" });
|
|
1928
|
+
const { data } = await api.POST("/auth/passkey/register", {
|
|
1929
|
+
body: { clientSessionId, response },
|
|
1930
|
+
signal
|
|
1931
|
+
});
|
|
1932
|
+
if (!data?.success) return failPasskey(setAuthState, "Passkey registration failed");
|
|
1933
|
+
} else {
|
|
1934
|
+
const { data } = await api.POST("/auth/passkey/login", {
|
|
1935
|
+
body: { clientSessionId, response },
|
|
1936
|
+
signal
|
|
1937
|
+
});
|
|
1938
|
+
if (!data?.success) return failPasskey(setAuthState, "Passkey authentication failed");
|
|
1939
|
+
}
|
|
1940
|
+
} catch {
|
|
1941
|
+
return failPasskey(setAuthState, "Passkey login failed");
|
|
1942
|
+
}
|
|
1943
|
+
await authenticate(clientSessionId, deps);
|
|
1944
|
+
}
|
|
1945
|
+
function failPasskey(setAuthState, message) {
|
|
1946
|
+
setAuthState({ step: "error", previousStep: "creating_passkey", message, errorCode: AUTH_ERROR_CODES.PASSKEY_FAILED });
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1831
1949
|
// src/client/auth/walletFlow.ts
|
|
1832
1950
|
function withSignal(promise, signal) {
|
|
1833
1951
|
return Promise.race([
|
|
@@ -1854,12 +1972,12 @@ async function loginWallet(type, deps) {
|
|
|
1854
1972
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
1855
1973
|
return;
|
|
1856
1974
|
}
|
|
1857
|
-
const {
|
|
1858
|
-
connectedWallet =
|
|
1975
|
+
const { address } = await withSignal(adapter.connect(), signal);
|
|
1976
|
+
connectedWallet = address;
|
|
1859
1977
|
deps.storeWalletAdapter(adapter, type);
|
|
1860
1978
|
setAuthState({ step: "authenticating_wallet" });
|
|
1861
1979
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
|
|
1862
|
-
body: { clientSessionId, walletAddress:
|
|
1980
|
+
body: { clientSessionId, walletAddress: address },
|
|
1863
1981
|
signal
|
|
1864
1982
|
});
|
|
1865
1983
|
if (walletError || !walletData?.success) {
|
|
@@ -1924,8 +2042,12 @@ var PollarClient = class {
|
|
|
1924
2042
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
1925
2043
|
this._txHistoryState = { step: "idle" };
|
|
1926
2044
|
this._txHistoryStateListeners = /* @__PURE__ */ new Set();
|
|
2045
|
+
this._sessionsState = { step: "idle" };
|
|
2046
|
+
this._sessionsStateListeners = /* @__PURE__ */ new Set();
|
|
1927
2047
|
this._walletBalanceState = { step: "idle" };
|
|
1928
2048
|
this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
|
|
2049
|
+
this._enabledAssetsState = { step: "idle" };
|
|
2050
|
+
this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
|
|
1929
2051
|
this._authState = { step: "idle" };
|
|
1930
2052
|
this._authStateListeners = /* @__PURE__ */ new Set();
|
|
1931
2053
|
this._networkState = { step: "idle" };
|
|
@@ -1941,6 +2063,8 @@ var PollarClient = class {
|
|
|
1941
2063
|
this._storageDegradeListeners = /* @__PURE__ */ new Set();
|
|
1942
2064
|
this._walletAdapter = null;
|
|
1943
2065
|
this._loginController = null;
|
|
2066
|
+
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2067
|
+
this._resumeController = null;
|
|
1944
2068
|
this.apiKey = config.apiKey;
|
|
1945
2069
|
this.id = randomUUID();
|
|
1946
2070
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
@@ -1953,6 +2077,8 @@ var PollarClient = class {
|
|
|
1953
2077
|
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
1954
2078
|
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
1955
2079
|
this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
|
|
2080
|
+
this._passkey = config.passkey ?? null;
|
|
2081
|
+
this._passkeySign = config.passkeySign ?? null;
|
|
1956
2082
|
this._deviceLabel = config.deviceLabel;
|
|
1957
2083
|
this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
|
|
1958
2084
|
this._maxIdleMs = config.maxIdleMs;
|
|
@@ -1966,7 +2092,9 @@ var PollarClient = class {
|
|
|
1966
2092
|
this._initialized = Promise.resolve();
|
|
1967
2093
|
return;
|
|
1968
2094
|
}
|
|
1969
|
-
console.info(
|
|
2095
|
+
console.info(
|
|
2096
|
+
`[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
|
|
2097
|
+
);
|
|
1970
2098
|
this._initialized = this._initialize();
|
|
1971
2099
|
}
|
|
1972
2100
|
/**
|
|
@@ -2004,7 +2132,11 @@ var PollarClient = class {
|
|
|
2004
2132
|
}
|
|
2005
2133
|
await this._restoreSession();
|
|
2006
2134
|
this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
|
|
2007
|
-
if (visible)
|
|
2135
|
+
if (!visible) return;
|
|
2136
|
+
void this._maybeProactiveRefresh();
|
|
2137
|
+
if (this._authState.step === "authenticated" && !this._authState.verified) {
|
|
2138
|
+
void this._resume();
|
|
2139
|
+
}
|
|
2008
2140
|
});
|
|
2009
2141
|
}
|
|
2010
2142
|
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
@@ -2015,6 +2147,8 @@ var PollarClient = class {
|
|
|
2015
2147
|
}
|
|
2016
2148
|
this._loginController?.abort();
|
|
2017
2149
|
this._loginController = null;
|
|
2150
|
+
this._resumeController?.abort();
|
|
2151
|
+
this._resumeController = null;
|
|
2018
2152
|
this._clearRefreshTimer();
|
|
2019
2153
|
if (this._visibilityUnsubscribe) {
|
|
2020
2154
|
this._visibilityUnsubscribe();
|
|
@@ -2029,7 +2163,9 @@ var PollarClient = class {
|
|
|
2029
2163
|
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
2030
2164
|
self._lastRequestAt = Date.now();
|
|
2031
2165
|
await self._initialized;
|
|
2032
|
-
|
|
2166
|
+
const cacheMethod = request.method.toUpperCase();
|
|
2167
|
+
const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
|
|
2168
|
+
if (cacheBodyAllowed && request.body != null) {
|
|
2033
2169
|
try {
|
|
2034
2170
|
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
2035
2171
|
} catch (err) {
|
|
@@ -2116,7 +2252,9 @@ var PollarClient = class {
|
|
|
2116
2252
|
}
|
|
2117
2253
|
}
|
|
2118
2254
|
}
|
|
2119
|
-
const
|
|
2255
|
+
const retryMethod = originalRequest.method.toUpperCase();
|
|
2256
|
+
const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
|
|
2257
|
+
const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
|
|
2120
2258
|
const retried = new Request(originalRequest.url, {
|
|
2121
2259
|
method: originalRequest.method,
|
|
2122
2260
|
headers,
|
|
@@ -2160,19 +2298,26 @@ var PollarClient = class {
|
|
|
2160
2298
|
throw err;
|
|
2161
2299
|
}
|
|
2162
2300
|
if (error || !data) {
|
|
2163
|
-
console.
|
|
2301
|
+
console.error("[PollarClient] /auth/refresh returned error", { error });
|
|
2164
2302
|
await this._clearSession();
|
|
2165
2303
|
throw new Error("Refresh failed");
|
|
2166
2304
|
}
|
|
2167
2305
|
const successData = data;
|
|
2168
2306
|
if (!successData.success || !successData.content?.token) {
|
|
2169
|
-
console.
|
|
2307
|
+
console.error("[PollarClient] /auth/refresh response malformed", {
|
|
2308
|
+
success: successData.success,
|
|
2309
|
+
hasToken: !!successData.content?.token
|
|
2310
|
+
});
|
|
2170
2311
|
await this._clearSession();
|
|
2171
2312
|
throw new Error("Refresh response malformed");
|
|
2172
2313
|
}
|
|
2173
2314
|
const newToken = successData.content.token;
|
|
2174
2315
|
if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
|
|
2175
|
-
console.
|
|
2316
|
+
console.error("[PollarClient] /auth/refresh token shape invalid", {
|
|
2317
|
+
accessToken: typeof newToken.accessToken,
|
|
2318
|
+
refreshToken: typeof newToken.refreshToken,
|
|
2319
|
+
expiresAt: typeof newToken.expiresAt
|
|
2320
|
+
});
|
|
2176
2321
|
await this._clearSession();
|
|
2177
2322
|
throw new Error("Refresh response token shape invalid");
|
|
2178
2323
|
}
|
|
@@ -2366,6 +2511,19 @@ var PollarClient = class {
|
|
|
2366
2511
|
const controller = this._newController();
|
|
2367
2512
|
loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2368
2513
|
}
|
|
2514
|
+
/**
|
|
2515
|
+
* "Smart Wallet" login: runs the passkey (WebAuthn) ceremony and, for a new
|
|
2516
|
+
* user, creates a sponsored smart-account C-address. Requires the `passkey`
|
|
2517
|
+
* ceremony to be configured (e.g. via `@pollar/react`).
|
|
2518
|
+
*/
|
|
2519
|
+
loginSmartWallet() {
|
|
2520
|
+
if (!isClientRuntime) {
|
|
2521
|
+
warnServerSide("loginSmartWallet");
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
const controller = this._newController();
|
|
2525
|
+
loginSmartWallet(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2526
|
+
}
|
|
2369
2527
|
// ─── Cancel ───────────────────────────────────────────────────────────────
|
|
2370
2528
|
cancelLogin() {
|
|
2371
2529
|
this._loginController?.abort();
|
|
@@ -2428,6 +2586,29 @@ var PollarClient = class {
|
|
|
2428
2586
|
}
|
|
2429
2587
|
return data.content.sessions;
|
|
2430
2588
|
}
|
|
2589
|
+
getSessionsState() {
|
|
2590
|
+
return this._sessionsState;
|
|
2591
|
+
}
|
|
2592
|
+
onSessionsStateChange(cb) {
|
|
2593
|
+
this._sessionsStateListeners.add(cb);
|
|
2594
|
+
cb(this._sessionsState);
|
|
2595
|
+
return () => this._sessionsStateListeners.delete(cb);
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* Fire-and-forget variant of {@link listSessions} that drives the observable
|
|
2599
|
+
* `SessionsState` store instead of returning the array. UI layers subscribe
|
|
2600
|
+
* via `onSessionsStateChange` and stay pure readers — mirrors `fetchTxHistory`.
|
|
2601
|
+
*/
|
|
2602
|
+
async fetchSessions() {
|
|
2603
|
+
this._setSessionsState({ step: "loading" });
|
|
2604
|
+
try {
|
|
2605
|
+
const sessions = await this.listSessions();
|
|
2606
|
+
this._setSessionsState({ step: "loaded", sessions });
|
|
2607
|
+
} catch (err) {
|
|
2608
|
+
const message = err instanceof Error ? err.message : "Failed to load sessions";
|
|
2609
|
+
this._setSessionsState({ step: "error", message });
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2431
2612
|
/**
|
|
2432
2613
|
* Revoke a specific refresh-token family (a single device session). Use
|
|
2433
2614
|
* `listSessions` to enumerate the familyIds. Revoking the current session
|
|
@@ -2504,16 +2685,19 @@ var PollarClient = class {
|
|
|
2504
2685
|
cb(this._walletBalanceState);
|
|
2505
2686
|
return () => this._walletBalanceStateListeners.delete(cb);
|
|
2506
2687
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2688
|
+
/**
|
|
2689
|
+
* Refreshes the balances of the authenticated user's OWN wallet. The wallet
|
|
2690
|
+
* and network are resolved server-side from the session — no arguments. Drives
|
|
2691
|
+
* `walletBalanceState`. For an arbitrary wallet, use {@link getWalletBalance}.
|
|
2692
|
+
*/
|
|
2693
|
+
async refreshBalance() {
|
|
2694
|
+
if (!this._session?.wallet?.address) {
|
|
2510
2695
|
this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
|
|
2511
2696
|
return;
|
|
2512
2697
|
}
|
|
2513
2698
|
this._setWalletBalanceState({ step: "loading" });
|
|
2514
2699
|
try {
|
|
2515
|
-
const
|
|
2516
|
-
const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
|
|
2700
|
+
const { data, error } = await this._api.GET("/wallet/balance");
|
|
2517
2701
|
if (!error && data?.success && data.content) {
|
|
2518
2702
|
this._setWalletBalanceState({ step: "loaded", data: data.content });
|
|
2519
2703
|
} else {
|
|
@@ -2523,6 +2707,53 @@ var PollarClient = class {
|
|
|
2523
2707
|
this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
|
|
2524
2708
|
}
|
|
2525
2709
|
}
|
|
2710
|
+
/**
|
|
2711
|
+
* General-purpose balance lookup for ANY wallet on ANY network — not scoped
|
|
2712
|
+
* to this application. Enumerates the account's real on-chain holdings via
|
|
2713
|
+
* Horizon (server-side) and returns the data directly (no reactive state).
|
|
2714
|
+
* `network` defaults to the client's current network.
|
|
2715
|
+
*/
|
|
2716
|
+
async getWalletBalance(publicKey, network) {
|
|
2717
|
+
const { data, error } = await this._api.GET("/wallet/{publicKey}/balance", {
|
|
2718
|
+
params: { path: { publicKey }, query: { network: network ?? this.getNetwork() } }
|
|
2719
|
+
});
|
|
2720
|
+
if (error || !data?.success || !data.content) {
|
|
2721
|
+
throw new Error("[PollarClient] Failed to load wallet balance");
|
|
2722
|
+
}
|
|
2723
|
+
return data.content;
|
|
2724
|
+
}
|
|
2725
|
+
// ─── Enabled assets ───────────────────────────────────────────────────────
|
|
2726
|
+
getEnabledAssetsState() {
|
|
2727
|
+
return this._enabledAssetsState;
|
|
2728
|
+
}
|
|
2729
|
+
onEnabledAssetsStateChange(cb) {
|
|
2730
|
+
this._enabledAssetsStateListeners.add(cb);
|
|
2731
|
+
cb(this._enabledAssetsState);
|
|
2732
|
+
return () => this._enabledAssetsStateListeners.delete(cb);
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* Loads the application's enabled assets paired with the authenticated
|
|
2736
|
+
* wallet's on-chain trustline state — so the SDK knows which trustlines still
|
|
2737
|
+
* need to be added. Wallet and network are resolved server-side from the
|
|
2738
|
+
* session. Drives `enabledAssetsState`; mirrors {@link refreshBalance}.
|
|
2739
|
+
*/
|
|
2740
|
+
async refreshAssets() {
|
|
2741
|
+
if (!this._session?.wallet?.address) {
|
|
2742
|
+
this._setEnabledAssetsState({ step: "error", message: "No wallet connected" });
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
this._setEnabledAssetsState({ step: "loading" });
|
|
2746
|
+
try {
|
|
2747
|
+
const { data, error } = await this._api.GET("/wallet/assets");
|
|
2748
|
+
if (!error && data?.success && data.content) {
|
|
2749
|
+
this._setEnabledAssetsState({ step: "loaded", data: data.content });
|
|
2750
|
+
} else {
|
|
2751
|
+
this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
|
|
2752
|
+
}
|
|
2753
|
+
} catch {
|
|
2754
|
+
this._setEnabledAssetsState({ step: "error", message: "Failed to load assets" });
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2526
2757
|
// ─── Transactions ─────────────────────────────────────────────────────────
|
|
2527
2758
|
/**
|
|
2528
2759
|
* Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
|
|
@@ -2530,14 +2761,14 @@ var PollarClient = class {
|
|
|
2530
2761
|
* inspect the result without subscribing to state changes.
|
|
2531
2762
|
*/
|
|
2532
2763
|
async buildTx(operation, params, options) {
|
|
2533
|
-
if (!this._session?.wallet?.
|
|
2764
|
+
if (!this._session?.wallet?.address) {
|
|
2534
2765
|
const details = "No wallet connected";
|
|
2535
2766
|
this._setTransactionState({ step: "error", phase: "building", details });
|
|
2536
2767
|
return { status: "error", details };
|
|
2537
2768
|
}
|
|
2538
2769
|
const body = {
|
|
2539
2770
|
network: this.getNetwork(),
|
|
2540
|
-
|
|
2771
|
+
address: this._session.wallet.address,
|
|
2541
2772
|
operation,
|
|
2542
2773
|
params,
|
|
2543
2774
|
options: options ?? {}
|
|
@@ -2578,7 +2809,7 @@ var PollarClient = class {
|
|
|
2578
2809
|
const buildData = this._currentBuildData();
|
|
2579
2810
|
this._setTransactionState({ step: "signing", ...buildData && { buildData } });
|
|
2580
2811
|
if (this._walletAdapter) {
|
|
2581
|
-
const accountToSign = this._session?.wallet?.
|
|
2812
|
+
const accountToSign = this._session?.wallet?.address;
|
|
2582
2813
|
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2583
2814
|
try {
|
|
2584
2815
|
const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
|
|
@@ -2599,10 +2830,10 @@ var PollarClient = class {
|
|
|
2599
2830
|
return { status: "error", ...details && { details } };
|
|
2600
2831
|
}
|
|
2601
2832
|
}
|
|
2602
|
-
const
|
|
2833
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2603
2834
|
try {
|
|
2604
2835
|
const { data, error } = await this._api.POST("/tx/sign", {
|
|
2605
|
-
body: { network: this.getNetwork(),
|
|
2836
|
+
body: { network: this.getNetwork(), address, unsignedXdr }
|
|
2606
2837
|
});
|
|
2607
2838
|
if (!error && data?.success && data.content?.signedXdr) {
|
|
2608
2839
|
const { signedXdr, idempotencyKey } = data.content;
|
|
@@ -2655,12 +2886,12 @@ var PollarClient = class {
|
|
|
2655
2886
|
const buildData = this._currentBuildData();
|
|
2656
2887
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
2657
2888
|
this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
|
|
2658
|
-
const
|
|
2889
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2659
2890
|
try {
|
|
2660
2891
|
const { data, error } = await this._api.POST("/tx/submit", {
|
|
2661
2892
|
body: {
|
|
2662
2893
|
network: this.getNetwork(),
|
|
2663
|
-
|
|
2894
|
+
address,
|
|
2664
2895
|
signedXdr,
|
|
2665
2896
|
...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
|
|
2666
2897
|
}
|
|
@@ -2720,6 +2951,19 @@ var PollarClient = class {
|
|
|
2720
2951
|
* `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
|
|
2721
2952
|
*/
|
|
2722
2953
|
async signAndSubmitTx(unsignedXdr) {
|
|
2954
|
+
if (this._session?.wallet?.type === "smart") {
|
|
2955
|
+
const buildData2 = this._currentBuildData();
|
|
2956
|
+
if (!buildData2?.smart) {
|
|
2957
|
+
const details = "no prepared smart transaction; call buildTx first";
|
|
2958
|
+
this._setTransactionState({ step: "error", phase: "signing", details });
|
|
2959
|
+
return { status: "error", details };
|
|
2960
|
+
}
|
|
2961
|
+
return this._signSubmitSmart(buildData2);
|
|
2962
|
+
}
|
|
2963
|
+
if (!unsignedXdr) {
|
|
2964
|
+
this._setTransactionState({ step: "error", phase: "signing", details: "missing unsigned transaction" });
|
|
2965
|
+
return { status: "error", details: "missing unsigned transaction" };
|
|
2966
|
+
}
|
|
2723
2967
|
if (this._walletAdapter) {
|
|
2724
2968
|
const signed = await this.signTx(unsignedXdr);
|
|
2725
2969
|
if (signed.status === "error") {
|
|
@@ -2737,7 +2981,7 @@ var PollarClient = class {
|
|
|
2737
2981
|
this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
|
|
2738
2982
|
const body = {
|
|
2739
2983
|
network: this.getNetwork(),
|
|
2740
|
-
|
|
2984
|
+
address: this._session?.wallet?.address ?? "",
|
|
2741
2985
|
unsignedXdr
|
|
2742
2986
|
};
|
|
2743
2987
|
try {
|
|
@@ -2806,14 +3050,20 @@ var PollarClient = class {
|
|
|
2806
3050
|
* `signTx`, and `submitTx` separately instead.
|
|
2807
3051
|
*/
|
|
2808
3052
|
async buildAndSignAndSubmitTx(operation, params, options) {
|
|
3053
|
+
if (this._session?.wallet?.type === "smart") {
|
|
3054
|
+
return this._runSmartTx(operation, params, options);
|
|
3055
|
+
}
|
|
2809
3056
|
if (this._walletAdapter) {
|
|
2810
3057
|
const built = await this.buildTx(operation, params, options);
|
|
2811
3058
|
if (built.status === "error") {
|
|
2812
3059
|
return { status: "error", ...built.details && { details: built.details } };
|
|
2813
3060
|
}
|
|
3061
|
+
if (!built.buildData.unsignedXdr) {
|
|
3062
|
+
return { status: "error", details: "build returned no unsigned transaction" };
|
|
3063
|
+
}
|
|
2814
3064
|
return this.signAndSubmitTx(built.buildData.unsignedXdr);
|
|
2815
3065
|
}
|
|
2816
|
-
if (!this._session?.wallet?.
|
|
3066
|
+
if (!this._session?.wallet?.address) {
|
|
2817
3067
|
this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
|
|
2818
3068
|
return { status: "error", details: "No wallet connected" };
|
|
2819
3069
|
}
|
|
@@ -2822,7 +3072,7 @@ var PollarClient = class {
|
|
|
2822
3072
|
const { data, error } = await this._api.POST("/tx/build-sign-submit", {
|
|
2823
3073
|
body: {
|
|
2824
3074
|
network: this.getNetwork(),
|
|
2825
|
-
|
|
3075
|
+
address: this._session.wallet.address,
|
|
2826
3076
|
operation,
|
|
2827
3077
|
params,
|
|
2828
3078
|
options: options ?? {}
|
|
@@ -2866,6 +3116,113 @@ var PollarClient = class {
|
|
|
2866
3116
|
async runTx(operation, params, options) {
|
|
2867
3117
|
return this.buildAndSignAndSubmitTx(operation, params, options);
|
|
2868
3118
|
}
|
|
3119
|
+
/**
|
|
3120
|
+
* Smart-wallet (passkey / C-address) transaction: build (server prepares the
|
|
3121
|
+
* SAC transfer + returns the auth digest) → sign the digest with the passkey
|
|
3122
|
+
* → submit (server assembles the signed auth entry and broadcasts; the
|
|
3123
|
+
* sponsor pays the fee). State machine: building → built → signing →
|
|
3124
|
+
* submitting → success.
|
|
3125
|
+
*/
|
|
3126
|
+
async _runSmartTx(operation, params, options) {
|
|
3127
|
+
const address = this._session?.wallet?.address;
|
|
3128
|
+
if (!address) {
|
|
3129
|
+
this._setTransactionState({ step: "error", phase: "building", details: "No wallet connected" });
|
|
3130
|
+
return { status: "error", details: "No wallet connected" };
|
|
3131
|
+
}
|
|
3132
|
+
if (!this._passkeySign) {
|
|
3133
|
+
const details = "Passkey signer not configured";
|
|
3134
|
+
this._setTransactionState({ step: "error", phase: "signing", details });
|
|
3135
|
+
return { status: "error", details };
|
|
3136
|
+
}
|
|
3137
|
+
this._setTransactionState({ step: "building" });
|
|
3138
|
+
let buildData;
|
|
3139
|
+
try {
|
|
3140
|
+
const body = {
|
|
3141
|
+
network: this.getNetwork(),
|
|
3142
|
+
address,
|
|
3143
|
+
operation,
|
|
3144
|
+
params,
|
|
3145
|
+
options: options ?? {}
|
|
3146
|
+
};
|
|
3147
|
+
const { data, error } = await this._api.POST("/tx/build", { body });
|
|
3148
|
+
if (error || !data?.success || !data.content?.smart) {
|
|
3149
|
+
const details = error?.details ?? "Failed to build transaction";
|
|
3150
|
+
this._setTransactionState({ step: "error", phase: "building", details });
|
|
3151
|
+
return { status: "error", details };
|
|
3152
|
+
}
|
|
3153
|
+
buildData = data.content;
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3156
|
+
this._setTransactionState({ step: "error", phase: "building", ...details && { details } });
|
|
3157
|
+
return { status: "error", ...details && { details } };
|
|
3158
|
+
}
|
|
3159
|
+
this._setTransactionState({ step: "built", buildData });
|
|
3160
|
+
return this._signSubmitSmart(buildData);
|
|
3161
|
+
}
|
|
3162
|
+
/**
|
|
3163
|
+
* Steps 2–3 of the smart-wallet flow: sign the prepared auth digest with the
|
|
3164
|
+
* passkey, then submit. Shared by `_runSmartTx` (atomic) and `signAndSubmitTx`
|
|
3165
|
+
* (split flow, when a smart build is already on the state machine).
|
|
3166
|
+
*/
|
|
3167
|
+
async _signSubmitSmart(buildData) {
|
|
3168
|
+
const address = this._session?.wallet?.address;
|
|
3169
|
+
const smart = buildData.smart;
|
|
3170
|
+
if (!address || !smart) {
|
|
3171
|
+
const details = "no prepared smart transaction";
|
|
3172
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, details });
|
|
3173
|
+
return { status: "error", buildData, details };
|
|
3174
|
+
}
|
|
3175
|
+
if (!this._passkeySign) {
|
|
3176
|
+
const details = "Passkey signer not configured";
|
|
3177
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, details });
|
|
3178
|
+
return { status: "error", buildData, details };
|
|
3179
|
+
}
|
|
3180
|
+
this._setTransactionState({ step: "signing", buildData });
|
|
3181
|
+
let assertion;
|
|
3182
|
+
try {
|
|
3183
|
+
assertion = await this._passkeySign({ credentialId: smart.credentialId, challenge: smart.digest });
|
|
3184
|
+
} catch (err) {
|
|
3185
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3186
|
+
this._setTransactionState({ step: "error", phase: "signing", buildData, ...details && { details } });
|
|
3187
|
+
return { status: "error", buildData, ...details && { details } };
|
|
3188
|
+
}
|
|
3189
|
+
this._setTransactionState({ step: "submitting", buildData });
|
|
3190
|
+
const outcomeExtra = { buildData };
|
|
3191
|
+
try {
|
|
3192
|
+
const { data, error } = await this._api.POST("/tx/submit", {
|
|
3193
|
+
body: {
|
|
3194
|
+
network: this.getNetwork(),
|
|
3195
|
+
address,
|
|
3196
|
+
smart: { entryXdr: smart.entryXdr, funcXdr: smart.funcXdr, assertion }
|
|
3197
|
+
}
|
|
3198
|
+
});
|
|
3199
|
+
if (!error && data?.success && data.content) {
|
|
3200
|
+
const { hash, status: backendStatus, resultCode } = data.content;
|
|
3201
|
+
if (backendStatus === "SUCCESS") {
|
|
3202
|
+
this._setTransactionState({ step: "success", hash, buildData });
|
|
3203
|
+
return { status: "success", hash, ...outcomeExtra };
|
|
3204
|
+
}
|
|
3205
|
+
if (backendStatus === "PENDING") {
|
|
3206
|
+
this._setTransactionState({ step: "submitted", hash, buildData });
|
|
3207
|
+
return { status: "pending", hash, ...outcomeExtra };
|
|
3208
|
+
}
|
|
3209
|
+
this._setTransactionState({
|
|
3210
|
+
step: "error",
|
|
3211
|
+
phase: "submitting",
|
|
3212
|
+
buildData,
|
|
3213
|
+
...resultCode && { details: resultCode }
|
|
3214
|
+
});
|
|
3215
|
+
return { status: "error", hash, ...outcomeExtra, ...resultCode && { details: resultCode, resultCode } };
|
|
3216
|
+
}
|
|
3217
|
+
const details = error?.details;
|
|
3218
|
+
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
3219
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
const details = err instanceof Error ? err.message : void 0;
|
|
3222
|
+
this._setTransactionState({ step: "error", phase: "submitting", buildData, ...details && { details } });
|
|
3223
|
+
return { status: "error", ...outcomeExtra, ...details && { details } };
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
2869
3226
|
// ─── App config ───────────────────────────────────────────────────────────
|
|
2870
3227
|
async getAppConfig() {
|
|
2871
3228
|
try {
|
|
@@ -2919,10 +3276,18 @@ var PollarClient = class {
|
|
|
2919
3276
|
this._txHistoryState = next;
|
|
2920
3277
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
2921
3278
|
}
|
|
3279
|
+
_setSessionsState(next) {
|
|
3280
|
+
this._sessionsState = next;
|
|
3281
|
+
for (const cb of this._sessionsStateListeners) cb(next);
|
|
3282
|
+
}
|
|
2922
3283
|
_setWalletBalanceState(next) {
|
|
2923
3284
|
this._walletBalanceState = next;
|
|
2924
3285
|
for (const cb of this._walletBalanceStateListeners) cb(next);
|
|
2925
3286
|
}
|
|
3287
|
+
_setEnabledAssetsState(next) {
|
|
3288
|
+
this._enabledAssetsState = next;
|
|
3289
|
+
for (const cb of this._enabledAssetsStateListeners) cb(next);
|
|
3290
|
+
}
|
|
2926
3291
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
2927
3292
|
_newController() {
|
|
2928
3293
|
this._loginController?.abort();
|
|
@@ -2947,6 +3312,7 @@ var PollarClient = class {
|
|
|
2947
3312
|
this._walletAdapter = adapter;
|
|
2948
3313
|
await writeWalletType(this._storage, this.apiKeyHash, id);
|
|
2949
3314
|
},
|
|
3315
|
+
...this._passkey ? { passkey: this._passkey } : {},
|
|
2950
3316
|
...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
|
|
2951
3317
|
};
|
|
2952
3318
|
}
|
|
@@ -2976,7 +3342,7 @@ var PollarClient = class {
|
|
|
2976
3342
|
}
|
|
2977
3343
|
}
|
|
2978
3344
|
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
2979
|
-
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
3345
|
+
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
|
|
2980
3346
|
throw new Error(
|
|
2981
3347
|
`[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
|
|
2982
3348
|
);
|
|
@@ -3017,21 +3383,66 @@ var PollarClient = class {
|
|
|
3017
3383
|
}
|
|
3018
3384
|
}
|
|
3019
3385
|
console.info("[PollarClient] Session restored from storage");
|
|
3020
|
-
this._setAuthState({ step: "authenticated", session: this._session });
|
|
3386
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: false });
|
|
3021
3387
|
this._scheduleNextRefresh();
|
|
3388
|
+
void this._resume();
|
|
3022
3389
|
} else {
|
|
3023
3390
|
console.info("[PollarClient] No session in storage");
|
|
3024
3391
|
}
|
|
3025
3392
|
}
|
|
3393
|
+
/**
|
|
3394
|
+
* Validate the restored session against the server and repopulate the
|
|
3395
|
+
* in-memory profile (PII is never persisted, so it's null after a cold
|
|
3396
|
+
* reload). Goes through the normal authed client, so it coalesces with any
|
|
3397
|
+
* in-flight refresh (onRequest awaits `_refreshPromise`) and, being a GET,
|
|
3398
|
+
* is auto-retried after a 401-triggered refresh.
|
|
3399
|
+
*
|
|
3400
|
+
* - 200 → store profile, mark the session `verified`.
|
|
3401
|
+
* - 401 → the refresh-on-401 path already ran; if the family was
|
|
3402
|
+
* revoked, refresh failed and `_clearSession()` took us to
|
|
3403
|
+
* idle. Nothing to do here — don't double-handle.
|
|
3404
|
+
* - network error → stay optimistic (do NOT log out); revalidated later on
|
|
3405
|
+
* `visibilitychange` or first use.
|
|
3406
|
+
*/
|
|
3407
|
+
async _resume() {
|
|
3408
|
+
if (!this._session) return;
|
|
3409
|
+
this._resumeController?.abort();
|
|
3410
|
+
const controller = new AbortController();
|
|
3411
|
+
this._resumeController = controller;
|
|
3412
|
+
try {
|
|
3413
|
+
const { data, error } = await this._api.GET("/auth/session/resume", { signal: controller.signal });
|
|
3414
|
+
if (error || !data) return;
|
|
3415
|
+
const content = data.content;
|
|
3416
|
+
if (!content || !this._session) return;
|
|
3417
|
+
this._profile = { ...content };
|
|
3418
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: true });
|
|
3419
|
+
} catch (err) {
|
|
3420
|
+
if (err?.name === "AbortError") return;
|
|
3421
|
+
console.warn("[PollarClient] resume failed (network); will retry", err);
|
|
3422
|
+
} finally {
|
|
3423
|
+
if (this._resumeController === controller) this._resumeController = null;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3026
3426
|
async _storeSession(session) {
|
|
3027
3427
|
console.info("[PollarClient] Session stored");
|
|
3428
|
+
const w = session.wallet;
|
|
3028
3429
|
const persisted = {
|
|
3029
3430
|
clientSessionId: session.clientSessionId,
|
|
3030
3431
|
userId: session.userId ?? null,
|
|
3031
3432
|
status: session.status,
|
|
3032
3433
|
token: session.token,
|
|
3033
3434
|
user: session.user,
|
|
3034
|
-
|
|
3435
|
+
// The wire response still carries the legacy `publicKey` alias (kept for
|
|
3436
|
+
// older SDKs); the persisted session standardizes on `address` only.
|
|
3437
|
+
wallet: {
|
|
3438
|
+
type: w.type,
|
|
3439
|
+
address: w.address ?? w.publicKey ?? null,
|
|
3440
|
+
...w.existsOnStellar !== void 0 ? { existsOnStellar: w.existsOnStellar } : {},
|
|
3441
|
+
...w.createdAt !== void 0 ? { createdAt: w.createdAt } : {},
|
|
3442
|
+
...w.linkedAt !== void 0 ? { linkedAt: w.linkedAt } : {},
|
|
3443
|
+
...w.network !== void 0 ? { network: w.network } : {},
|
|
3444
|
+
...w.deployTxHash !== void 0 ? { deployTxHash: w.deployTxHash } : {}
|
|
3445
|
+
}
|
|
3035
3446
|
};
|
|
3036
3447
|
this._session = persisted;
|
|
3037
3448
|
if (session.data) {
|
|
@@ -3044,7 +3455,7 @@ var PollarClient = class {
|
|
|
3044
3455
|
};
|
|
3045
3456
|
}
|
|
3046
3457
|
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
3047
|
-
this._setAuthState({ step: "authenticated", session: persisted });
|
|
3458
|
+
this._setAuthState({ step: "authenticated", session: persisted, verified: true });
|
|
3048
3459
|
this._scheduleNextRefresh();
|
|
3049
3460
|
}
|
|
3050
3461
|
async _clearSession() {
|
|
@@ -3127,6 +3538,6 @@ var StellarClient = class {
|
|
|
3127
3538
|
// src/index.ts
|
|
3128
3539
|
_setDefaultKeyManagerFactory((_storage, apiKey) => new WebCryptoKeyManager(apiKey));
|
|
3129
3540
|
|
|
3130
|
-
export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, 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 };
|
|
3541
|
+
export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, 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 };
|
|
3131
3542
|
//# sourceMappingURL=index.mjs.map
|
|
3132
3543
|
//# sourceMappingURL=index.mjs.map
|