@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.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { sha256 as sha256$1 } from '@noble/hashes/sha2';
|
|
2
|
+
|
|
1
3
|
var __create = Object.create;
|
|
2
4
|
var __defProp = Object.defineProperty;
|
|
3
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -183,20 +185,6 @@ function defaultKeyManager(storage, apiKey) {
|
|
|
183
185
|
return _factory(storage, apiKey);
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
// src/lib/sha256.ts
|
|
187
|
-
async function sha256(data) {
|
|
188
|
-
const buf = await crypto.subtle.digest("SHA-256", data);
|
|
189
|
-
return new Uint8Array(buf);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// src/lib/api-key-hash.ts
|
|
193
|
-
async function hashApiKey(apiKey) {
|
|
194
|
-
const digest = await sha256(new TextEncoder().encode(apiKey));
|
|
195
|
-
let hex = "";
|
|
196
|
-
for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
|
|
197
|
-
return hex;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
188
|
// src/lib/base64url.ts
|
|
201
189
|
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
202
190
|
(() => {
|
|
@@ -233,6 +221,17 @@ function base64urlEncode(bytes) {
|
|
|
233
221
|
function base64urlEncodeString(s) {
|
|
234
222
|
return base64urlEncode(new TextEncoder().encode(s));
|
|
235
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
|
+
}
|
|
236
235
|
|
|
237
236
|
// src/keys/thumbprint.ts
|
|
238
237
|
async function computeJwkThumbprint(jwk) {
|
|
@@ -243,11 +242,14 @@ async function computeJwkThumbprint(jwk) {
|
|
|
243
242
|
const digest = await sha256(new TextEncoder().encode(canonical));
|
|
244
243
|
return base64urlEncode(digest);
|
|
245
244
|
}
|
|
245
|
+
function toBase64url(value) {
|
|
246
|
+
return value.replace(/\+/g, "-").replace(/\//g, "_").replace(/[^A-Za-z0-9_-]/g, "");
|
|
247
|
+
}
|
|
246
248
|
function canonicalEcJwk(jwk) {
|
|
247
249
|
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
|
|
248
250
|
throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
|
|
249
251
|
}
|
|
250
|
-
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) };
|
|
251
253
|
}
|
|
252
254
|
|
|
253
255
|
// src/keys/web-crypto.ts
|
|
@@ -261,7 +263,7 @@ function openDb() {
|
|
|
261
263
|
return;
|
|
262
264
|
}
|
|
263
265
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
264
|
-
req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
|
|
266
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB open failed"));
|
|
265
267
|
req.onsuccess = () => resolve(req.result);
|
|
266
268
|
req.onupgradeneeded = () => {
|
|
267
269
|
const db = req.result;
|
|
@@ -274,7 +276,7 @@ function openDb() {
|
|
|
274
276
|
function awaitTx(req) {
|
|
275
277
|
return new Promise((resolve, reject) => {
|
|
276
278
|
req.onsuccess = () => resolve(req.result);
|
|
277
|
-
req.onerror = () => reject(req.error ?? new Error("IDB request failed"));
|
|
279
|
+
req.onerror = () => reject(req.error ?? new Error("[PollarClient:keys] IDB request failed"));
|
|
278
280
|
});
|
|
279
281
|
}
|
|
280
282
|
async function dbGet(key) {
|
|
@@ -369,10 +371,39 @@ var WebCryptoKeyManager = class {
|
|
|
369
371
|
}
|
|
370
372
|
}
|
|
371
373
|
this.keyPair = pair;
|
|
372
|
-
|
|
373
|
-
this.publicJwk = canonicalEcJwk(exported);
|
|
374
|
+
this.publicJwk = await this._exportPublicJwk(pair.publicKey);
|
|
374
375
|
this.thumbprint = await computeJwkThumbprint(this.publicJwk);
|
|
375
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
|
+
}
|
|
376
407
|
async reset() {
|
|
377
408
|
try {
|
|
378
409
|
if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
|
|
@@ -1130,6 +1161,9 @@ function defaultStorage(options = {}) {
|
|
|
1130
1161
|
return createLocalStorageAdapter(options);
|
|
1131
1162
|
}
|
|
1132
1163
|
|
|
1164
|
+
// src/version.ts
|
|
1165
|
+
var POLLAR_CORE_VERSION = "0.9.0-rc.0" ;
|
|
1166
|
+
|
|
1133
1167
|
// src/visibility/noop.ts
|
|
1134
1168
|
function createNoopVisibilityProvider() {
|
|
1135
1169
|
return {
|
|
@@ -1194,6 +1228,7 @@ var AUTH_ERROR_CODES = {
|
|
|
1194
1228
|
WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
|
|
1195
1229
|
WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
|
|
1196
1230
|
WALLET_RESOLVER_TIMEOUT: "WALLET_RESOLVER_TIMEOUT",
|
|
1231
|
+
PASSKEY_FAILED: "PASSKEY_FAILED",
|
|
1197
1232
|
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
1198
1233
|
};
|
|
1199
1234
|
var PollarFlowError = class extends Error {
|
|
@@ -1239,7 +1274,7 @@ var FreighterAdapter = class {
|
|
|
1239
1274
|
if (!userInfo?.publicKey) {
|
|
1240
1275
|
throw new Error("Failed to get user information from Freighter");
|
|
1241
1276
|
}
|
|
1242
|
-
return { address: userInfo.publicKey
|
|
1277
|
+
return { address: userInfo.publicKey };
|
|
1243
1278
|
}
|
|
1244
1279
|
async disconnect() {
|
|
1245
1280
|
}
|
|
@@ -1277,6 +1312,19 @@ var FreighterAdapter = class {
|
|
|
1277
1312
|
};
|
|
1278
1313
|
|
|
1279
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
|
+
}
|
|
1280
1328
|
function openAlbedoPopup(url) {
|
|
1281
1329
|
const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
|
|
1282
1330
|
if (!popup) {
|
|
@@ -1315,7 +1363,13 @@ function waitForAlbedoResult() {
|
|
|
1315
1363
|
});
|
|
1316
1364
|
}
|
|
1317
1365
|
var AlbedoAdapter = class {
|
|
1318
|
-
|
|
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;
|
|
1319
1373
|
this.type = "albedo" /* ALBEDO */;
|
|
1320
1374
|
}
|
|
1321
1375
|
async isAvailable() {
|
|
@@ -1325,7 +1379,7 @@ var AlbedoAdapter = class {
|
|
|
1325
1379
|
const url = new URL("https://albedo.link");
|
|
1326
1380
|
url.searchParams.set("intent", "public-key");
|
|
1327
1381
|
url.searchParams.set("app_name", "Pollar");
|
|
1328
|
-
url.searchParams.set("network",
|
|
1382
|
+
url.searchParams.set("network", this.network);
|
|
1329
1383
|
url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
|
|
1330
1384
|
url.searchParams.set("origin", window.location.origin);
|
|
1331
1385
|
openAlbedoPopup(url.toString());
|
|
@@ -1333,7 +1387,7 @@ var AlbedoAdapter = class {
|
|
|
1333
1387
|
if (!result.pubkey) {
|
|
1334
1388
|
throw new Error("Albedo connection rejected");
|
|
1335
1389
|
}
|
|
1336
|
-
return { address: result.pubkey
|
|
1390
|
+
return { address: result.pubkey };
|
|
1337
1391
|
}
|
|
1338
1392
|
async disconnect() {
|
|
1339
1393
|
}
|
|
@@ -1343,12 +1397,12 @@ var AlbedoAdapter = class {
|
|
|
1343
1397
|
async getNetwork() {
|
|
1344
1398
|
throw new Error("Albedo does not expose network");
|
|
1345
1399
|
}
|
|
1346
|
-
async signTransaction(xdr,
|
|
1400
|
+
async signTransaction(xdr, options) {
|
|
1347
1401
|
const url = new URL("https://albedo.link");
|
|
1348
1402
|
url.searchParams.set("intent", "tx");
|
|
1349
1403
|
url.searchParams.set("xdr", xdr);
|
|
1350
1404
|
url.searchParams.set("app_name", "Pollar");
|
|
1351
|
-
url.searchParams.set("network",
|
|
1405
|
+
url.searchParams.set("network", albedoNetwork(options, this.network));
|
|
1352
1406
|
url.searchParams.set("callback", window.location.href);
|
|
1353
1407
|
url.searchParams.set("origin", window.location.origin);
|
|
1354
1408
|
window.location.href = url.toString();
|
|
@@ -1361,7 +1415,7 @@ var AlbedoAdapter = class {
|
|
|
1361
1415
|
url.searchParams.set("intent", "sign-auth-entry");
|
|
1362
1416
|
url.searchParams.set("xdr", entryXdr);
|
|
1363
1417
|
url.searchParams.set("app_name", "Pollar");
|
|
1364
|
-
url.searchParams.set("network",
|
|
1418
|
+
url.searchParams.set("network", this.network);
|
|
1365
1419
|
url.searchParams.set("callback", window.location.href);
|
|
1366
1420
|
url.searchParams.set("origin", window.location.origin);
|
|
1367
1421
|
window.location.href = url.toString();
|
|
@@ -1448,8 +1502,12 @@ function isValidSession(value) {
|
|
|
1448
1502
|
return false;
|
|
1449
1503
|
}
|
|
1450
1504
|
const w = wallet;
|
|
1451
|
-
if (w["
|
|
1452
|
-
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");
|
|
1453
1511
|
return false;
|
|
1454
1512
|
}
|
|
1455
1513
|
if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
|
|
@@ -1460,6 +1518,10 @@ function isValidSession(value) {
|
|
|
1460
1518
|
console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
|
|
1461
1519
|
return false;
|
|
1462
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
|
+
}
|
|
1463
1525
|
return true;
|
|
1464
1526
|
}
|
|
1465
1527
|
async function readStorage(storage, apiKeyHash) {
|
|
@@ -1467,6 +1529,12 @@ async function readStorage(storage, apiKeyHash) {
|
|
|
1467
1529
|
if (!raw) return null;
|
|
1468
1530
|
try {
|
|
1469
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
|
+
}
|
|
1470
1538
|
if (!isValidSession(session)) {
|
|
1471
1539
|
await storage.remove(sessionStorageKey(apiKeyHash));
|
|
1472
1540
|
console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
|
|
@@ -1557,7 +1625,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1557
1625
|
}));
|
|
1558
1626
|
} catch (e) {
|
|
1559
1627
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1560
|
-
console.warn(e);
|
|
1628
|
+
console.warn("[PollarClient:stream] session-status request failed; will retry", e);
|
|
1561
1629
|
}
|
|
1562
1630
|
if (error || !data) {
|
|
1563
1631
|
await sleep(backoff);
|
|
@@ -1597,7 +1665,7 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
|
|
|
1597
1665
|
} catch (e) {
|
|
1598
1666
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1599
1667
|
if (e instanceof SessionStatusError) throw e;
|
|
1600
|
-
console.warn(e);
|
|
1668
|
+
console.warn("[PollarClient:stream] session-status stream read failed; will retry", e);
|
|
1601
1669
|
} finally {
|
|
1602
1670
|
reader.releaseLock();
|
|
1603
1671
|
}
|
|
@@ -1625,7 +1693,7 @@ async function pollUntilFound(baseUrl, clientSessionId, check, intervalMs = 500,
|
|
|
1625
1693
|
envelope = await response.json().catch(() => null);
|
|
1626
1694
|
} catch (e) {
|
|
1627
1695
|
if (e instanceof Error && e.name === "AbortError") throw e;
|
|
1628
|
-
console.warn(e);
|
|
1696
|
+
console.warn("[PollarClient:stream] session-status poll failed; will retry", e);
|
|
1629
1697
|
}
|
|
1630
1698
|
if (httpStatus === 404 || envelope?.code === "INVALID_CLIENT_SESSION_ID") {
|
|
1631
1699
|
throw new SessionStatusError("INVALID_CLIENT_SESSION_ID");
|
|
@@ -1829,6 +1897,55 @@ async function loginOAuth(provider, deps) {
|
|
|
1829
1897
|
await authenticate(clientSessionId, deps);
|
|
1830
1898
|
}
|
|
1831
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
|
+
|
|
1832
1949
|
// src/client/auth/walletFlow.ts
|
|
1833
1950
|
function withSignal(promise, signal) {
|
|
1834
1951
|
return Promise.race([
|
|
@@ -1855,12 +1972,12 @@ async function loginWallet(type, deps) {
|
|
|
1855
1972
|
setAuthState({ step: "wallet_not_installed", walletType: type });
|
|
1856
1973
|
return;
|
|
1857
1974
|
}
|
|
1858
|
-
const {
|
|
1859
|
-
connectedWallet =
|
|
1975
|
+
const { address } = await withSignal(adapter.connect(), signal);
|
|
1976
|
+
connectedWallet = address;
|
|
1860
1977
|
deps.storeWalletAdapter(adapter, type);
|
|
1861
1978
|
setAuthState({ step: "authenticating_wallet" });
|
|
1862
1979
|
const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
|
|
1863
|
-
body: { clientSessionId, walletAddress:
|
|
1980
|
+
body: { clientSessionId, walletAddress: address },
|
|
1864
1981
|
signal
|
|
1865
1982
|
});
|
|
1866
1983
|
if (walletError || !walletData?.success) {
|
|
@@ -1925,8 +2042,12 @@ var PollarClient = class {
|
|
|
1925
2042
|
this._transactionStateListeners = /* @__PURE__ */ new Set();
|
|
1926
2043
|
this._txHistoryState = { step: "idle" };
|
|
1927
2044
|
this._txHistoryStateListeners = /* @__PURE__ */ new Set();
|
|
2045
|
+
this._sessionsState = { step: "idle" };
|
|
2046
|
+
this._sessionsStateListeners = /* @__PURE__ */ new Set();
|
|
1928
2047
|
this._walletBalanceState = { step: "idle" };
|
|
1929
2048
|
this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
|
|
2049
|
+
this._enabledAssetsState = { step: "idle" };
|
|
2050
|
+
this._enabledAssetsStateListeners = /* @__PURE__ */ new Set();
|
|
1930
2051
|
this._authState = { step: "idle" };
|
|
1931
2052
|
this._authStateListeners = /* @__PURE__ */ new Set();
|
|
1932
2053
|
this._networkState = { step: "idle" };
|
|
@@ -1942,6 +2063,8 @@ var PollarClient = class {
|
|
|
1942
2063
|
this._storageDegradeListeners = /* @__PURE__ */ new Set();
|
|
1943
2064
|
this._walletAdapter = null;
|
|
1944
2065
|
this._loginController = null;
|
|
2066
|
+
/** Aborts an in-flight `/auth/session/resume` on destroy() or re-trigger. */
|
|
2067
|
+
this._resumeController = null;
|
|
1945
2068
|
this.apiKey = config.apiKey;
|
|
1946
2069
|
this.id = randomUUID();
|
|
1947
2070
|
this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
|
|
@@ -1954,6 +2077,8 @@ var PollarClient = class {
|
|
|
1954
2077
|
this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
|
|
1955
2078
|
this._walletAdapterResolver = config.walletAdapter ?? null;
|
|
1956
2079
|
this._walletResolverTimeoutMs = config.walletResolverTimeoutMs ?? 5e3;
|
|
2080
|
+
this._passkey = config.passkey ?? null;
|
|
2081
|
+
this._passkeySign = config.passkeySign ?? null;
|
|
1957
2082
|
this._deviceLabel = config.deviceLabel;
|
|
1958
2083
|
this._visibilityProvider = config.visibilityProvider ?? defaultVisibilityProvider();
|
|
1959
2084
|
this._maxIdleMs = config.maxIdleMs;
|
|
@@ -1967,7 +2092,9 @@ var PollarClient = class {
|
|
|
1967
2092
|
this._initialized = Promise.resolve();
|
|
1968
2093
|
return;
|
|
1969
2094
|
}
|
|
1970
|
-
console.info(
|
|
2095
|
+
console.info(
|
|
2096
|
+
`[PollarClient] Initialized v${POLLAR_CORE_VERSION} \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`
|
|
2097
|
+
);
|
|
1971
2098
|
this._initialized = this._initialize();
|
|
1972
2099
|
}
|
|
1973
2100
|
/**
|
|
@@ -2005,7 +2132,11 @@ var PollarClient = class {
|
|
|
2005
2132
|
}
|
|
2006
2133
|
await this._restoreSession();
|
|
2007
2134
|
this._visibilityUnsubscribe = this._visibilityProvider.onChange((visible) => {
|
|
2008
|
-
if (visible)
|
|
2135
|
+
if (!visible) return;
|
|
2136
|
+
void this._maybeProactiveRefresh();
|
|
2137
|
+
if (this._authState.step === "authenticated" && !this._authState.verified) {
|
|
2138
|
+
void this._resume();
|
|
2139
|
+
}
|
|
2009
2140
|
});
|
|
2010
2141
|
}
|
|
2011
2142
|
/** Detach the cross-tab storage listener and abort any in-flight login. */
|
|
@@ -2016,6 +2147,8 @@ var PollarClient = class {
|
|
|
2016
2147
|
}
|
|
2017
2148
|
this._loginController?.abort();
|
|
2018
2149
|
this._loginController = null;
|
|
2150
|
+
this._resumeController?.abort();
|
|
2151
|
+
this._resumeController = null;
|
|
2019
2152
|
this._clearRefreshTimer();
|
|
2020
2153
|
if (this._visibilityUnsubscribe) {
|
|
2021
2154
|
this._visibilityUnsubscribe();
|
|
@@ -2030,7 +2163,9 @@ var PollarClient = class {
|
|
|
2030
2163
|
request.headers.set("x-pollar-api-key", self.apiKey);
|
|
2031
2164
|
self._lastRequestAt = Date.now();
|
|
2032
2165
|
await self._initialized;
|
|
2033
|
-
|
|
2166
|
+
const cacheMethod = request.method.toUpperCase();
|
|
2167
|
+
const cacheBodyAllowed = cacheMethod !== "GET" && cacheMethod !== "HEAD";
|
|
2168
|
+
if (cacheBodyAllowed && request.body != null) {
|
|
2034
2169
|
try {
|
|
2035
2170
|
self._requestBodyCache.set(request, await request.clone().arrayBuffer());
|
|
2036
2171
|
} catch (err) {
|
|
@@ -2117,7 +2252,9 @@ var PollarClient = class {
|
|
|
2117
2252
|
}
|
|
2118
2253
|
}
|
|
2119
2254
|
}
|
|
2120
|
-
const
|
|
2255
|
+
const retryMethod = originalRequest.method.toUpperCase();
|
|
2256
|
+
const retryBodyAllowed = retryMethod !== "GET" && retryMethod !== "HEAD";
|
|
2257
|
+
const cachedBody = retryBodyAllowed ? this._requestBodyCache.get(originalRequest) : void 0;
|
|
2121
2258
|
const retried = new Request(originalRequest.url, {
|
|
2122
2259
|
method: originalRequest.method,
|
|
2123
2260
|
headers,
|
|
@@ -2161,19 +2298,26 @@ var PollarClient = class {
|
|
|
2161
2298
|
throw err;
|
|
2162
2299
|
}
|
|
2163
2300
|
if (error || !data) {
|
|
2164
|
-
console.
|
|
2301
|
+
console.error("[PollarClient] /auth/refresh returned error", { error });
|
|
2165
2302
|
await this._clearSession();
|
|
2166
2303
|
throw new Error("Refresh failed");
|
|
2167
2304
|
}
|
|
2168
2305
|
const successData = data;
|
|
2169
2306
|
if (!successData.success || !successData.content?.token) {
|
|
2170
|
-
console.
|
|
2307
|
+
console.error("[PollarClient] /auth/refresh response malformed", {
|
|
2308
|
+
success: successData.success,
|
|
2309
|
+
hasToken: !!successData.content?.token
|
|
2310
|
+
});
|
|
2171
2311
|
await this._clearSession();
|
|
2172
2312
|
throw new Error("Refresh response malformed");
|
|
2173
2313
|
}
|
|
2174
2314
|
const newToken = successData.content.token;
|
|
2175
2315
|
if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
|
|
2176
|
-
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
|
+
});
|
|
2177
2321
|
await this._clearSession();
|
|
2178
2322
|
throw new Error("Refresh response token shape invalid");
|
|
2179
2323
|
}
|
|
@@ -2367,6 +2511,19 @@ var PollarClient = class {
|
|
|
2367
2511
|
const controller = this._newController();
|
|
2368
2512
|
loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
|
|
2369
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
|
+
}
|
|
2370
2527
|
// ─── Cancel ───────────────────────────────────────────────────────────────
|
|
2371
2528
|
cancelLogin() {
|
|
2372
2529
|
this._loginController?.abort();
|
|
@@ -2429,6 +2586,29 @@ var PollarClient = class {
|
|
|
2429
2586
|
}
|
|
2430
2587
|
return data.content.sessions;
|
|
2431
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
|
+
}
|
|
2432
2612
|
/**
|
|
2433
2613
|
* Revoke a specific refresh-token family (a single device session). Use
|
|
2434
2614
|
* `listSessions` to enumerate the familyIds. Revoking the current session
|
|
@@ -2505,16 +2685,19 @@ var PollarClient = class {
|
|
|
2505
2685
|
cb(this._walletBalanceState);
|
|
2506
2686
|
return () => this._walletBalanceStateListeners.delete(cb);
|
|
2507
2687
|
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
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) {
|
|
2511
2695
|
this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
|
|
2512
2696
|
return;
|
|
2513
2697
|
}
|
|
2514
2698
|
this._setWalletBalanceState({ step: "loading" });
|
|
2515
2699
|
try {
|
|
2516
|
-
const
|
|
2517
|
-
const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
|
|
2700
|
+
const { data, error } = await this._api.GET("/wallet/balance");
|
|
2518
2701
|
if (!error && data?.success && data.content) {
|
|
2519
2702
|
this._setWalletBalanceState({ step: "loaded", data: data.content });
|
|
2520
2703
|
} else {
|
|
@@ -2524,6 +2707,53 @@ var PollarClient = class {
|
|
|
2524
2707
|
this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
|
|
2525
2708
|
}
|
|
2526
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
|
+
}
|
|
2527
2757
|
// ─── Transactions ─────────────────────────────────────────────────────────
|
|
2528
2758
|
/**
|
|
2529
2759
|
* Builds an unsigned XDR. Drives `_setTransactionState` for modal-style UIs
|
|
@@ -2531,14 +2761,14 @@ var PollarClient = class {
|
|
|
2531
2761
|
* inspect the result without subscribing to state changes.
|
|
2532
2762
|
*/
|
|
2533
2763
|
async buildTx(operation, params, options) {
|
|
2534
|
-
if (!this._session?.wallet?.
|
|
2764
|
+
if (!this._session?.wallet?.address) {
|
|
2535
2765
|
const details = "No wallet connected";
|
|
2536
2766
|
this._setTransactionState({ step: "error", phase: "building", details });
|
|
2537
2767
|
return { status: "error", details };
|
|
2538
2768
|
}
|
|
2539
2769
|
const body = {
|
|
2540
2770
|
network: this.getNetwork(),
|
|
2541
|
-
|
|
2771
|
+
address: this._session.wallet.address,
|
|
2542
2772
|
operation,
|
|
2543
2773
|
params,
|
|
2544
2774
|
options: options ?? {}
|
|
@@ -2579,7 +2809,7 @@ var PollarClient = class {
|
|
|
2579
2809
|
const buildData = this._currentBuildData();
|
|
2580
2810
|
this._setTransactionState({ step: "signing", ...buildData && { buildData } });
|
|
2581
2811
|
if (this._walletAdapter) {
|
|
2582
|
-
const accountToSign = this._session?.wallet?.
|
|
2812
|
+
const accountToSign = this._session?.wallet?.address;
|
|
2583
2813
|
const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
|
|
2584
2814
|
try {
|
|
2585
2815
|
const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
|
|
@@ -2600,10 +2830,10 @@ var PollarClient = class {
|
|
|
2600
2830
|
return { status: "error", ...details && { details } };
|
|
2601
2831
|
}
|
|
2602
2832
|
}
|
|
2603
|
-
const
|
|
2833
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2604
2834
|
try {
|
|
2605
2835
|
const { data, error } = await this._api.POST("/tx/sign", {
|
|
2606
|
-
body: { network: this.getNetwork(),
|
|
2836
|
+
body: { network: this.getNetwork(), address, unsignedXdr }
|
|
2607
2837
|
});
|
|
2608
2838
|
if (!error && data?.success && data.content?.signedXdr) {
|
|
2609
2839
|
const { signedXdr, idempotencyKey } = data.content;
|
|
@@ -2656,12 +2886,12 @@ var PollarClient = class {
|
|
|
2656
2886
|
const buildData = this._currentBuildData();
|
|
2657
2887
|
const outcomeExtra = buildData ? { buildData } : {};
|
|
2658
2888
|
this._setTransactionState({ step: "submitting", signedXdr, ...buildData && { buildData } });
|
|
2659
|
-
const
|
|
2889
|
+
const address = this._session?.wallet?.address ?? "";
|
|
2660
2890
|
try {
|
|
2661
2891
|
const { data, error } = await this._api.POST("/tx/submit", {
|
|
2662
2892
|
body: {
|
|
2663
2893
|
network: this.getNetwork(),
|
|
2664
|
-
|
|
2894
|
+
address,
|
|
2665
2895
|
signedXdr,
|
|
2666
2896
|
...opts?.submissionToken && { idempotencyKey: opts.submissionToken }
|
|
2667
2897
|
}
|
|
@@ -2721,6 +2951,19 @@ var PollarClient = class {
|
|
|
2721
2951
|
* `success` (ledger-confirmed), or `error[phase: 'signing-submitting']`.
|
|
2722
2952
|
*/
|
|
2723
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
|
+
}
|
|
2724
2967
|
if (this._walletAdapter) {
|
|
2725
2968
|
const signed = await this.signTx(unsignedXdr);
|
|
2726
2969
|
if (signed.status === "error") {
|
|
@@ -2738,7 +2981,7 @@ var PollarClient = class {
|
|
|
2738
2981
|
this._setTransactionState({ step: "signing-submitting", ...buildData && { buildData } });
|
|
2739
2982
|
const body = {
|
|
2740
2983
|
network: this.getNetwork(),
|
|
2741
|
-
|
|
2984
|
+
address: this._session?.wallet?.address ?? "",
|
|
2742
2985
|
unsignedXdr
|
|
2743
2986
|
};
|
|
2744
2987
|
try {
|
|
@@ -2807,14 +3050,20 @@ var PollarClient = class {
|
|
|
2807
3050
|
* `signTx`, and `submitTx` separately instead.
|
|
2808
3051
|
*/
|
|
2809
3052
|
async buildAndSignAndSubmitTx(operation, params, options) {
|
|
3053
|
+
if (this._session?.wallet?.type === "smart") {
|
|
3054
|
+
return this._runSmartTx(operation, params, options);
|
|
3055
|
+
}
|
|
2810
3056
|
if (this._walletAdapter) {
|
|
2811
3057
|
const built = await this.buildTx(operation, params, options);
|
|
2812
3058
|
if (built.status === "error") {
|
|
2813
3059
|
return { status: "error", ...built.details && { details: built.details } };
|
|
2814
3060
|
}
|
|
3061
|
+
if (!built.buildData.unsignedXdr) {
|
|
3062
|
+
return { status: "error", details: "build returned no unsigned transaction" };
|
|
3063
|
+
}
|
|
2815
3064
|
return this.signAndSubmitTx(built.buildData.unsignedXdr);
|
|
2816
3065
|
}
|
|
2817
|
-
if (!this._session?.wallet?.
|
|
3066
|
+
if (!this._session?.wallet?.address) {
|
|
2818
3067
|
this._setTransactionState({ step: "error", phase: "building-signing-submitting", details: "No wallet connected" });
|
|
2819
3068
|
return { status: "error", details: "No wallet connected" };
|
|
2820
3069
|
}
|
|
@@ -2823,7 +3072,7 @@ var PollarClient = class {
|
|
|
2823
3072
|
const { data, error } = await this._api.POST("/tx/build-sign-submit", {
|
|
2824
3073
|
body: {
|
|
2825
3074
|
network: this.getNetwork(),
|
|
2826
|
-
|
|
3075
|
+
address: this._session.wallet.address,
|
|
2827
3076
|
operation,
|
|
2828
3077
|
params,
|
|
2829
3078
|
options: options ?? {}
|
|
@@ -2867,6 +3116,113 @@ var PollarClient = class {
|
|
|
2867
3116
|
async runTx(operation, params, options) {
|
|
2868
3117
|
return this.buildAndSignAndSubmitTx(operation, params, options);
|
|
2869
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
|
+
}
|
|
2870
3226
|
// ─── App config ───────────────────────────────────────────────────────────
|
|
2871
3227
|
async getAppConfig() {
|
|
2872
3228
|
try {
|
|
@@ -2920,10 +3276,18 @@ var PollarClient = class {
|
|
|
2920
3276
|
this._txHistoryState = next;
|
|
2921
3277
|
for (const cb of this._txHistoryStateListeners) cb(next);
|
|
2922
3278
|
}
|
|
3279
|
+
_setSessionsState(next) {
|
|
3280
|
+
this._sessionsState = next;
|
|
3281
|
+
for (const cb of this._sessionsStateListeners) cb(next);
|
|
3282
|
+
}
|
|
2923
3283
|
_setWalletBalanceState(next) {
|
|
2924
3284
|
this._walletBalanceState = next;
|
|
2925
3285
|
for (const cb of this._walletBalanceStateListeners) cb(next);
|
|
2926
3286
|
}
|
|
3287
|
+
_setEnabledAssetsState(next) {
|
|
3288
|
+
this._enabledAssetsState = next;
|
|
3289
|
+
for (const cb of this._enabledAssetsStateListeners) cb(next);
|
|
3290
|
+
}
|
|
2927
3291
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
2928
3292
|
_newController() {
|
|
2929
3293
|
this._loginController?.abort();
|
|
@@ -2948,6 +3312,7 @@ var PollarClient = class {
|
|
|
2948
3312
|
this._walletAdapter = adapter;
|
|
2949
3313
|
await writeWalletType(this._storage, this.apiKeyHash, id);
|
|
2950
3314
|
},
|
|
3315
|
+
...this._passkey ? { passkey: this._passkey } : {},
|
|
2951
3316
|
...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
|
|
2952
3317
|
};
|
|
2953
3318
|
}
|
|
@@ -2977,7 +3342,7 @@ var PollarClient = class {
|
|
|
2977
3342
|
}
|
|
2978
3343
|
}
|
|
2979
3344
|
if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
|
|
2980
|
-
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
|
|
3345
|
+
if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter(this.getNetwork() === "mainnet" ? "public" : "testnet");
|
|
2981
3346
|
throw new Error(
|
|
2982
3347
|
`[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
|
|
2983
3348
|
);
|
|
@@ -3018,21 +3383,66 @@ var PollarClient = class {
|
|
|
3018
3383
|
}
|
|
3019
3384
|
}
|
|
3020
3385
|
console.info("[PollarClient] Session restored from storage");
|
|
3021
|
-
this._setAuthState({ step: "authenticated", session: this._session });
|
|
3386
|
+
this._setAuthState({ step: "authenticated", session: this._session, verified: false });
|
|
3022
3387
|
this._scheduleNextRefresh();
|
|
3388
|
+
void this._resume();
|
|
3023
3389
|
} else {
|
|
3024
3390
|
console.info("[PollarClient] No session in storage");
|
|
3025
3391
|
}
|
|
3026
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
|
+
}
|
|
3027
3426
|
async _storeSession(session) {
|
|
3028
3427
|
console.info("[PollarClient] Session stored");
|
|
3428
|
+
const w = session.wallet;
|
|
3029
3429
|
const persisted = {
|
|
3030
3430
|
clientSessionId: session.clientSessionId,
|
|
3031
3431
|
userId: session.userId ?? null,
|
|
3032
3432
|
status: session.status,
|
|
3033
3433
|
token: session.token,
|
|
3034
3434
|
user: session.user,
|
|
3035
|
-
|
|
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
|
+
}
|
|
3036
3446
|
};
|
|
3037
3447
|
this._session = persisted;
|
|
3038
3448
|
if (session.data) {
|
|
@@ -3045,7 +3455,7 @@ var PollarClient = class {
|
|
|
3045
3455
|
};
|
|
3046
3456
|
}
|
|
3047
3457
|
await writeStorage(this._storage, this.apiKeyHash, persisted);
|
|
3048
|
-
this._setAuthState({ step: "authenticated", session: persisted });
|
|
3458
|
+
this._setAuthState({ step: "authenticated", session: persisted, verified: true });
|
|
3049
3459
|
this._scheduleNextRefresh();
|
|
3050
3460
|
}
|
|
3051
3461
|
async _clearSession() {
|
|
@@ -3128,6 +3538,6 @@ var StellarClient = class {
|
|
|
3128
3538
|
// src/index.ts
|
|
3129
3539
|
_setDefaultKeyManagerFactory((_storage, apiKey) => new WebCryptoKeyManager(apiKey));
|
|
3130
3540
|
|
|
3131
|
-
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 };
|
|
3132
3542
|
//# sourceMappingURL=index.mjs.map
|
|
3133
3543
|
//# sourceMappingURL=index.mjs.map
|