@pollar/core 0.5.3 → 0.7.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.
@@ -0,0 +1,2599 @@
1
+ import { p256 } from '@noble/curves/p256';
2
+
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+
29
+ // ../../node_modules/@stellar/freighter-api/build/index.min.js
30
+ var require_index_min = __commonJS({
31
+ "../../node_modules/@stellar/freighter-api/build/index.min.js"(exports$1, module) {
32
+ !(function(e, r) {
33
+ "object" == typeof exports$1 && "object" == typeof module ? module.exports = r() : "function" == typeof define && define.amd ? define([], r) : "object" == typeof exports$1 ? exports$1.freighterApi = r() : e.freighterApi = r();
34
+ })(exports$1, (() => (() => {
35
+ var e, r, E = { d: (e2, r2) => {
36
+ for (var o2 in r2) E.o(r2, o2) && !E.o(e2, o2) && Object.defineProperty(e2, o2, { enumerable: true, get: r2[o2] });
37
+ }, o: (e2, r2) => Object.prototype.hasOwnProperty.call(e2, r2), r: (e2) => {
38
+ "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e2, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e2, "__esModule", { value: true });
39
+ } }, o = {};
40
+ E.r(o), E.d(o, { default: () => I, getNetwork: () => N, getNetworkDetails: () => s, getPublicKey: () => _, getUserInfo: () => R, isAllowed: () => C, isBrowser: () => c, isConnected: () => n, requestAccess: () => i, setAllowed: () => O, signAuthEntry: () => S, signBlob: () => A, signTransaction: () => t }), (function(e2) {
41
+ e2.CREATE_ACCOUNT = "CREATE_ACCOUNT", e2.FUND_ACCOUNT = "FUND_ACCOUNT", e2.ADD_ACCOUNT = "ADD_ACCOUNT", e2.IMPORT_ACCOUNT = "IMPORT_ACCOUNT", e2.IMPORT_HARDWARE_WALLET = "IMPORT_HARDWARE_WALLET", e2.LOAD_ACCOUNT = "LOAD_ACCOUNT", e2.MAKE_ACCOUNT_ACTIVE = "MAKE_ACCOUNT_ACTIVE", e2.UPDATE_ACCOUNT_NAME = "UPDATE_ACCOUNT_NAME", e2.GET_MNEMONIC_PHRASE = "GET_MNEMONIC_PHRASE", e2.CONFIRM_MNEMONIC_PHRASE = "CONFIRM_MNEMONIC_PHRASE", e2.CONFIRM_MIGRATED_MNEMONIC_PHRASE = "CONFIRM_MIGRATED_MNEMONIC_PHRASE", e2.RECOVER_ACCOUNT = "RECOVER_ACCOUNT", e2.CONFIRM_PASSWORD = "CONFIRM_PASSWORD", e2.REJECT_ACCESS = "REJECT_ACCESS", e2.GRANT_ACCESS = "GRANT_ACCESS", e2.SIGN_TRANSACTION = "SIGN_TRANSACTION", e2.SIGN_BLOB = "SIGN_BLOB", e2.SIGN_AUTH_ENTRY = "SIGN_AUTH_ENTRY", e2.HANDLE_SIGNED_HW_TRANSACTION = "HANDLE_SIGNED_HW_TRANSACTION", e2.REJECT_TRANSACTION = "REJECT_TRANSACTION", e2.SIGN_FREIGHTER_TRANSACTION = "SIGN_FREIGHTER_TRANSACTION", e2.SIGN_FREIGHTER_SOROBAN_TRANSACTION = "SIGN_FREIGHTER_SOROBAN_TRANSACTION", e2.ADD_RECENT_ADDRESS = "ADD_RECENT_ADDRESS", e2.LOAD_RECENT_ADDRESSES = "LOAD_RECENT_ADDRESSES", e2.SIGN_OUT = "SIGN_OUT", e2.SHOW_BACKUP_PHRASE = "SHOW_BACKUP_PHRASE", e2.SAVE_ALLOWLIST = "SAVE_ALLOWLIST", e2.SAVE_SETTINGS = "SAVE_SETTINGS", e2.LOAD_SETTINGS = "LOAD_SETTINGS", e2.GET_CACHED_ASSET_ICON = "GET_CACHED_ASSET_ICON", e2.CACHE_ASSET_ICON = "CACHE_ASSET_ICON", e2.GET_CACHED_ASSET_DOMAIN = "GET_CACHED_ASSET_DOMAIN", e2.CACHE_ASSET_DOMAIN = "CACHE_ASSET_DOMAIN", e2.GET_BLOCKED_ACCOUNTS = "GET_BLOCKED_ACCOUNTS", e2.GET_BLOCKED_DOMAINS = "GET_BLOCKED_DOMAINS", e2.ADD_CUSTOM_NETWORK = "ADD_CUSTOM_NETWORK", e2.CHANGE_NETWORK = "CHANGE_NETWORK", e2.REMOVE_CUSTOM_NETWORK = "REMOVE_CUSTOM_NETWORK", e2.EDIT_CUSTOM_NETWORK = "EDIT_CUSTOM_NETWORK", e2.RESET_EXP_DATA = "RESET_EXP_DATA", e2.ADD_TOKEN_ID = "ADD_TOKEN_ID", e2.GET_TOKEN_IDS = "GET_TOKEN_IDS", e2.REMOVE_TOKEN_ID = "REMOVE_TOKEN_ID", e2.GET_MIGRATABLE_ACCOUNTS = "GET_MIGRATABLE_ACCOUNTS", e2.GET_MIGRATED_MNEMONIC_PHRASE = "GET_MIGRATED_MNEMONIC_PHRASE", e2.MIGRATE_ACCOUNTS = "MIGRATE_ACCOUNTS";
42
+ })(e || (e = {})), (function(e2) {
43
+ e2.REQUEST_ACCESS = "REQUEST_ACCESS", e2.REQUEST_PUBLIC_KEY = "REQUEST_PUBLIC_KEY", e2.SUBMIT_TRANSACTION = "SUBMIT_TRANSACTION", e2.SUBMIT_BLOB = "SUBMIT_BLOB", e2.SUBMIT_AUTH_ENTRY = "SUBMIT_AUTH_ENTRY", e2.REQUEST_NETWORK = "REQUEST_NETWORK", e2.REQUEST_NETWORK_DETAILS = "REQUEST_NETWORK_DETAILS", e2.REQUEST_CONNECTION_STATUS = "REQUEST_CONNECTION_STATUS", e2.REQUEST_ALLOWED_STATUS = "REQUEST_ALLOWED_STATUS", e2.SET_ALLOWED_STATUS = "SET_ALLOWED_STATUS", e2.REQUEST_USER_INFO = "REQUEST_USER_INFO";
44
+ })(r || (r = {}));
45
+ const T = (e2) => {
46
+ const E2 = Date.now() + Math.random();
47
+ return window.postMessage({ source: "FREIGHTER_EXTERNAL_MSG_REQUEST", messageId: E2, ...e2 }, window.location.origin), new Promise(((o2) => {
48
+ let T2 = 0;
49
+ e2.type !== r.REQUEST_CONNECTION_STATUS && e2.type !== r.REQUEST_PUBLIC_KEY || (T2 = setTimeout((() => {
50
+ o2({ isConnected: false, publicKey: "" }), window.removeEventListener("message", _2);
51
+ }), 2e3));
52
+ const _2 = (e3) => {
53
+ var r2, t2;
54
+ e3.source === window && "FREIGHTER_EXTERNAL_MSG_RESPONSE" === (null === (r2 = null == e3 ? void 0 : e3.data) || void 0 === r2 ? void 0 : r2.source) && (null === (t2 = null == e3 ? void 0 : e3.data) || void 0 === t2 ? void 0 : t2.messagedId) === E2 && (o2(e3.data), window.removeEventListener("message", _2), clearTimeout(T2));
55
+ };
56
+ window.addEventListener("message", _2, false);
57
+ }));
58
+ }, _ = () => c ? (async () => {
59
+ let e2 = { publicKey: "", error: "" };
60
+ try {
61
+ e2 = await T({ type: r.REQUEST_PUBLIC_KEY });
62
+ } catch (e3) {
63
+ console.error(e3);
64
+ }
65
+ const { publicKey: E2, error: o2 } = e2;
66
+ if (o2) throw o2;
67
+ return E2;
68
+ })() : Promise.resolve(""), t = (e2, E2) => c ? (async (e3, E3, o2) => {
69
+ let _2 = "", t2 = "", A2 = "";
70
+ "object" == typeof E3 ? (_2 = E3.network || "", t2 = E3.accountToSign || "", A2 = E3.networkPassphrase || "") : (_2 = E3 || "", t2 = "");
71
+ let S2 = { signedTransaction: "", error: "" };
72
+ try {
73
+ S2 = await T({ transactionXdr: e3, network: _2, networkPassphrase: A2, accountToSign: t2, type: r.SUBMIT_TRANSACTION });
74
+ } catch (e4) {
75
+ throw console.error(e4), e4;
76
+ }
77
+ const { signedTransaction: n2, error: N2 } = S2;
78
+ if (N2) throw N2;
79
+ return n2;
80
+ })(e2, E2) : Promise.resolve(""), A = (e2, E2) => c ? (async (e3, E3) => {
81
+ let o2 = { signedBlob: "", error: "" };
82
+ const _2 = (E3 || {}).accountToSign || "";
83
+ try {
84
+ o2 = await T({ blob: e3, accountToSign: _2, type: r.SUBMIT_BLOB });
85
+ } catch (e4) {
86
+ throw console.error(e4), e4;
87
+ }
88
+ const { signedBlob: t2, error: A2 } = o2;
89
+ if (A2) throw A2;
90
+ return t2;
91
+ })(e2, E2) : Promise.resolve(""), S = (e2, E2) => c ? (async (e3, E3) => {
92
+ let o2 = { signedAuthEntry: "", error: "" };
93
+ const _2 = (E3 || {}).accountToSign || "";
94
+ try {
95
+ o2 = await T({ entryXdr: e3, accountToSign: _2, type: r.SUBMIT_AUTH_ENTRY });
96
+ } catch (e4) {
97
+ console.error(e4);
98
+ }
99
+ const { signedAuthEntry: t2, error: A2 } = o2;
100
+ if (A2) throw A2;
101
+ return t2;
102
+ })(e2, E2) : Promise.resolve(""), n = () => c ? window.freighter ? Promise.resolve(window.freighter) : (async () => {
103
+ let e2 = { isConnected: false };
104
+ try {
105
+ e2 = await T({ type: r.REQUEST_CONNECTION_STATUS });
106
+ } catch (e3) {
107
+ console.error(e3);
108
+ }
109
+ return e2.isConnected;
110
+ })() : Promise.resolve(false), N = () => c ? (async () => {
111
+ let e2 = { network: "", error: "" };
112
+ try {
113
+ e2 = await T({ type: r.REQUEST_NETWORK });
114
+ } catch (e3) {
115
+ console.error(e3);
116
+ }
117
+ const { network: E2, error: o2 } = e2;
118
+ if (o2) throw o2;
119
+ return E2;
120
+ })() : Promise.resolve(""), s = () => c ? (async () => {
121
+ let e2 = { networkDetails: { network: "", networkName: "", networkUrl: "", networkPassphrase: "", sorobanRpcUrl: void 0 }, error: "" };
122
+ try {
123
+ e2 = await T({ type: r.REQUEST_NETWORK_DETAILS });
124
+ } catch (e3) {
125
+ console.error(e3);
126
+ }
127
+ const { networkDetails: E2, error: o2 } = e2, { network: _2, networkUrl: t2, networkPassphrase: A2, sorobanRpcUrl: S2 } = E2;
128
+ if (o2) throw o2;
129
+ return { network: _2, networkUrl: t2, networkPassphrase: A2, sorobanRpcUrl: S2 };
130
+ })() : Promise.resolve({ network: "", networkUrl: "", networkPassphrase: "", sorobanRpcUrl: "" }), C = () => c ? (async () => {
131
+ let e2 = { isAllowed: false };
132
+ try {
133
+ e2 = await T({ type: r.REQUEST_ALLOWED_STATUS });
134
+ } catch (e3) {
135
+ console.error(e3);
136
+ }
137
+ return e2.isAllowed;
138
+ })() : Promise.resolve(false), O = () => c ? (async () => {
139
+ let e2 = { isAllowed: false, error: "" };
140
+ try {
141
+ e2 = await T({ type: r.SET_ALLOWED_STATUS });
142
+ } catch (e3) {
143
+ console.error(e3);
144
+ }
145
+ const { isAllowed: E2, error: o2 } = e2;
146
+ if (o2) throw o2;
147
+ return E2;
148
+ })() : Promise.resolve(false), R = () => c ? (async () => {
149
+ let e2 = { userInfo: { publicKey: "" }, error: "" };
150
+ try {
151
+ e2 = await T({ type: r.REQUEST_USER_INFO });
152
+ } catch (e3) {
153
+ console.error(e3);
154
+ }
155
+ const { userInfo: E2, error: o2 } = e2;
156
+ if (o2) throw o2;
157
+ return E2;
158
+ })() : Promise.resolve({ publicKey: "" }), i = () => c ? (async () => {
159
+ let e2 = { publicKey: "", error: "" };
160
+ try {
161
+ e2 = await T({ type: r.REQUEST_ACCESS });
162
+ } catch (e3) {
163
+ console.error(e3);
164
+ }
165
+ const { publicKey: E2, error: o2 } = e2;
166
+ if (o2) throw o2;
167
+ return E2;
168
+ })() : Promise.resolve(""), c = "undefined" != typeof window, I = { getPublicKey: _, signTransaction: t, signBlob: A, signAuthEntry: S, isConnected: n, getNetwork: N, getNetworkDetails: s, isAllowed: C, setAllowed: O, getUserInfo: R, requestAccess: i };
169
+ return o;
170
+ })()));
171
+ }
172
+ });
173
+
174
+ // src/keys/factory.ts
175
+ var _factory = null;
176
+ function _setDefaultKeyManagerFactory(factory) {
177
+ _factory = factory;
178
+ }
179
+ function defaultKeyManager(storage, apiKey) {
180
+ if (!_factory) {
181
+ throw new Error(
182
+ '[PollarClient] No default KeyManager factory registered. Did you import from "@pollar/core" via a non-standard path?'
183
+ );
184
+ }
185
+ return _factory(storage, apiKey);
186
+ }
187
+
188
+ // src/lib/sha256.ts
189
+ async function sha256(data) {
190
+ const buf = await crypto.subtle.digest("SHA-256", data);
191
+ return new Uint8Array(buf);
192
+ }
193
+
194
+ // src/lib/api-key-hash.ts
195
+ async function hashApiKey(apiKey) {
196
+ const digest = await sha256(new TextEncoder().encode(apiKey));
197
+ let hex = "";
198
+ for (let i = 0; i < 4; i++) hex += digest[i].toString(16).padStart(2, "0");
199
+ return hex;
200
+ }
201
+
202
+ // src/lib/base64url.ts
203
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
204
+ var REVERSE = (() => {
205
+ const m = /* @__PURE__ */ new Map();
206
+ for (let i = 0; i < ALPHABET.length; i++) m.set(ALPHABET[i], i);
207
+ return m;
208
+ })();
209
+ function base64urlEncode(bytes) {
210
+ let result = "";
211
+ let i = 0;
212
+ for (; i + 2 < bytes.length; i += 3) {
213
+ const b1 = bytes[i];
214
+ const b2 = bytes[i + 1];
215
+ const b3 = bytes[i + 2];
216
+ result += ALPHABET[b1 >> 2];
217
+ result += ALPHABET[(b1 & 3) << 4 | b2 >> 4];
218
+ result += ALPHABET[(b2 & 15) << 2 | b3 >> 6];
219
+ result += ALPHABET[b3 & 63];
220
+ }
221
+ if (i < bytes.length) {
222
+ const b1 = bytes[i];
223
+ if (i + 1 === bytes.length) {
224
+ result += ALPHABET[b1 >> 2];
225
+ result += ALPHABET[(b1 & 3) << 4];
226
+ } else {
227
+ const b2 = bytes[i + 1];
228
+ result += ALPHABET[b1 >> 2];
229
+ result += ALPHABET[(b1 & 3) << 4 | b2 >> 4];
230
+ result += ALPHABET[(b2 & 15) << 2];
231
+ }
232
+ }
233
+ return result;
234
+ }
235
+ function base64urlDecode(input) {
236
+ const clean = input.replace(/=+$/, "");
237
+ const out = new Uint8Array(Math.floor(clean.length * 3 / 4));
238
+ let byteIdx = 0;
239
+ for (let i = 0; i < clean.length; i += 4) {
240
+ const c1 = REVERSE.get(clean[i]);
241
+ const c2 = REVERSE.get(clean[i + 1]);
242
+ const c3 = i + 2 < clean.length ? REVERSE.get(clean[i + 2]) : void 0;
243
+ const c4 = i + 3 < clean.length ? REVERSE.get(clean[i + 3]) : void 0;
244
+ if (c1 === void 0 || c2 === void 0) {
245
+ throw new Error("[PollarClient] Invalid base64url input");
246
+ }
247
+ out[byteIdx++] = c1 << 2 | c2 >> 4;
248
+ if (c3 !== void 0) {
249
+ out[byteIdx++] = (c2 & 15) << 4 | c3 >> 2;
250
+ if (c4 !== void 0) {
251
+ out[byteIdx++] = (c3 & 3) << 6 | c4;
252
+ }
253
+ }
254
+ }
255
+ return out.slice(0, byteIdx);
256
+ }
257
+ function base64urlEncodeString(s) {
258
+ return base64urlEncode(new TextEncoder().encode(s));
259
+ }
260
+
261
+ // src/keys/thumbprint.ts
262
+ async function computeJwkThumbprint(jwk) {
263
+ if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.x || !jwk.y) {
264
+ throw new Error("[PollarClient:thumbprint] Expected EC P-256 JWK with x and y");
265
+ }
266
+ const canonical = `{"crv":"${jwk.crv}","kty":"${jwk.kty}","x":"${jwk.x}","y":"${jwk.y}"}`;
267
+ const digest = await sha256(new TextEncoder().encode(canonical));
268
+ return base64urlEncode(digest);
269
+ }
270
+ function canonicalEcJwk(jwk) {
271
+ if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
272
+ throw new Error("[PollarClient:thumbprint] Source JWK is not an EC P-256 public key");
273
+ }
274
+ return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
275
+ }
276
+
277
+ // src/keys/noble.ts
278
+ var STORAGE_KEY_PREFIX = "pollar:dpop-key:";
279
+ var NobleKeyManager = class {
280
+ constructor(storage, apiKey) {
281
+ this.apiKeyHash = null;
282
+ this.privateKey = null;
283
+ this.publicJwk = null;
284
+ this.thumbprint = null;
285
+ /** Cached in-flight init — see `WebCryptoKeyManager` for the rationale. */
286
+ this._initPromise = null;
287
+ this.storage = storage;
288
+ this.apiKey = apiKey;
289
+ }
290
+ get storageKey() {
291
+ if (!this.apiKeyHash) {
292
+ throw new Error("[PollarClient:keys] init() must be called before storage access");
293
+ }
294
+ return `${STORAGE_KEY_PREFIX}${this.apiKeyHash}`;
295
+ }
296
+ /**
297
+ * Idempotent and safe under concurrency. Other methods auto-await this so
298
+ * the manager is self-healing if `init()` was never explicitly invoked.
299
+ */
300
+ async init() {
301
+ if (this.privateKey) return;
302
+ if (!this._initPromise) {
303
+ this._initPromise = this._doInit().catch((err) => {
304
+ console.error("[PollarClient:keys] NobleKeyManager init failed", err);
305
+ this._initPromise = null;
306
+ throw err;
307
+ });
308
+ }
309
+ return this._initPromise;
310
+ }
311
+ async _doInit() {
312
+ if (!this.apiKeyHash) {
313
+ this.apiKeyHash = await hashApiKey(this.apiKey);
314
+ }
315
+ let priv = null;
316
+ try {
317
+ const stored = await this.storage.get(this.storageKey);
318
+ if (stored) {
319
+ const decoded = base64urlDecode(stored);
320
+ if (decoded.length === 32) priv = decoded;
321
+ }
322
+ } catch {
323
+ priv = null;
324
+ }
325
+ if (!priv) {
326
+ priv = p256.utils.randomPrivateKey();
327
+ try {
328
+ await this.storage.set(this.storageKey, base64urlEncode(priv));
329
+ } catch {
330
+ }
331
+ }
332
+ this.privateKey = priv;
333
+ const pub = p256.getPublicKey(priv, false);
334
+ if (pub.length !== 65 || pub[0] !== 4) {
335
+ throw new Error("[PollarClient:keys] Unexpected public key format from @noble/curves");
336
+ }
337
+ this.publicJwk = {
338
+ kty: "EC",
339
+ crv: "P-256",
340
+ x: base64urlEncode(pub.slice(1, 33)),
341
+ y: base64urlEncode(pub.slice(33, 65))
342
+ };
343
+ this.thumbprint = await computeJwkThumbprint(this.publicJwk);
344
+ }
345
+ async reset() {
346
+ try {
347
+ if (this.apiKeyHash) await this.storage.remove(this.storageKey);
348
+ } catch {
349
+ }
350
+ this.privateKey = null;
351
+ this.publicJwk = null;
352
+ this.thumbprint = null;
353
+ this._initPromise = null;
354
+ }
355
+ async getPublicJwk() {
356
+ if (!this.publicJwk) await this.init();
357
+ if (!this.publicJwk) {
358
+ throw new Error("[PollarClient:keys] Keypair initialization failed; getPublicJwk unavailable");
359
+ }
360
+ return { kty: this.publicJwk.kty, crv: this.publicJwk.crv, x: this.publicJwk.x, y: this.publicJwk.y };
361
+ }
362
+ async getThumbprint() {
363
+ if (!this.thumbprint) await this.init();
364
+ if (!this.thumbprint) {
365
+ throw new Error("[PollarClient:keys] Keypair initialization failed; getThumbprint unavailable");
366
+ }
367
+ return this.thumbprint;
368
+ }
369
+ async sign(payload) {
370
+ if (!this.privateKey) await this.init();
371
+ if (!this.privateKey) {
372
+ throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
373
+ }
374
+ const digest = await sha256(payload);
375
+ const signature = p256.sign(digest, this.privateKey, { prehash: false });
376
+ return signature.toCompactRawBytes();
377
+ }
378
+ };
379
+
380
+ // src/keys/web-crypto.ts
381
+ var DB_NAME = "pollar-keys";
382
+ var DB_VERSION = 1;
383
+ var STORE_NAME = "keys";
384
+ function openDb() {
385
+ return new Promise((resolve, reject) => {
386
+ if (typeof indexedDB === "undefined") {
387
+ reject(new Error("[PollarClient:keys] IndexedDB not available"));
388
+ return;
389
+ }
390
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
391
+ req.onerror = () => reject(req.error ?? new Error("IDB open failed"));
392
+ req.onsuccess = () => resolve(req.result);
393
+ req.onupgradeneeded = () => {
394
+ const db = req.result;
395
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
396
+ db.createObjectStore(STORE_NAME);
397
+ }
398
+ };
399
+ });
400
+ }
401
+ function awaitTx(req) {
402
+ return new Promise((resolve, reject) => {
403
+ req.onsuccess = () => resolve(req.result);
404
+ req.onerror = () => reject(req.error ?? new Error("IDB request failed"));
405
+ });
406
+ }
407
+ async function dbGet(key) {
408
+ const db = await openDb();
409
+ try {
410
+ const tx = db.transaction(STORE_NAME, "readonly");
411
+ const result = await awaitTx(tx.objectStore(STORE_NAME).get(key));
412
+ return result;
413
+ } finally {
414
+ db.close();
415
+ }
416
+ }
417
+ async function dbPut(key, value) {
418
+ const db = await openDb();
419
+ try {
420
+ const tx = db.transaction(STORE_NAME, "readwrite");
421
+ await awaitTx(tx.objectStore(STORE_NAME).put(value, key));
422
+ } finally {
423
+ db.close();
424
+ }
425
+ }
426
+ async function dbDelete(key) {
427
+ const db = await openDb();
428
+ try {
429
+ const tx = db.transaction(STORE_NAME, "readwrite");
430
+ await awaitTx(tx.objectStore(STORE_NAME).delete(key));
431
+ } finally {
432
+ db.close();
433
+ }
434
+ }
435
+ function isCryptoKeyPair(v) {
436
+ if (typeof v !== "object" || v === null) return false;
437
+ const obj = v;
438
+ return obj.privateKey !== void 0 && obj.publicKey !== void 0;
439
+ }
440
+ var WebCryptoKeyManager = class {
441
+ constructor(apiKey) {
442
+ this.apiKeyHash = null;
443
+ this.keyPair = null;
444
+ this.publicJwk = null;
445
+ this.thumbprint = null;
446
+ /**
447
+ * Cached in-flight init. Lets `init()` be called concurrently (or implicitly
448
+ * from `getPublicJwk` / `sign`) without doing the work twice. Cleared on
449
+ * failure so callers can retry, and cleared on `reset()`.
450
+ */
451
+ this._initPromise = null;
452
+ if (typeof globalThis.crypto === "undefined" || !globalThis.crypto.subtle) {
453
+ throw new Error(
454
+ "[PollarClient:keys] SubtleCrypto is unavailable. DPoP requires a secure context (HTTPS or localhost)."
455
+ );
456
+ }
457
+ this.apiKey = apiKey;
458
+ }
459
+ /**
460
+ * Idempotent and safe under concurrency. The first call kicks off the real
461
+ * init; subsequent (and concurrent) calls return the same in-flight promise.
462
+ * Other methods (`getPublicJwk`, `getThumbprint`, `sign`) auto-await this so
463
+ * the manager is self-healing if `init()` was never explicitly invoked.
464
+ */
465
+ async init() {
466
+ if (this.keyPair) return;
467
+ if (!this._initPromise) {
468
+ this._initPromise = this._doInit().catch((err) => {
469
+ console.error("[PollarClient:keys] WebCryptoKeyManager init failed", err);
470
+ this._initPromise = null;
471
+ throw err;
472
+ });
473
+ }
474
+ return this._initPromise;
475
+ }
476
+ async _doInit() {
477
+ if (!this.apiKeyHash) {
478
+ this.apiKeyHash = await hashApiKey(this.apiKey);
479
+ }
480
+ let pair;
481
+ try {
482
+ pair = await dbGet(this.apiKeyHash);
483
+ if (pair && !isCryptoKeyPair(pair)) pair = void 0;
484
+ } catch {
485
+ pair = void 0;
486
+ }
487
+ if (!pair) {
488
+ pair = await globalThis.crypto.subtle.generateKey(
489
+ { name: "ECDSA", namedCurve: "P-256" },
490
+ // false → private key non-extractable; per W3C ECDSA spec the public
491
+ // key is always extractable regardless of this flag.
492
+ false,
493
+ ["sign", "verify"]
494
+ );
495
+ try {
496
+ await dbPut(this.apiKeyHash, pair);
497
+ } catch {
498
+ }
499
+ }
500
+ this.keyPair = pair;
501
+ const exported = await globalThis.crypto.subtle.exportKey("jwk", pair.publicKey);
502
+ this.publicJwk = canonicalEcJwk(exported);
503
+ this.thumbprint = await computeJwkThumbprint(this.publicJwk);
504
+ }
505
+ async reset() {
506
+ try {
507
+ if (this.apiKeyHash) await dbDelete(this.apiKeyHash);
508
+ } catch {
509
+ }
510
+ this.keyPair = null;
511
+ this.publicJwk = null;
512
+ this.thumbprint = null;
513
+ this._initPromise = null;
514
+ }
515
+ async getPublicJwk() {
516
+ if (!this.publicJwk) await this.init();
517
+ if (!this.publicJwk) {
518
+ throw new Error("[PollarClient:keys] Keypair initialization failed; getPublicJwk unavailable");
519
+ }
520
+ return { kty: this.publicJwk.kty, crv: this.publicJwk.crv, x: this.publicJwk.x, y: this.publicJwk.y };
521
+ }
522
+ async getThumbprint() {
523
+ if (!this.thumbprint) await this.init();
524
+ if (!this.thumbprint) {
525
+ throw new Error("[PollarClient:keys] Keypair initialization failed; getThumbprint unavailable");
526
+ }
527
+ return this.thumbprint;
528
+ }
529
+ async sign(payload) {
530
+ if (!this.keyPair) await this.init();
531
+ if (!this.keyPair) {
532
+ throw new Error("[PollarClient:keys] Keypair initialization failed; sign unavailable");
533
+ }
534
+ const sig = await globalThis.crypto.subtle.sign(
535
+ { name: "ECDSA", hash: "SHA-256" },
536
+ this.keyPair.privateKey,
537
+ payload
538
+ );
539
+ return new Uint8Array(sig);
540
+ }
541
+ };
542
+
543
+ // ../../node_modules/openapi-fetch/dist/index.mjs
544
+ var PATH_PARAM_RE = /\{[^{}]+\}/g;
545
+ var supportsRequestInitExt = () => {
546
+ return typeof process === "object" && Number.parseInt(process?.versions?.node?.substring(0, 2)) >= 18 && process.versions.undici;
547
+ };
548
+ function randomID() {
549
+ return Math.random().toString(36).slice(2, 11);
550
+ }
551
+ function createClient(clientOptions) {
552
+ let {
553
+ baseUrl = "",
554
+ Request: CustomRequest = globalThis.Request,
555
+ fetch: baseFetch = globalThis.fetch,
556
+ querySerializer: globalQuerySerializer,
557
+ bodySerializer: globalBodySerializer,
558
+ pathSerializer: globalPathSerializer,
559
+ headers: baseHeaders,
560
+ requestInitExt = void 0,
561
+ ...baseOptions
562
+ } = { ...clientOptions };
563
+ requestInitExt = supportsRequestInitExt() ? requestInitExt : void 0;
564
+ baseUrl = removeTrailingSlash(baseUrl);
565
+ const globalMiddlewares = [];
566
+ async function coreFetch(schemaPath, fetchOptions) {
567
+ const {
568
+ baseUrl: localBaseUrl,
569
+ fetch: fetch2 = baseFetch,
570
+ Request = CustomRequest,
571
+ headers,
572
+ params = {},
573
+ parseAs = "json",
574
+ querySerializer: requestQuerySerializer,
575
+ bodySerializer = globalBodySerializer ?? defaultBodySerializer,
576
+ pathSerializer: requestPathSerializer,
577
+ body,
578
+ middleware: requestMiddlewares = [],
579
+ ...init
580
+ } = fetchOptions || {};
581
+ let finalBaseUrl = baseUrl;
582
+ if (localBaseUrl) {
583
+ finalBaseUrl = removeTrailingSlash(localBaseUrl) ?? baseUrl;
584
+ }
585
+ let querySerializer = typeof globalQuerySerializer === "function" ? globalQuerySerializer : createQuerySerializer(globalQuerySerializer);
586
+ if (requestQuerySerializer) {
587
+ querySerializer = typeof requestQuerySerializer === "function" ? requestQuerySerializer : createQuerySerializer({
588
+ ...typeof globalQuerySerializer === "object" ? globalQuerySerializer : {},
589
+ ...requestQuerySerializer
590
+ });
591
+ }
592
+ const pathSerializer = requestPathSerializer || globalPathSerializer || defaultPathSerializer;
593
+ const serializedBody = body === void 0 ? void 0 : bodySerializer(
594
+ body,
595
+ // Note: we declare mergeHeaders() both here and below because it’s a bit of a chicken-or-egg situation:
596
+ // bodySerializer() needs all headers so we aren’t dropping ones set by the user, however,
597
+ // the result of this ALSO sets the lowest-priority content-type header. So we re-merge below,
598
+ // setting the content-type at the very beginning to be overwritten.
599
+ // Lastly, based on the way headers work, it’s not a simple “present-or-not” check becauase null intentionally un-sets headers.
600
+ mergeHeaders(baseHeaders, headers, params.header)
601
+ );
602
+ const finalHeaders = mergeHeaders(
603
+ // with no body, we should not to set Content-Type
604
+ serializedBody === void 0 || // if serialized body is FormData; browser will correctly set Content-Type & boundary expression
605
+ serializedBody instanceof FormData ? {} : {
606
+ "Content-Type": "application/json"
607
+ },
608
+ baseHeaders,
609
+ headers,
610
+ params.header
611
+ );
612
+ const finalMiddlewares = [...globalMiddlewares, ...requestMiddlewares];
613
+ const requestInit = {
614
+ redirect: "follow",
615
+ ...baseOptions,
616
+ ...init,
617
+ body: serializedBody,
618
+ headers: finalHeaders
619
+ };
620
+ let id;
621
+ let options;
622
+ let request = new Request(
623
+ createFinalURL(schemaPath, { baseUrl: finalBaseUrl, params, querySerializer, pathSerializer }),
624
+ requestInit
625
+ );
626
+ let response;
627
+ for (const key in init) {
628
+ if (!(key in request)) {
629
+ request[key] = init[key];
630
+ }
631
+ }
632
+ if (finalMiddlewares.length) {
633
+ id = randomID();
634
+ options = Object.freeze({
635
+ baseUrl: finalBaseUrl,
636
+ fetch: fetch2,
637
+ parseAs,
638
+ querySerializer,
639
+ bodySerializer,
640
+ pathSerializer
641
+ });
642
+ for (const m of finalMiddlewares) {
643
+ if (m && typeof m === "object" && typeof m.onRequest === "function") {
644
+ const result = await m.onRequest({
645
+ request,
646
+ schemaPath,
647
+ params,
648
+ options,
649
+ id
650
+ });
651
+ if (result) {
652
+ if (result instanceof Request) {
653
+ request = result;
654
+ } else if (result instanceof Response) {
655
+ response = result;
656
+ break;
657
+ } else {
658
+ throw new Error("onRequest: must return new Request() or Response() when modifying the request");
659
+ }
660
+ }
661
+ }
662
+ }
663
+ }
664
+ if (!response) {
665
+ try {
666
+ response = await fetch2(request, requestInitExt);
667
+ } catch (error2) {
668
+ let errorAfterMiddleware = error2;
669
+ if (finalMiddlewares.length) {
670
+ for (let i = finalMiddlewares.length - 1; i >= 0; i--) {
671
+ const m = finalMiddlewares[i];
672
+ if (m && typeof m === "object" && typeof m.onError === "function") {
673
+ const result = await m.onError({
674
+ request,
675
+ error: errorAfterMiddleware,
676
+ schemaPath,
677
+ params,
678
+ options,
679
+ id
680
+ });
681
+ if (result) {
682
+ if (result instanceof Response) {
683
+ errorAfterMiddleware = void 0;
684
+ response = result;
685
+ break;
686
+ }
687
+ if (result instanceof Error) {
688
+ errorAfterMiddleware = result;
689
+ continue;
690
+ }
691
+ throw new Error("onError: must return new Response() or instance of Error");
692
+ }
693
+ }
694
+ }
695
+ }
696
+ if (errorAfterMiddleware) {
697
+ throw errorAfterMiddleware;
698
+ }
699
+ }
700
+ if (finalMiddlewares.length) {
701
+ for (let i = finalMiddlewares.length - 1; i >= 0; i--) {
702
+ const m = finalMiddlewares[i];
703
+ if (m && typeof m === "object" && typeof m.onResponse === "function") {
704
+ const result = await m.onResponse({
705
+ request,
706
+ response,
707
+ schemaPath,
708
+ params,
709
+ options,
710
+ id
711
+ });
712
+ if (result) {
713
+ if (!(result instanceof Response)) {
714
+ throw new Error("onResponse: must return new Response() when modifying the response");
715
+ }
716
+ response = result;
717
+ }
718
+ }
719
+ }
720
+ }
721
+ }
722
+ const contentLength = response.headers.get("Content-Length");
723
+ if (response.status === 204 || request.method === "HEAD" || contentLength === "0" && !response.headers.get("Transfer-Encoding")?.includes("chunked")) {
724
+ return response.ok ? { data: void 0, response } : { error: void 0, response };
725
+ }
726
+ if (response.ok) {
727
+ const getResponseData = async () => {
728
+ if (parseAs === "stream") {
729
+ return response.body;
730
+ }
731
+ if (parseAs === "json" && !contentLength) {
732
+ const raw = await response.text();
733
+ return raw ? JSON.parse(raw) : void 0;
734
+ }
735
+ return await response[parseAs]();
736
+ };
737
+ return { data: await getResponseData(), response };
738
+ }
739
+ let error = await response.text();
740
+ try {
741
+ error = JSON.parse(error);
742
+ } catch {
743
+ }
744
+ return { error, response };
745
+ }
746
+ return {
747
+ request(method, url, init) {
748
+ return coreFetch(url, { ...init, method: method.toUpperCase() });
749
+ },
750
+ /** Call a GET endpoint */
751
+ GET(url, init) {
752
+ return coreFetch(url, { ...init, method: "GET" });
753
+ },
754
+ /** Call a PUT endpoint */
755
+ PUT(url, init) {
756
+ return coreFetch(url, { ...init, method: "PUT" });
757
+ },
758
+ /** Call a POST endpoint */
759
+ POST(url, init) {
760
+ return coreFetch(url, { ...init, method: "POST" });
761
+ },
762
+ /** Call a DELETE endpoint */
763
+ DELETE(url, init) {
764
+ return coreFetch(url, { ...init, method: "DELETE" });
765
+ },
766
+ /** Call a OPTIONS endpoint */
767
+ OPTIONS(url, init) {
768
+ return coreFetch(url, { ...init, method: "OPTIONS" });
769
+ },
770
+ /** Call a HEAD endpoint */
771
+ HEAD(url, init) {
772
+ return coreFetch(url, { ...init, method: "HEAD" });
773
+ },
774
+ /** Call a PATCH endpoint */
775
+ PATCH(url, init) {
776
+ return coreFetch(url, { ...init, method: "PATCH" });
777
+ },
778
+ /** Call a TRACE endpoint */
779
+ TRACE(url, init) {
780
+ return coreFetch(url, { ...init, method: "TRACE" });
781
+ },
782
+ /** Register middleware */
783
+ use(...middleware) {
784
+ for (const m of middleware) {
785
+ if (!m) {
786
+ continue;
787
+ }
788
+ if (typeof m !== "object" || !("onRequest" in m || "onResponse" in m || "onError" in m)) {
789
+ throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");
790
+ }
791
+ globalMiddlewares.push(m);
792
+ }
793
+ },
794
+ /** Unregister middleware */
795
+ eject(...middleware) {
796
+ for (const m of middleware) {
797
+ const i = globalMiddlewares.indexOf(m);
798
+ if (i !== -1) {
799
+ globalMiddlewares.splice(i, 1);
800
+ }
801
+ }
802
+ }
803
+ };
804
+ }
805
+ function serializePrimitiveParam(name, value, options) {
806
+ if (value === void 0 || value === null) {
807
+ return "";
808
+ }
809
+ if (typeof value === "object") {
810
+ throw new Error(
811
+ "Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these."
812
+ );
813
+ }
814
+ return `${name}=${options?.allowReserved === true ? value : encodeURIComponent(value)}`;
815
+ }
816
+ function serializeObjectParam(name, value, options) {
817
+ if (!value || typeof value !== "object") {
818
+ return "";
819
+ }
820
+ const values = [];
821
+ const joiner = {
822
+ simple: ",",
823
+ label: ".",
824
+ matrix: ";"
825
+ }[options.style] || "&";
826
+ if (options.style !== "deepObject" && options.explode === false) {
827
+ for (const k in value) {
828
+ values.push(k, options.allowReserved === true ? value[k] : encodeURIComponent(value[k]));
829
+ }
830
+ const final2 = values.join(",");
831
+ switch (options.style) {
832
+ case "form": {
833
+ return `${name}=${final2}`;
834
+ }
835
+ case "label": {
836
+ return `.${final2}`;
837
+ }
838
+ case "matrix": {
839
+ return `;${name}=${final2}`;
840
+ }
841
+ default: {
842
+ return final2;
843
+ }
844
+ }
845
+ }
846
+ for (const k in value) {
847
+ const finalName = options.style === "deepObject" ? `${name}[${k}]` : k;
848
+ values.push(serializePrimitiveParam(finalName, value[k], options));
849
+ }
850
+ const final = values.join(joiner);
851
+ return options.style === "label" || options.style === "matrix" ? `${joiner}${final}` : final;
852
+ }
853
+ function serializeArrayParam(name, value, options) {
854
+ if (!Array.isArray(value)) {
855
+ return "";
856
+ }
857
+ if (options.explode === false) {
858
+ const joiner2 = { form: ",", spaceDelimited: "%20", pipeDelimited: "|" }[options.style] || ",";
859
+ const final = (options.allowReserved === true ? value : value.map((v) => encodeURIComponent(v))).join(joiner2);
860
+ switch (options.style) {
861
+ case "simple": {
862
+ return final;
863
+ }
864
+ case "label": {
865
+ return `.${final}`;
866
+ }
867
+ case "matrix": {
868
+ return `;${name}=${final}`;
869
+ }
870
+ // case "spaceDelimited":
871
+ // case "pipeDelimited":
872
+ default: {
873
+ return `${name}=${final}`;
874
+ }
875
+ }
876
+ }
877
+ const joiner = { simple: ",", label: ".", matrix: ";" }[options.style] || "&";
878
+ const values = [];
879
+ for (const v of value) {
880
+ if (options.style === "simple" || options.style === "label") {
881
+ values.push(options.allowReserved === true ? v : encodeURIComponent(v));
882
+ } else {
883
+ values.push(serializePrimitiveParam(name, v, options));
884
+ }
885
+ }
886
+ return options.style === "label" || options.style === "matrix" ? `${joiner}${values.join(joiner)}` : values.join(joiner);
887
+ }
888
+ function createQuerySerializer(options) {
889
+ return function querySerializer(queryParams) {
890
+ const search = [];
891
+ if (queryParams && typeof queryParams === "object") {
892
+ for (const name in queryParams) {
893
+ const value = queryParams[name];
894
+ if (value === void 0 || value === null) {
895
+ continue;
896
+ }
897
+ if (Array.isArray(value)) {
898
+ if (value.length === 0) {
899
+ continue;
900
+ }
901
+ search.push(
902
+ serializeArrayParam(name, value, {
903
+ style: "form",
904
+ explode: true,
905
+ ...options?.array,
906
+ allowReserved: options?.allowReserved || false
907
+ })
908
+ );
909
+ continue;
910
+ }
911
+ if (typeof value === "object") {
912
+ search.push(
913
+ serializeObjectParam(name, value, {
914
+ style: "deepObject",
915
+ explode: true,
916
+ ...options?.object,
917
+ allowReserved: options?.allowReserved || false
918
+ })
919
+ );
920
+ continue;
921
+ }
922
+ search.push(serializePrimitiveParam(name, value, options));
923
+ }
924
+ }
925
+ return search.join("&");
926
+ };
927
+ }
928
+ function defaultPathSerializer(pathname, pathParams) {
929
+ let nextURL = pathname;
930
+ for (const match of pathname.match(PATH_PARAM_RE) ?? []) {
931
+ let name = match.substring(1, match.length - 1);
932
+ let explode = false;
933
+ let style = "simple";
934
+ if (name.endsWith("*")) {
935
+ explode = true;
936
+ name = name.substring(0, name.length - 1);
937
+ }
938
+ if (name.startsWith(".")) {
939
+ style = "label";
940
+ name = name.substring(1);
941
+ } else if (name.startsWith(";")) {
942
+ style = "matrix";
943
+ name = name.substring(1);
944
+ }
945
+ if (!pathParams || pathParams[name] === void 0 || pathParams[name] === null) {
946
+ continue;
947
+ }
948
+ const value = pathParams[name];
949
+ if (Array.isArray(value)) {
950
+ nextURL = nextURL.replace(match, serializeArrayParam(name, value, { style, explode }));
951
+ continue;
952
+ }
953
+ if (typeof value === "object") {
954
+ nextURL = nextURL.replace(match, serializeObjectParam(name, value, { style, explode }));
955
+ continue;
956
+ }
957
+ if (style === "matrix") {
958
+ nextURL = nextURL.replace(match, `;${serializePrimitiveParam(name, value)}`);
959
+ continue;
960
+ }
961
+ nextURL = nextURL.replace(match, style === "label" ? `.${encodeURIComponent(value)}` : encodeURIComponent(value));
962
+ }
963
+ return nextURL;
964
+ }
965
+ function defaultBodySerializer(body, headers) {
966
+ if (body instanceof FormData) {
967
+ return body;
968
+ }
969
+ if (headers) {
970
+ const contentType = headers.get instanceof Function ? headers.get("Content-Type") ?? headers.get("content-type") : headers["Content-Type"] ?? headers["content-type"];
971
+ if (contentType === "application/x-www-form-urlencoded") {
972
+ return new URLSearchParams(body).toString();
973
+ }
974
+ }
975
+ return JSON.stringify(body);
976
+ }
977
+ function createFinalURL(pathname, options) {
978
+ let finalURL = `${options.baseUrl}${pathname}`;
979
+ if (options.params?.path) {
980
+ finalURL = options.pathSerializer(finalURL, options.params.path);
981
+ }
982
+ let search = options.querySerializer(options.params.query ?? {});
983
+ if (search.startsWith("?")) {
984
+ search = search.substring(1);
985
+ }
986
+ if (search) {
987
+ finalURL += `?${search}`;
988
+ }
989
+ return finalURL;
990
+ }
991
+ function mergeHeaders(...allHeaders) {
992
+ const finalHeaders = new Headers();
993
+ for (const h of allHeaders) {
994
+ if (!h || typeof h !== "object") {
995
+ continue;
996
+ }
997
+ const iterator = h instanceof Headers ? h.entries() : Object.entries(h);
998
+ for (const [k, v] of iterator) {
999
+ if (v === null) {
1000
+ finalHeaders.delete(k);
1001
+ } else if (Array.isArray(v)) {
1002
+ for (const v2 of v) {
1003
+ finalHeaders.append(k, v2);
1004
+ }
1005
+ } else if (v !== void 0) {
1006
+ finalHeaders.set(k, v);
1007
+ }
1008
+ }
1009
+ }
1010
+ return finalHeaders;
1011
+ }
1012
+ function removeTrailingSlash(url) {
1013
+ if (url.endsWith("/")) {
1014
+ return url.substring(0, url.length - 1);
1015
+ }
1016
+ return url;
1017
+ }
1018
+
1019
+ // src/api/client.ts
1020
+ function createApiClient(baseUrl) {
1021
+ return createClient({ baseUrl });
1022
+ }
1023
+
1024
+ // src/api/endpoints/kyc.ts
1025
+ async function getKycStatus(api, providerId) {
1026
+ const { data, error } = await api.GET("/kyc/status", {
1027
+ params: { query: providerId ? { providerId } : {} }
1028
+ });
1029
+ if (!data?.content || error) {
1030
+ throw new Error(error?.error ?? "Failed to get KYC status");
1031
+ }
1032
+ return data.content;
1033
+ }
1034
+ async function getKycProviders(api, country) {
1035
+ const { data, error } = await api.GET("/kyc/providers", { params: { query: { country } } });
1036
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to get KYC providers");
1037
+ return data.content;
1038
+ }
1039
+ async function startKyc(api, body) {
1040
+ const { data, error } = await api.POST("/kyc/start", { body });
1041
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to start KYC");
1042
+ return data.content;
1043
+ }
1044
+ async function resolveKyc(api, providerId, level = "basic") {
1045
+ const { status } = await getKycStatus(api, providerId);
1046
+ if (status === "approved") return { alreadyApproved: true };
1047
+ const started = await startKyc(api, { providerId, level });
1048
+ return { alreadyApproved: false, ...started };
1049
+ }
1050
+ async function pollKycStatus(api, providerId, { intervalMs = 3e3, timeoutMs = 3e5 } = {}) {
1051
+ const deadline = Date.now() + timeoutMs;
1052
+ while (Date.now() < deadline) {
1053
+ const { status } = await getKycStatus(api, providerId);
1054
+ if (status === "approved" || status === "rejected") return status;
1055
+ await new Promise((r) => setTimeout(r, intervalMs));
1056
+ }
1057
+ throw new Error("KYC polling timed out");
1058
+ }
1059
+
1060
+ // src/api/endpoints/ramps.ts
1061
+ async function getRampsQuote(api, query) {
1062
+ const { data, error } = await api.GET("/ramps/quote", { params: { query } });
1063
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to get ramp quotes");
1064
+ return data.content;
1065
+ }
1066
+ async function createOnRamp(api, body) {
1067
+ const { data, error } = await api.POST("/ramps/onramp", { body });
1068
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to create onramp");
1069
+ return data.content;
1070
+ }
1071
+ async function createOffRamp(api, body) {
1072
+ const { data, error } = await api.POST("/ramps/offramp", { body });
1073
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to create offramp");
1074
+ return data.content;
1075
+ }
1076
+ async function getRampTransaction(api, txId) {
1077
+ const { data, error } = await api.GET("/ramps/transaction/{txId}", { params: { path: { txId } } });
1078
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to get transaction");
1079
+ return data.content;
1080
+ }
1081
+ async function pollRampTransaction(api, txId, { intervalMs = 5e3, timeoutMs = 6e5 } = {}) {
1082
+ const deadline = Date.now() + timeoutMs;
1083
+ while (Date.now() < deadline) {
1084
+ const { status } = await getRampTransaction(api, txId);
1085
+ if (status === "completed" || status === "failed") return status;
1086
+ await new Promise((r) => setTimeout(r, intervalMs));
1087
+ }
1088
+ throw new Error("Ramp transaction polling timed out");
1089
+ }
1090
+
1091
+ // src/dpop.ts
1092
+ async function buildProof(args, keyManager) {
1093
+ const jwk = await keyManager.getPublicJwk();
1094
+ const header = {
1095
+ typ: "dpop+jwt",
1096
+ alg: "ES256",
1097
+ jwk
1098
+ };
1099
+ const payload = {
1100
+ jti: generateJti(),
1101
+ htm: args.htm.toUpperCase(),
1102
+ htu: normalizeHtu(args.htu),
1103
+ iat: Math.floor(Date.now() / 1e3)
1104
+ };
1105
+ if (args.accessToken !== void 0 && args.accessToken !== "") {
1106
+ payload.ath = base64urlEncode(await sha256(new TextEncoder().encode(args.accessToken)));
1107
+ }
1108
+ if (args.nonce !== void 0 && args.nonce !== "") {
1109
+ payload.nonce = args.nonce;
1110
+ }
1111
+ const encodedHeader = base64urlEncodeString(JSON.stringify(header));
1112
+ const encodedPayload = base64urlEncodeString(JSON.stringify(payload));
1113
+ const signingInput = `${encodedHeader}.${encodedPayload}`;
1114
+ const signature = await keyManager.sign(new TextEncoder().encode(signingInput));
1115
+ const encodedSignature = base64urlEncode(signature);
1116
+ return `${signingInput}.${encodedSignature}`;
1117
+ }
1118
+ function normalizeHtu(rawUrl) {
1119
+ let url;
1120
+ try {
1121
+ url = new URL(rawUrl);
1122
+ } catch {
1123
+ return rawUrl.split("#")[0].split("?")[0];
1124
+ }
1125
+ const scheme = url.protocol.toLowerCase();
1126
+ const host = url.hostname.toLowerCase();
1127
+ let port = url.port;
1128
+ if (scheme === "https:" && port === "443" || scheme === "http:" && port === "80") {
1129
+ port = "";
1130
+ }
1131
+ const portPart = port ? `:${port}` : "";
1132
+ return `${scheme}//${host}${portPart}${url.pathname}`;
1133
+ }
1134
+ function generateJti() {
1135
+ const c = globalThis.crypto;
1136
+ if (c && typeof c.randomUUID === "function") {
1137
+ return c.randomUUID();
1138
+ }
1139
+ if (c && typeof c.getRandomValues === "function") {
1140
+ const bytes = new Uint8Array(16);
1141
+ c.getRandomValues(bytes);
1142
+ bytes[6] = bytes[6] & 15 | 64;
1143
+ bytes[8] = bytes[8] & 63 | 128;
1144
+ const hex = [];
1145
+ for (let i = 0; i < 16; i++) hex.push(bytes[i].toString(16).padStart(2, "0"));
1146
+ return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
1147
+ }
1148
+ throw new Error(
1149
+ "[PollarClient:dpop] No secure random source available (crypto.randomUUID / crypto.getRandomValues). DPoP requires a secure context (HTTPS) or, in React Native, the `react-native-get-random-values` polyfill."
1150
+ );
1151
+ }
1152
+
1153
+ // src/stellar/StellarClient.ts
1154
+ var HORIZON_URLS = {
1155
+ mainnet: "https://horizon.stellar.org",
1156
+ testnet: "https://horizon-testnet.stellar.org"
1157
+ };
1158
+ var StellarClient = class {
1159
+ constructor(config) {
1160
+ this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
1161
+ }
1162
+ async submitTransaction(signedXdr) {
1163
+ try {
1164
+ const response = await fetch(`${this.horizonUrl}/transactions`, {
1165
+ method: "POST",
1166
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1167
+ body: new URLSearchParams({ tx: signedXdr })
1168
+ });
1169
+ if (!response.ok) {
1170
+ const body = await response.json().catch(() => ({}));
1171
+ return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
1172
+ }
1173
+ const data = await response.json();
1174
+ return { success: true, hash: data.hash };
1175
+ } catch {
1176
+ return { success: false, errorCode: "NETWORK_ERROR" };
1177
+ }
1178
+ }
1179
+ };
1180
+
1181
+ // src/storage/web.ts
1182
+ var LOG_PREFIX = "[PollarClient:storage]";
1183
+ function createMemoryAdapter() {
1184
+ const store = /* @__PURE__ */ new Map();
1185
+ return {
1186
+ async get(key) {
1187
+ const value = store.get(key);
1188
+ return value === void 0 ? null : value;
1189
+ },
1190
+ async set(key, value) {
1191
+ store.set(key, value);
1192
+ },
1193
+ async remove(key) {
1194
+ store.delete(key);
1195
+ }
1196
+ };
1197
+ }
1198
+ function createLocalStorageAdapter(options = {}) {
1199
+ const fallback = createMemoryAdapter();
1200
+ let degraded = false;
1201
+ function degrade(reason, error) {
1202
+ if (degraded) return;
1203
+ degraded = true;
1204
+ console.warn(`${LOG_PREFIX} localStorage unavailable (${reason}); degrading to in-memory storage`);
1205
+ options.onDegrade?.(reason, error);
1206
+ }
1207
+ return {
1208
+ async get(key) {
1209
+ if (degraded) return fallback.get(key);
1210
+ try {
1211
+ return globalThis.localStorage.getItem(key);
1212
+ } catch (error) {
1213
+ degrade("read-failed", error);
1214
+ return fallback.get(key);
1215
+ }
1216
+ },
1217
+ async set(key, value) {
1218
+ if (degraded) return fallback.set(key, value);
1219
+ try {
1220
+ globalThis.localStorage.setItem(key, value);
1221
+ } catch (error) {
1222
+ const reason = isQuotaError(error) ? "quota-exceeded" : "write-failed";
1223
+ degrade(reason, error);
1224
+ await fallback.set(key, value);
1225
+ }
1226
+ },
1227
+ async remove(key) {
1228
+ if (degraded) return fallback.remove(key);
1229
+ try {
1230
+ globalThis.localStorage.removeItem(key);
1231
+ } catch (error) {
1232
+ degrade("remove-failed", error);
1233
+ await fallback.remove(key);
1234
+ }
1235
+ }
1236
+ };
1237
+ }
1238
+ function isQuotaError(error) {
1239
+ if (typeof error !== "object" || error === null) return false;
1240
+ const name = error.name;
1241
+ const code = error.code;
1242
+ return name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED" || code === 22 || code === 1014;
1243
+ }
1244
+
1245
+ // src/storage/autodetect.ts
1246
+ var PROBE_KEY = "__pollar_storage_probe__";
1247
+ function defaultStorage(options = {}) {
1248
+ if (typeof globalThis === "undefined" || typeof globalThis.localStorage === "undefined") {
1249
+ options.onDegrade?.("unavailable");
1250
+ return createMemoryAdapter();
1251
+ }
1252
+ try {
1253
+ const probeValue = String(Date.now());
1254
+ globalThis.localStorage.setItem(PROBE_KEY, probeValue);
1255
+ const read = globalThis.localStorage.getItem(PROBE_KEY);
1256
+ globalThis.localStorage.removeItem(PROBE_KEY);
1257
+ if (read !== probeValue) {
1258
+ options.onDegrade?.("probe-failed");
1259
+ return createMemoryAdapter();
1260
+ }
1261
+ } catch (error) {
1262
+ options.onDegrade?.("probe-failed", error);
1263
+ return createMemoryAdapter();
1264
+ }
1265
+ return createLocalStorageAdapter(options);
1266
+ }
1267
+
1268
+ // src/types.ts
1269
+ var AUTH_ERROR_CODES = {
1270
+ SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
1271
+ EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
1272
+ EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
1273
+ EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
1274
+ EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
1275
+ AUTH_FAILED: "AUTH_FAILED",
1276
+ WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
1277
+ WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
1278
+ UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
1279
+ };
1280
+ var PollarFlowError = class extends Error {
1281
+ constructor(message) {
1282
+ super(message);
1283
+ this.code = "INVALID_FLOW";
1284
+ this.name = "PollarFlowError";
1285
+ }
1286
+ };
1287
+
1288
+ // src/wallets/FreighterAdapter.ts
1289
+ var import_freighter_api = __toESM(require_index_min());
1290
+
1291
+ // src/wallets/types.ts
1292
+ var WalletType = /* @__PURE__ */ ((WalletType2) => {
1293
+ WalletType2["FREIGHTER"] = "freighter";
1294
+ WalletType2["ALBEDO"] = "albedo";
1295
+ return WalletType2;
1296
+ })(WalletType || {});
1297
+
1298
+ // src/wallets/FreighterAdapter.ts
1299
+ var FreighterAdapter = class {
1300
+ constructor() {
1301
+ this.type = "freighter" /* FREIGHTER */;
1302
+ }
1303
+ async isAvailable() {
1304
+ try {
1305
+ return await (0, import_freighter_api.isConnected)();
1306
+ } catch {
1307
+ return false;
1308
+ }
1309
+ }
1310
+ async connect() {
1311
+ const connected = await (0, import_freighter_api.isConnected)();
1312
+ if (!connected) {
1313
+ throw new Error("Freighter wallet is not installed");
1314
+ }
1315
+ const allowed = await (0, import_freighter_api.isAllowed)();
1316
+ if (!allowed) {
1317
+ await (0, import_freighter_api.setAllowed)();
1318
+ }
1319
+ const userInfo = await (0, import_freighter_api.getUserInfo)();
1320
+ if (!userInfo?.publicKey) {
1321
+ throw new Error("Failed to get user information from Freighter");
1322
+ }
1323
+ return { address: userInfo.publicKey, publicKey: userInfo.publicKey };
1324
+ }
1325
+ async disconnect() {
1326
+ }
1327
+ async getPublicKey() {
1328
+ try {
1329
+ const allowed = await (0, import_freighter_api.isAllowed)();
1330
+ if (!allowed) return null;
1331
+ const userInfo = await (0, import_freighter_api.getUserInfo)();
1332
+ return userInfo?.publicKey ?? null;
1333
+ } catch {
1334
+ return null;
1335
+ }
1336
+ }
1337
+ async getNetwork() {
1338
+ return (0, import_freighter_api.getNetwork)();
1339
+ }
1340
+ async signTransaction(xdr, options) {
1341
+ const result = await (0, import_freighter_api.signTransaction)(xdr, {
1342
+ network: options?.network,
1343
+ networkPassphrase: options?.networkPassphrase,
1344
+ accountToSign: options?.accountToSign
1345
+ });
1346
+ if (!result || typeof result !== "string") {
1347
+ throw new Error("Invalid response from Freighter");
1348
+ }
1349
+ return { signedTxXdr: result };
1350
+ }
1351
+ async signAuthEntry(entryXdr, options) {
1352
+ const result = await (0, import_freighter_api.signAuthEntry)(entryXdr, { accountToSign: options?.accountToSign });
1353
+ if (!result || typeof result !== "string") {
1354
+ throw new Error("Invalid response from Freighter");
1355
+ }
1356
+ return { signedAuthEntry: result };
1357
+ }
1358
+ };
1359
+
1360
+ // src/wallets/AlbedoAdapter.ts
1361
+ function openAlbedoPopup(url) {
1362
+ const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
1363
+ if (!popup) {
1364
+ throw new Error("Failed to open Albedo popup (blocked by browser)");
1365
+ }
1366
+ return popup;
1367
+ }
1368
+ function waitForAlbedoPopup() {
1369
+ return new Promise((resolve, reject) => {
1370
+ const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1371
+ function handler(event) {
1372
+ if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
1373
+ clearTimeout(timeout);
1374
+ window.removeEventListener("message", handler);
1375
+ resolve(event.data.payload);
1376
+ }
1377
+ window.addEventListener("message", handler);
1378
+ });
1379
+ }
1380
+ function waitForAlbedoResult() {
1381
+ return new Promise((resolve, reject) => {
1382
+ const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1383
+ const parseResult = () => {
1384
+ const params = new URLSearchParams(window.location.search);
1385
+ if (!params.has("pubkey") && !params.has("signed_envelope_xdr") && !params.has("signed_xdr")) return;
1386
+ clearTimeout(timeout);
1387
+ const result = {};
1388
+ params.forEach((value, key) => {
1389
+ result[key] = value;
1390
+ });
1391
+ window.history.replaceState({}, document.title, window.location.pathname);
1392
+ resolve(result);
1393
+ };
1394
+ parseResult();
1395
+ window.addEventListener("popstate", parseResult);
1396
+ });
1397
+ }
1398
+ var AlbedoAdapter = class {
1399
+ constructor() {
1400
+ this.type = "albedo" /* ALBEDO */;
1401
+ }
1402
+ async isAvailable() {
1403
+ return typeof window !== "undefined";
1404
+ }
1405
+ async connect() {
1406
+ const url = new URL("https://albedo.link");
1407
+ url.searchParams.set("intent", "public-key");
1408
+ url.searchParams.set("app_name", "Pollar");
1409
+ url.searchParams.set("network", "testnet");
1410
+ url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1411
+ url.searchParams.set("origin", window.location.origin);
1412
+ openAlbedoPopup(url.toString());
1413
+ const result = await waitForAlbedoPopup();
1414
+ if (!result.pubkey) {
1415
+ throw new Error("Albedo connection rejected");
1416
+ }
1417
+ return { address: result.pubkey, publicKey: result.pubkey };
1418
+ }
1419
+ async disconnect() {
1420
+ }
1421
+ async getPublicKey() {
1422
+ return null;
1423
+ }
1424
+ async getNetwork() {
1425
+ throw new Error("Albedo does not expose network");
1426
+ }
1427
+ async signTransaction(xdr, _options) {
1428
+ const url = new URL("https://albedo.link");
1429
+ url.searchParams.set("intent", "tx");
1430
+ url.searchParams.set("xdr", xdr);
1431
+ url.searchParams.set("app_name", "Pollar");
1432
+ url.searchParams.set("network", "testnet");
1433
+ url.searchParams.set("callback", window.location.href);
1434
+ url.searchParams.set("origin", window.location.origin);
1435
+ window.location.href = url.toString();
1436
+ const result = await waitForAlbedoResult();
1437
+ if (!result.signed_envelope_xdr) throw new Error("Albedo signing rejected");
1438
+ return { signedTxXdr: result.signed_envelope_xdr };
1439
+ }
1440
+ async signAuthEntry(entryXdr, _options) {
1441
+ const url = new URL("https://albedo.link");
1442
+ url.searchParams.set("intent", "sign-auth-entry");
1443
+ url.searchParams.set("xdr", entryXdr);
1444
+ url.searchParams.set("app_name", "Pollar");
1445
+ url.searchParams.set("network", "testnet");
1446
+ url.searchParams.set("callback", window.location.href);
1447
+ url.searchParams.set("origin", window.location.origin);
1448
+ window.location.href = url.toString();
1449
+ const result = await waitForAlbedoResult();
1450
+ if (!result.signed_xdr) throw new Error("Albedo auth entry signing rejected");
1451
+ return { signedAuthEntry: result.signed_xdr };
1452
+ }
1453
+ };
1454
+
1455
+ // src/client/session.ts
1456
+ var SESSION_SUFFIX = ":session";
1457
+ var WALLET_TYPE_SUFFIX = ":walletType";
1458
+ function sessionStorageKey(apiKeyHash) {
1459
+ return `pollar:${apiKeyHash}${SESSION_SUFFIX}`;
1460
+ }
1461
+ function walletTypeStorageKey(apiKeyHash) {
1462
+ return `pollar:${apiKeyHash}${WALLET_TYPE_SUFFIX}`;
1463
+ }
1464
+ var MAX_ACCESS_TOKEN = 4096;
1465
+ var MAX_REFRESH_TOKEN = 4096;
1466
+ var MAX_USER_ID = 64;
1467
+ var MAX_CLIENT_SESSION_ID = 64;
1468
+ var MAX_STATUS = 64;
1469
+ var MAX_WALLET_PUBLIC_KEY = 128;
1470
+ var MAX_WALLET_TYPE = 32;
1471
+ function isBoundedString(v, max, allowEmpty = false) {
1472
+ if (typeof v !== "string") return false;
1473
+ if (!allowEmpty && v.length === 0) return false;
1474
+ return v.length <= max;
1475
+ }
1476
+ function isValidSession(value) {
1477
+ if (typeof value !== "object" || value === null) {
1478
+ console.warn("[PollarClient:session] Invalid session \u2014 value is not an object");
1479
+ return false;
1480
+ }
1481
+ const s = value;
1482
+ if (!isBoundedString(s["clientSessionId"], MAX_CLIENT_SESSION_ID)) {
1483
+ console.warn("[PollarClient:session] Invalid session \u2014 clientSessionId missing/empty/too long");
1484
+ return false;
1485
+ }
1486
+ if (s["userId"] !== null && !isBoundedString(s["userId"], MAX_USER_ID)) {
1487
+ console.warn("[PollarClient:session] Invalid session \u2014 userId must be string|null");
1488
+ return false;
1489
+ }
1490
+ if (!isBoundedString(s["status"], MAX_STATUS)) {
1491
+ console.warn("[PollarClient:session] Invalid session \u2014 status must be string");
1492
+ return false;
1493
+ }
1494
+ const token = s["token"];
1495
+ if (typeof token !== "object" || token === null) {
1496
+ console.warn("[PollarClient:session] Invalid session \u2014 token missing or not an object");
1497
+ return false;
1498
+ }
1499
+ const t = token;
1500
+ if (!isBoundedString(t["accessToken"], MAX_ACCESS_TOKEN)) {
1501
+ console.warn("[PollarClient:session] Invalid session \u2014 token.accessToken missing/empty/too long");
1502
+ return false;
1503
+ }
1504
+ if (!isBoundedString(t["refreshToken"], MAX_REFRESH_TOKEN)) {
1505
+ console.warn("[PollarClient:session] Invalid session \u2014 token.refreshToken missing/empty/too long");
1506
+ return false;
1507
+ }
1508
+ if (typeof t["expiresAt"] !== "number" || !Number.isFinite(t["expiresAt"])) {
1509
+ console.warn("[PollarClient:session] Invalid session \u2014 token.expiresAt must be a finite number");
1510
+ return false;
1511
+ }
1512
+ const user = s["user"];
1513
+ if (typeof user !== "object" || user === null) {
1514
+ console.warn("[PollarClient:session] Invalid session \u2014 user missing or not an object");
1515
+ return false;
1516
+ }
1517
+ const u = user;
1518
+ if (u["id"] !== void 0 && !isBoundedString(u["id"], MAX_USER_ID)) {
1519
+ console.warn("[PollarClient:session] Invalid session \u2014 user.id must be string if present");
1520
+ return false;
1521
+ }
1522
+ if (typeof u["ready"] !== "boolean") {
1523
+ console.warn("[PollarClient:session] Invalid session \u2014 user.ready must be boolean");
1524
+ return false;
1525
+ }
1526
+ const wallet = s["wallet"];
1527
+ if (typeof wallet !== "object" || wallet === null) {
1528
+ console.warn("[PollarClient:session] Invalid session \u2014 wallet missing or not an object");
1529
+ return false;
1530
+ }
1531
+ const w = wallet;
1532
+ if (w["publicKey"] !== null && !isBoundedString(w["publicKey"], MAX_WALLET_PUBLIC_KEY)) {
1533
+ console.warn("[PollarClient:session] Invalid session \u2014 wallet.publicKey must be string|null");
1534
+ return false;
1535
+ }
1536
+ if (w["existsOnStellar"] !== void 0 && typeof w["existsOnStellar"] !== "boolean") {
1537
+ console.warn("[PollarClient:session] Invalid session \u2014 wallet.existsOnStellar must be boolean if present");
1538
+ return false;
1539
+ }
1540
+ if (w["createdAt"] !== void 0 && (typeof w["createdAt"] !== "number" || !Number.isFinite(w["createdAt"]))) {
1541
+ console.warn("[PollarClient:session] Invalid session \u2014 wallet.createdAt must be a finite number if present");
1542
+ return false;
1543
+ }
1544
+ return true;
1545
+ }
1546
+ async function readStorage(storage, apiKeyHash) {
1547
+ const raw = await storage.get(sessionStorageKey(apiKeyHash));
1548
+ if (!raw) return null;
1549
+ try {
1550
+ const session = JSON.parse(raw);
1551
+ if (!isValidSession(session)) {
1552
+ await storage.remove(sessionStorageKey(apiKeyHash));
1553
+ console.warn("[PollarClient:session] Stored session is invalid \u2014 clearing storage");
1554
+ return null;
1555
+ }
1556
+ if (session.token.expiresAt * 1e3 < Date.now()) {
1557
+ return session;
1558
+ }
1559
+ return session;
1560
+ } catch (error) {
1561
+ console.error("[PollarClient:session] Failed to parse session from storage", error);
1562
+ await storage.remove(sessionStorageKey(apiKeyHash));
1563
+ return null;
1564
+ }
1565
+ }
1566
+ async function writeStorage(storage, apiKeyHash, session) {
1567
+ await storage.set(sessionStorageKey(apiKeyHash), JSON.stringify(session));
1568
+ }
1569
+ async function removeStorage(storage, apiKeyHash) {
1570
+ await storage.remove(sessionStorageKey(apiKeyHash));
1571
+ await storage.remove(walletTypeStorageKey(apiKeyHash));
1572
+ }
1573
+ async function writeWalletType(storage, apiKeyHash, type) {
1574
+ if (type.length > MAX_WALLET_TYPE) {
1575
+ throw new Error(`[PollarClient:session] walletType too long: ${type.length} > ${MAX_WALLET_TYPE}`);
1576
+ }
1577
+ await storage.set(walletTypeStorageKey(apiKeyHash), type);
1578
+ }
1579
+ async function readWalletType(storage, apiKeyHash) {
1580
+ return storage.get(walletTypeStorageKey(apiKeyHash));
1581
+ }
1582
+
1583
+ // src/client/stream.ts
1584
+ function abortableDelay(ms, signal) {
1585
+ return new Promise((resolve, reject) => {
1586
+ const t = setTimeout(resolve, ms);
1587
+ signal.addEventListener(
1588
+ "abort",
1589
+ () => {
1590
+ clearTimeout(t);
1591
+ reject(new DOMException("Aborted", "AbortError"));
1592
+ },
1593
+ { once: true }
1594
+ );
1595
+ });
1596
+ }
1597
+ var MAX_BACKOFF_MS = 5e3;
1598
+ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200, signal) {
1599
+ let backoff = retryDelayMs;
1600
+ const sleep = async (ms) => {
1601
+ if (ms <= 0) return;
1602
+ if (signal) await abortableDelay(ms, signal);
1603
+ else await new Promise((r) => setTimeout(r, ms));
1604
+ };
1605
+ while (true) {
1606
+ signal?.throwIfAborted();
1607
+ let data, error;
1608
+ try {
1609
+ ({ data, error } = await api.GET("/auth/session/status/{clientSessionId}", {
1610
+ params: { path: { clientSessionId } },
1611
+ parseAs: "stream",
1612
+ signal: signal ?? null
1613
+ }));
1614
+ } catch (e) {
1615
+ if (e instanceof Error && e.name === "AbortError") throw e;
1616
+ console.warn(e);
1617
+ }
1618
+ if (error || !data) {
1619
+ await sleep(backoff);
1620
+ backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
1621
+ continue;
1622
+ }
1623
+ const reader = data.getReader();
1624
+ const decoder = new TextDecoder();
1625
+ let streamDone = false;
1626
+ let sawAnyChunk = false;
1627
+ try {
1628
+ while (true) {
1629
+ signal?.throwIfAborted();
1630
+ const { done, value } = await reader.read();
1631
+ if (done) {
1632
+ streamDone = true;
1633
+ break;
1634
+ }
1635
+ sawAnyChunk = true;
1636
+ const chunk = decoder.decode(value);
1637
+ for (const message of chunk.split("\n\n").filter(Boolean)) {
1638
+ const dataLine = message.split("\n").find((l) => l.startsWith("data:"));
1639
+ if (!dataLine) continue;
1640
+ try {
1641
+ const parsed = JSON.parse(dataLine.slice("data:".length).trim());
1642
+ if (check(parsed)) {
1643
+ return parsed;
1644
+ }
1645
+ } catch {
1646
+ }
1647
+ }
1648
+ }
1649
+ } catch (e) {
1650
+ if (e instanceof Error && e.name === "AbortError") throw e;
1651
+ console.warn(e);
1652
+ } finally {
1653
+ reader.releaseLock();
1654
+ }
1655
+ if (sawAnyChunk) backoff = retryDelayMs;
1656
+ else backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
1657
+ const delay = streamDone ? backoff : 0;
1658
+ if (delay) await sleep(delay);
1659
+ }
1660
+ }
1661
+
1662
+ // src/client/auth/authenticate.ts
1663
+ async function authenticate(clientSessionId, deps, expectedWallet) {
1664
+ const { api, signal, setAuthState, storeSession, clearSession } = deps;
1665
+ setAuthState({ step: "authenticating" });
1666
+ await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
1667
+ const dpopJwk = await deps.getPublicJwk();
1668
+ const { data, error } = await api.POST("/auth/login", {
1669
+ body: {
1670
+ clientSessionId,
1671
+ dpopJwk,
1672
+ ...deps.deviceLabel ? { deviceLabel: deps.deviceLabel } : {}
1673
+ },
1674
+ signal
1675
+ });
1676
+ if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content)) {
1677
+ if (expectedWallet && data.content.data.providers.wallet?.address !== expectedWallet) {
1678
+ setAuthState({
1679
+ step: "error",
1680
+ previousStep: "authenticating",
1681
+ message: "Wallet mismatch: session wallet does not match connected wallet",
1682
+ errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
1683
+ });
1684
+ clearSession();
1685
+ return;
1686
+ }
1687
+ storeSession(data.content);
1688
+ } else {
1689
+ setAuthState({
1690
+ step: "error",
1691
+ previousStep: "authenticating",
1692
+ message: "Failed to load session",
1693
+ errorCode: AUTH_ERROR_CODES.AUTH_FAILED
1694
+ });
1695
+ clearSession();
1696
+ }
1697
+ }
1698
+
1699
+ // src/client/auth/deps.ts
1700
+ async function createAuthSession(deps) {
1701
+ const { api, signal, setAuthState } = deps;
1702
+ setAuthState({ step: "creating_session" });
1703
+ const { data, error } = await api.POST("/auth/session", { signal });
1704
+ if (error || !data?.success) {
1705
+ setAuthState({
1706
+ step: "error",
1707
+ previousStep: "creating_session",
1708
+ message: "Failed to create session",
1709
+ errorCode: AUTH_ERROR_CODES.SESSION_CREATE_FAILED
1710
+ });
1711
+ return null;
1712
+ }
1713
+ return data.content.clientSessionId;
1714
+ }
1715
+
1716
+ // src/client/auth/emailFlow.ts
1717
+ async function initEmailSession(deps) {
1718
+ const clientSessionId = await createAuthSession(deps);
1719
+ if (!clientSessionId) return;
1720
+ deps.setAuthState({ step: "entering_email", clientSessionId });
1721
+ }
1722
+ async function sendEmailCode(email, clientSessionId, deps) {
1723
+ const { api, signal, setAuthState } = deps;
1724
+ setAuthState({ step: "sending_email", email });
1725
+ const { data, error } = await api.POST("/auth/email", {
1726
+ body: { clientSessionId, email },
1727
+ signal
1728
+ });
1729
+ if (error || !data?.success) {
1730
+ setAuthState({
1731
+ step: "error",
1732
+ previousStep: "sending_email",
1733
+ message: "Failed to send code",
1734
+ errorCode: AUTH_ERROR_CODES.EMAIL_SEND_FAILED
1735
+ });
1736
+ return;
1737
+ }
1738
+ setAuthState({ step: "entering_code", clientSessionId, email });
1739
+ }
1740
+ async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
1741
+ const { api, signal, setAuthState } = deps;
1742
+ setAuthState({ step: "verifying_email_code", clientSessionId, email });
1743
+ const { data, error } = await api.POST("/auth/email/verify-code", {
1744
+ body: { clientSessionId, code },
1745
+ signal
1746
+ });
1747
+ if (data?.code === "SDK_EMAIL_CODE_VERIFIED") {
1748
+ await authenticate(clientSessionId, deps);
1749
+ return;
1750
+ }
1751
+ const errCode = error?.error ?? data?.code;
1752
+ if (errCode === "SDK_EMAIL_CODE_EXPIRED") {
1753
+ setAuthState({
1754
+ step: "error",
1755
+ previousStep: "verifying_email_code",
1756
+ message: "Code expired \u2014 request a new one",
1757
+ errorCode: AUTH_ERROR_CODES.EMAIL_CODE_EXPIRED,
1758
+ clientSessionId,
1759
+ email
1760
+ });
1761
+ return;
1762
+ }
1763
+ if (errCode === "INVALID_EMAIL_CODE" || errCode === "SDK_EMAIL_CODE_INVALID") {
1764
+ setAuthState({
1765
+ step: "error",
1766
+ previousStep: "verifying_email_code",
1767
+ message: "Invalid code \u2014 try again",
1768
+ errorCode: AUTH_ERROR_CODES.EMAIL_CODE_INVALID,
1769
+ clientSessionId,
1770
+ email
1771
+ });
1772
+ return;
1773
+ }
1774
+ setAuthState({
1775
+ step: "error",
1776
+ previousStep: "verifying_email_code",
1777
+ message: "Failed to verify code \u2014 try again",
1778
+ errorCode: AUTH_ERROR_CODES.EMAIL_VERIFY_FAILED
1779
+ });
1780
+ }
1781
+
1782
+ // src/client/auth/oauthFlow.ts
1783
+ function severOpener(popup) {
1784
+ if (!popup) return;
1785
+ try {
1786
+ popup.opener = null;
1787
+ } catch {
1788
+ }
1789
+ }
1790
+ async function loginOAuth(provider, deps) {
1791
+ const { setAuthState, basePath, apiKey } = deps;
1792
+ const popup = window.open("about:blank", "_blank");
1793
+ severOpener(popup);
1794
+ const clientSessionId = await createAuthSession(deps);
1795
+ if (!clientSessionId) {
1796
+ popup?.close();
1797
+ return;
1798
+ }
1799
+ setAuthState({ step: "opening_oauth", provider });
1800
+ const url = new URL(`${basePath}/auth/${provider}`);
1801
+ url.searchParams.set("api_key", apiKey);
1802
+ url.searchParams.set("client_session_id", clientSessionId);
1803
+ url.searchParams.set("redirect_uri", window.location.origin);
1804
+ if (popup) {
1805
+ popup.location.href = url.toString();
1806
+ severOpener(popup);
1807
+ } else {
1808
+ window.open(url.toString(), "_blank", "noopener,noreferrer");
1809
+ }
1810
+ await authenticate(clientSessionId, deps);
1811
+ }
1812
+
1813
+ // src/client/auth/walletFlow.ts
1814
+ function withSignal(promise, signal) {
1815
+ return Promise.race([
1816
+ promise,
1817
+ new Promise((_, reject) => {
1818
+ if (signal.aborted) {
1819
+ reject(new DOMException("Aborted", "AbortError"));
1820
+ return;
1821
+ }
1822
+ signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
1823
+ })
1824
+ ]);
1825
+ }
1826
+ async function loginWallet(type, deps) {
1827
+ const { api, signal, setAuthState } = deps;
1828
+ const clientSessionId = await createAuthSession(deps);
1829
+ if (!clientSessionId) return;
1830
+ let connectedWallet;
1831
+ try {
1832
+ setAuthState({ step: "connecting_wallet", walletType: type });
1833
+ const adapter = await deps.resolveWalletAdapter(type);
1834
+ const available = await withSignal(adapter.isAvailable(), signal);
1835
+ if (!available) {
1836
+ setAuthState({ step: "wallet_not_installed", walletType: type });
1837
+ return;
1838
+ }
1839
+ const { publicKey } = await withSignal(adapter.connect(), signal);
1840
+ connectedWallet = publicKey;
1841
+ deps.storeWalletAdapter(adapter, type);
1842
+ setAuthState({ step: "authenticating_wallet" });
1843
+ const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
1844
+ body: { clientSessionId, walletAddress: publicKey },
1845
+ signal
1846
+ });
1847
+ if (walletError || !walletData?.success) {
1848
+ setAuthState({
1849
+ step: "error",
1850
+ previousStep: "authenticating_wallet",
1851
+ message: "Wallet authentication failed",
1852
+ errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
1853
+ });
1854
+ return;
1855
+ }
1856
+ } catch {
1857
+ setAuthState({
1858
+ step: "error",
1859
+ previousStep: "connecting_wallet",
1860
+ message: "Wallet connection failed",
1861
+ errorCode: AUTH_ERROR_CODES.WALLET_CONNECT_FAILED
1862
+ });
1863
+ return;
1864
+ }
1865
+ await authenticate(clientSessionId, deps, connectedWallet);
1866
+ }
1867
+
1868
+ // src/client/client.ts
1869
+ var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
1870
+ var RETRIED_HEADER = "X-Pollar-Retried";
1871
+ function warnServerSide(method) {
1872
+ console.warn(
1873
+ `[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
1874
+ );
1875
+ }
1876
+ var PollarClient = class {
1877
+ constructor(config) {
1878
+ /**
1879
+ * Per-API-key storage namespace. Computed asynchronously inside
1880
+ * `_initialize()` because SHA-256 lives behind `crypto.subtle.digest`.
1881
+ * Accessing `apiKeyHash` before `await client.ready()` throws.
1882
+ */
1883
+ this._apiKeyHash = null;
1884
+ this._session = null;
1885
+ this._profile = null;
1886
+ /** Last `DPoP-Nonce` we saw from a server response. Carried into the next proof. */
1887
+ this._dpopNonce = null;
1888
+ /** Singleton in-flight refresh — concurrent 401s coalesce into one /auth/refresh call. */
1889
+ this._refreshPromise = null;
1890
+ this._storageEventHandler = null;
1891
+ this._transactionState = null;
1892
+ this._transactionStateListeners = /* @__PURE__ */ new Set();
1893
+ this._txHistoryState = { step: "idle" };
1894
+ this._txHistoryStateListeners = /* @__PURE__ */ new Set();
1895
+ this._walletBalanceState = { step: "idle" };
1896
+ this._walletBalanceStateListeners = /* @__PURE__ */ new Set();
1897
+ this._authState = { step: "idle" };
1898
+ this._authStateListeners = /* @__PURE__ */ new Set();
1899
+ this._networkState = { step: "idle" };
1900
+ this._networkStateListeners = /* @__PURE__ */ new Set();
1901
+ this._walletAdapter = null;
1902
+ this._loginController = null;
1903
+ this.apiKey = config.apiKey;
1904
+ this.id = crypto.randomUUID();
1905
+ this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
1906
+ this._storage = config.storage ?? defaultStorage(config.onStorageDegrade ? { onDegrade: config.onStorageDegrade } : void 0);
1907
+ this._keyManager = config.keyManager ?? defaultKeyManager(this._storage, config.apiKey);
1908
+ this._walletAdapterResolver = config.walletAdapter ?? null;
1909
+ this._deviceLabel = config.deviceLabel;
1910
+ this._api = createApiClient(this.basePath);
1911
+ this._wireMiddlewares();
1912
+ this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
1913
+ if (!isBrowser) {
1914
+ warnServerSide("constructor");
1915
+ this._initialized = Promise.resolve();
1916
+ return;
1917
+ }
1918
+ console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`);
1919
+ this._initialized = this._initialize();
1920
+ }
1921
+ /**
1922
+ * Short SHA-256-derived namespace for this client's persisted state.
1923
+ * Available after `await client.ready()` (or any awaited method); throws
1924
+ * if read before initialization completes.
1925
+ */
1926
+ get apiKeyHash() {
1927
+ if (this._apiKeyHash === null) {
1928
+ throw new Error("[PollarClient] apiKeyHash is not available until client.ready() resolves");
1929
+ }
1930
+ return this._apiKeyHash;
1931
+ }
1932
+ /** Awaitable handle for the initial keypair + session restore. */
1933
+ ready() {
1934
+ return this._initialized;
1935
+ }
1936
+ // ─── Lifecycle ────────────────────────────────────────────────────────────
1937
+ async _initialize() {
1938
+ this._apiKeyHash = await hashApiKey(this.apiKey);
1939
+ if (typeof window !== "undefined") {
1940
+ const sessionKey = sessionStorageKey(this._apiKeyHash);
1941
+ const handler = (e) => {
1942
+ if (e.key === sessionKey) {
1943
+ this._restoreSession().catch((err) => console.error("[PollarClient] Cross-tab restore failed", err));
1944
+ }
1945
+ };
1946
+ window.addEventListener("storage", handler);
1947
+ this._storageEventHandler = handler;
1948
+ }
1949
+ try {
1950
+ await this._keyManager.init();
1951
+ } catch (err) {
1952
+ console.warn("[PollarClient] KeyManager init failed; DPoP unavailable for this session", err);
1953
+ }
1954
+ await this._restoreSession();
1955
+ }
1956
+ /** Detach the cross-tab storage listener and abort any in-flight login. */
1957
+ destroy() {
1958
+ if (this._storageEventHandler && typeof window !== "undefined") {
1959
+ window.removeEventListener("storage", this._storageEventHandler);
1960
+ this._storageEventHandler = null;
1961
+ }
1962
+ this._loginController?.abort();
1963
+ this._loginController = null;
1964
+ }
1965
+ // ─── Middlewares (DPoP + auto-refresh) ────────────────────────────────────
1966
+ _wireMiddlewares() {
1967
+ const self = this;
1968
+ this._api.use({
1969
+ onRequest: async ({ request }) => {
1970
+ request.headers.set("x-pollar-api-key", self.apiKey);
1971
+ await self._initialized;
1972
+ const isRefresh = request.url.includes("/auth/refresh");
1973
+ if (!isRefresh && self._refreshPromise) await self._refreshPromise;
1974
+ if (isRefresh) {
1975
+ const refreshProof = await self._buildProofForRequest(request, void 0);
1976
+ if (refreshProof) request.headers.set("DPoP", refreshProof);
1977
+ return request;
1978
+ }
1979
+ const accessToken = self._session?.token?.accessToken;
1980
+ if (!accessToken) return request;
1981
+ const proof = await self._buildProofForRequest(request, accessToken);
1982
+ if (proof) {
1983
+ request.headers.set("Authorization", `DPoP ${accessToken}`);
1984
+ request.headers.set("DPoP", proof);
1985
+ } else {
1986
+ request.headers.set("Authorization", `Bearer ${accessToken}`);
1987
+ }
1988
+ return request;
1989
+ },
1990
+ onResponse: async ({ request, response }) => {
1991
+ const newNonce = response.headers.get("DPoP-Nonce");
1992
+ if (newNonce) self._dpopNonce = newNonce;
1993
+ if (response.status !== 401) return response;
1994
+ if (request.headers.get(RETRIED_HEADER)) return response;
1995
+ if (request.url.includes("/auth/refresh")) return response;
1996
+ const wwwAuth = response.headers.get("WWW-Authenticate") ?? "";
1997
+ const isNonceChallenge = wwwAuth.includes("use_dpop_nonce");
1998
+ if (!isNonceChallenge) {
1999
+ try {
2000
+ await self.refresh();
2001
+ } catch {
2002
+ return response;
2003
+ }
2004
+ }
2005
+ return self._retryRequest(request);
2006
+ }
2007
+ });
2008
+ }
2009
+ async _buildProofForRequest(request, accessToken) {
2010
+ try {
2011
+ const htu = request.url.split("?")[0].split("#")[0];
2012
+ return await buildProof(
2013
+ {
2014
+ htm: request.method,
2015
+ htu,
2016
+ ...accessToken ? { accessToken } : {},
2017
+ ...this._dpopNonce !== null ? { nonce: this._dpopNonce } : {}
2018
+ },
2019
+ this._keyManager
2020
+ );
2021
+ } catch (err) {
2022
+ console.warn("[PollarClient] DPoP proof build failed", err);
2023
+ return null;
2024
+ }
2025
+ }
2026
+ async _retryRequest(originalRequest) {
2027
+ const clone = originalRequest.clone();
2028
+ clone.headers.set(RETRIED_HEADER, "1");
2029
+ const accessToken = this._session?.token?.accessToken;
2030
+ if (accessToken) {
2031
+ const proof = await this._buildProofForRequest(clone, accessToken);
2032
+ if (proof) {
2033
+ clone.headers.set("Authorization", `DPoP ${accessToken}`);
2034
+ clone.headers.set("DPoP", proof);
2035
+ } else {
2036
+ clone.headers.set("Authorization", `Bearer ${accessToken}`);
2037
+ }
2038
+ }
2039
+ return fetch(clone);
2040
+ }
2041
+ // ─── Refresh (race-safe singleton) ───────────────────────────────────────
2042
+ /**
2043
+ * Coalesce concurrent refresh attempts. The first caller does the work;
2044
+ * everyone else awaits the same promise and sees the new tokens.
2045
+ */
2046
+ refresh() {
2047
+ if (this._refreshPromise) return this._refreshPromise;
2048
+ this._refreshPromise = this._doRefresh().finally(() => {
2049
+ this._refreshPromise = null;
2050
+ });
2051
+ return this._refreshPromise;
2052
+ }
2053
+ async _doRefresh() {
2054
+ const refreshToken = this._session?.token?.refreshToken;
2055
+ if (!refreshToken) {
2056
+ console.warn("[PollarClient] Refresh skipped: no refresh token in session");
2057
+ await this._clearSession();
2058
+ throw new Error("No refresh token available");
2059
+ }
2060
+ let data;
2061
+ let error;
2062
+ try {
2063
+ const response = await this._api.POST("/auth/refresh", { body: { refreshToken } });
2064
+ data = response.data;
2065
+ error = response.error;
2066
+ } catch (err) {
2067
+ console.error("[PollarClient] /auth/refresh request threw", err);
2068
+ await this._clearSession();
2069
+ throw err;
2070
+ }
2071
+ if (error || !data) {
2072
+ console.warn("[PollarClient] /auth/refresh returned error", { error });
2073
+ await this._clearSession();
2074
+ throw new Error("Refresh failed");
2075
+ }
2076
+ const successData = data;
2077
+ if (!successData.success || !successData.content?.token) {
2078
+ console.warn("[PollarClient] /auth/refresh response malformed", successData);
2079
+ await this._clearSession();
2080
+ throw new Error("Refresh response malformed");
2081
+ }
2082
+ const newToken = successData.content.token;
2083
+ if (typeof newToken.accessToken !== "string" || typeof newToken.refreshToken !== "string" || typeof newToken.expiresAt !== "number") {
2084
+ console.warn("[PollarClient] /auth/refresh token shape invalid", newToken);
2085
+ await this._clearSession();
2086
+ throw new Error("Refresh response token shape invalid");
2087
+ }
2088
+ if (this._session) {
2089
+ try {
2090
+ this._session = { ...this._session, token: newToken };
2091
+ await writeStorage(this._storage, this.apiKeyHash, this._session);
2092
+ console.info("[PollarClient] Tokens refreshed");
2093
+ } catch (err) {
2094
+ console.error("[PollarClient] Failed to persist refreshed session", err);
2095
+ }
2096
+ }
2097
+ }
2098
+ // ─── Auth state ──────────────────────────────────────────────────────────────
2099
+ getAuthState() {
2100
+ return this._authState;
2101
+ }
2102
+ onAuthStateChange(cb) {
2103
+ this._authStateListeners.add(cb);
2104
+ cb(this._authState);
2105
+ return () => this._authStateListeners.delete(cb);
2106
+ }
2107
+ /** PII (email, names, avatar, providers). Held in memory only — never persisted. */
2108
+ getUserProfile() {
2109
+ return this._profile;
2110
+ }
2111
+ // ─── Login (unified entry point) ─────────────────────────────────────────
2112
+ login(options) {
2113
+ if (!isBrowser) {
2114
+ warnServerSide("login");
2115
+ return;
2116
+ }
2117
+ if (options.provider === "google" || options.provider === "github" || options.provider === "email") {
2118
+ const controller = this._newController();
2119
+ const deps = this._flowDeps(controller.signal);
2120
+ if (options.provider === "google" || options.provider === "github") {
2121
+ loginOAuth(options.provider, {
2122
+ ...deps,
2123
+ basePath: this.basePath,
2124
+ apiKey: this.apiKey
2125
+ }).catch((err) => this._handleFlowError(err));
2126
+ } else if (options.provider === "email") {
2127
+ const { email } = options;
2128
+ initEmailSession(deps).then(() => {
2129
+ if (this._authState.step === "entering_email") {
2130
+ return sendEmailCode(email, this._authState.clientSessionId, deps);
2131
+ }
2132
+ }).catch((err) => this._handleFlowError(err));
2133
+ }
2134
+ } else if (options.provider === "wallet") {
2135
+ this.loginWallet(options.type);
2136
+ }
2137
+ }
2138
+ // ─── Email OTP flow (3 steps) ─────────────────────────────────────────────
2139
+ beginEmailLogin() {
2140
+ if (!isBrowser) {
2141
+ warnServerSide("beginEmailLogin");
2142
+ return;
2143
+ }
2144
+ const controller = this._newController();
2145
+ initEmailSession(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2146
+ }
2147
+ sendEmailCode(email) {
2148
+ if (!isBrowser) {
2149
+ warnServerSide("sendEmailCode");
2150
+ return;
2151
+ }
2152
+ if (this._authState.step !== "entering_email") {
2153
+ throw new PollarFlowError(`sendEmailCode() requires step 'entering_email', current step is '${this._authState.step}'`);
2154
+ }
2155
+ const { clientSessionId } = this._authState;
2156
+ const signal = this._loginController.signal;
2157
+ sendEmailCode(email, clientSessionId, this._flowDeps(signal)).catch((err) => this._handleFlowError(err));
2158
+ }
2159
+ verifyEmailCode(code) {
2160
+ if (!isBrowser) {
2161
+ warnServerSide("verifyEmailCode");
2162
+ return;
2163
+ }
2164
+ const isRetryableError = this._authState.step === "error" && this._authState.clientSessionId != null && (this._authState.errorCode === AUTH_ERROR_CODES.EMAIL_CODE_INVALID || this._authState.errorCode === AUTH_ERROR_CODES.EMAIL_CODE_EXPIRED);
2165
+ if (this._authState.step !== "entering_code" && !isRetryableError) {
2166
+ throw new PollarFlowError(`verifyEmailCode() requires step 'entering_code', current step is '${this._authState.step}'`);
2167
+ }
2168
+ const state = this._authState;
2169
+ const clientSessionId = state.step === "entering_code" ? state.clientSessionId : state.clientSessionId;
2170
+ const email = state.step === "entering_code" ? state.email : state.email ?? "";
2171
+ const controller = this._newController();
2172
+ verifyAndAuthenticate(code, clientSessionId, email, this._flowDeps(controller.signal)).catch(
2173
+ (err) => this._handleFlowError(err)
2174
+ );
2175
+ }
2176
+ // ─── Wallet flow (single call) ────────────────────────────────────────────
2177
+ loginWallet(type) {
2178
+ if (!isBrowser) {
2179
+ warnServerSide("loginWallet");
2180
+ return;
2181
+ }
2182
+ const controller = this._newController();
2183
+ loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
2184
+ }
2185
+ // ─── Cancel ───────────────────────────────────────────────────────────────
2186
+ cancelLogin() {
2187
+ this._loginController?.abort();
2188
+ this._loginController = null;
2189
+ this._setAuthState({ step: "idle" });
2190
+ }
2191
+ // ─── Logout ───────────────────────────────────────────────────────────────
2192
+ /**
2193
+ * Revoke the current session server-side, then clear local storage.
2194
+ *
2195
+ * Server revocation is best-effort: if the POST fails (offline, server
2196
+ * down), local state is wiped regardless. The orphan refresh token then
2197
+ * remains unused until its natural expiry. The in-flight access token
2198
+ * stays valid until its own TTL elapses (≤10 min for DPoP-bound tokens).
2199
+ *
2200
+ * Pass `everywhere: true` to revoke every active session for this user
2201
+ * across all devices.
2202
+ */
2203
+ async logout(options = {}) {
2204
+ if (!isBrowser) {
2205
+ warnServerSide("logout");
2206
+ return;
2207
+ }
2208
+ console.info("[PollarClient] Logout requested", { everywhere: !!options.everywhere });
2209
+ if (this._session?.token?.accessToken) {
2210
+ try {
2211
+ await this._api.POST("/auth/logout", {
2212
+ body: options.everywhere ? { everywhere: true } : {}
2213
+ });
2214
+ } catch (err) {
2215
+ console.warn("[PollarClient] Server logout failed (continuing with local clear)", err);
2216
+ }
2217
+ }
2218
+ try {
2219
+ await this._clearSession();
2220
+ } catch (err) {
2221
+ console.warn("[PollarClient] Local logout cleanup failed", err);
2222
+ }
2223
+ }
2224
+ /** Convenience: revoke every active session for this user (all devices). */
2225
+ logoutEverywhere() {
2226
+ return this.logout({ everywhere: true });
2227
+ }
2228
+ /**
2229
+ * List active sessions for the authenticated user. Returns one entry per
2230
+ * refresh-token family with the metadata captured at issuance time. The
2231
+ * `current` flag identifies which entry corresponds to this client.
2232
+ */
2233
+ async listSessions() {
2234
+ if (!isBrowser) {
2235
+ warnServerSide("listSessions");
2236
+ return [];
2237
+ }
2238
+ if (!this._session?.token?.accessToken) {
2239
+ throw new Error("[PollarClient] listSessions requires an authenticated session");
2240
+ }
2241
+ const { data, error } = await this._api.GET("/auth/sessions");
2242
+ if (error || !data?.success) {
2243
+ throw new Error("[PollarClient] Failed to list sessions");
2244
+ }
2245
+ return data.content.sessions;
2246
+ }
2247
+ /**
2248
+ * Revoke a specific refresh-token family (a single device session). Use
2249
+ * `listSessions` to enumerate the familyIds. Revoking the current session
2250
+ * does NOT clear local state — call `logout()` for that case.
2251
+ */
2252
+ async revokeSession(familyId) {
2253
+ if (!isBrowser) {
2254
+ warnServerSide("revokeSession");
2255
+ return;
2256
+ }
2257
+ if (!this._session?.token?.accessToken) {
2258
+ throw new Error("[PollarClient] revokeSession requires an authenticated session");
2259
+ }
2260
+ const { error } = await this._api.DELETE("/auth/sessions/{familyId}", {
2261
+ params: { path: { familyId } }
2262
+ });
2263
+ if (error) {
2264
+ throw new Error("[PollarClient] Failed to revoke session");
2265
+ }
2266
+ }
2267
+ // ─── Network ──────────────────────────────────────────────────────────────
2268
+ getNetwork() {
2269
+ return this._networkState.step === "connected" ? this._networkState.network : "testnet";
2270
+ }
2271
+ getNetworkState() {
2272
+ return this._networkState;
2273
+ }
2274
+ setNetwork(network) {
2275
+ this._setNetworkState({ step: "connected", network });
2276
+ }
2277
+ onNetworkStateChange(cb) {
2278
+ this._networkStateListeners.add(cb);
2279
+ cb(this._networkState);
2280
+ return () => this._networkStateListeners.delete(cb);
2281
+ }
2282
+ // ─── Transaction state ────────────────────────────────────────────────────
2283
+ getTransactionState() {
2284
+ return this._transactionState;
2285
+ }
2286
+ onTransactionStateChange(cb) {
2287
+ this._transactionStateListeners.add(cb);
2288
+ if (this._transactionState) cb(this._transactionState);
2289
+ return () => this._transactionStateListeners.delete(cb);
2290
+ }
2291
+ // ─── Tx history ──────────────────────────────────────────────────────────
2292
+ getTxHistoryState() {
2293
+ return this._txHistoryState;
2294
+ }
2295
+ onTxHistoryStateChange(cb) {
2296
+ this._txHistoryStateListeners.add(cb);
2297
+ cb(this._txHistoryState);
2298
+ return () => this._txHistoryStateListeners.delete(cb);
2299
+ }
2300
+ async fetchTxHistory(params = {}) {
2301
+ this._setTxHistoryState({ step: "loading", params });
2302
+ try {
2303
+ const { data, error } = await this._api.GET("/tx/history", { params: { query: params } });
2304
+ if (!error && data?.success && data.content) {
2305
+ this._setTxHistoryState({ step: "loaded", params, data: data.content });
2306
+ } else {
2307
+ const message = error?.message ?? "Failed to load history";
2308
+ this._setTxHistoryState({ step: "error", params, message });
2309
+ }
2310
+ } catch {
2311
+ this._setTxHistoryState({ step: "error", params, message: "Failed to load history" });
2312
+ }
2313
+ }
2314
+ // ─── Wallet balance ───────────────────────────────────────────────────────
2315
+ getWalletBalanceState() {
2316
+ return this._walletBalanceState;
2317
+ }
2318
+ onWalletBalanceStateChange(cb) {
2319
+ this._walletBalanceStateListeners.add(cb);
2320
+ cb(this._walletBalanceState);
2321
+ return () => this._walletBalanceStateListeners.delete(cb);
2322
+ }
2323
+ async refreshBalance(publicKey) {
2324
+ const pk = publicKey ?? this._session?.wallet?.publicKey;
2325
+ if (!pk) {
2326
+ this._setWalletBalanceState({ step: "error", message: "No wallet connected" });
2327
+ return;
2328
+ }
2329
+ this._setWalletBalanceState({ step: "loading" });
2330
+ try {
2331
+ const network = this.getNetwork();
2332
+ const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
2333
+ if (!error && data?.success && data.content) {
2334
+ this._setWalletBalanceState({ step: "loaded", data: data.content });
2335
+ } else {
2336
+ this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
2337
+ }
2338
+ } catch {
2339
+ this._setWalletBalanceState({ step: "error", message: "Failed to load balance" });
2340
+ }
2341
+ }
2342
+ // ─── Transactions ─────────────────────────────────────────────────────────
2343
+ async buildTx(operation, params, options) {
2344
+ if (!this._session?.wallet?.publicKey) {
2345
+ this._setTransactionState({ step: "error", details: "No wallet connected" });
2346
+ return;
2347
+ }
2348
+ const body = {
2349
+ network: this.getNetwork(),
2350
+ publicKey: this._session.wallet.publicKey,
2351
+ operation,
2352
+ params,
2353
+ options: options ?? {}
2354
+ };
2355
+ try {
2356
+ this._setTransactionState({ step: "building" });
2357
+ const { data, error } = await this._api.POST("/tx/build", { body });
2358
+ if (!error && data?.success && data.content) {
2359
+ this._setTransactionState({ step: "built", buildData: data.content });
2360
+ } else {
2361
+ const details = error?.details;
2362
+ this._setTransactionState({ step: "error", ...details && { details } });
2363
+ }
2364
+ } catch {
2365
+ this._setTransactionState({ step: "error" });
2366
+ }
2367
+ }
2368
+ getWalletType() {
2369
+ return this._walletAdapter?.type ?? null;
2370
+ }
2371
+ async signAndSubmitTx(unsignedXdr) {
2372
+ const state = this._transactionState;
2373
+ const buildData = state?.step === "built" ? state.buildData : state?.step === "error" ? state.buildData : void 0;
2374
+ const stateExtra = buildData ? { buildData } : { external: true };
2375
+ this._setTransactionState({ step: "signing", ...stateExtra });
2376
+ const accountToSign = this._session?.wallet?.publicKey;
2377
+ if (this._walletAdapter) {
2378
+ try {
2379
+ const signOpts = accountToSign ? { networkPassphrase: this._networkPassphrase(), accountToSign } : { networkPassphrase: this._networkPassphrase() };
2380
+ const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
2381
+ const stellarClient = new StellarClient(this.getNetwork());
2382
+ const result = await stellarClient.submitTransaction(signedTxXdr);
2383
+ if (result.success) {
2384
+ this._setTransactionState({ step: "success", ...stateExtra, hash: result.hash });
2385
+ } else {
2386
+ this._setTransactionState({ step: "error", ...stateExtra, details: result.errorCode });
2387
+ }
2388
+ } catch {
2389
+ this._setTransactionState({ step: "error", ...stateExtra });
2390
+ }
2391
+ return;
2392
+ }
2393
+ const body = {
2394
+ network: this.getNetwork(),
2395
+ publicKey: this._session?.wallet?.publicKey ?? "",
2396
+ unsignedXdr
2397
+ };
2398
+ try {
2399
+ const { data, error } = await this._api.POST("/tx/sign-and-send", { body });
2400
+ if (!error && data?.success && data.content?.hash) {
2401
+ this._setTransactionState({ step: "success", ...stateExtra, hash: data.content.hash });
2402
+ } else {
2403
+ const details = error?.details;
2404
+ this._setTransactionState({ step: "error", ...stateExtra, ...details && { details } });
2405
+ }
2406
+ } catch {
2407
+ this._setTransactionState({ step: "error", ...stateExtra });
2408
+ }
2409
+ }
2410
+ // ─── App config ───────────────────────────────────────────────────────────
2411
+ async getAppConfig() {
2412
+ try {
2413
+ const { data, error } = await this._api.GET("/applications/config");
2414
+ if (!data || error) return null;
2415
+ return data.content;
2416
+ } catch {
2417
+ return null;
2418
+ }
2419
+ }
2420
+ // ─── KYC ──────────────────────────────────────────────────────────────────
2421
+ getKycStatus(providerId) {
2422
+ return getKycStatus(this._api, providerId);
2423
+ }
2424
+ getKycProviders(country) {
2425
+ return getKycProviders(this._api, country);
2426
+ }
2427
+ startKyc(body) {
2428
+ return startKyc(this._api, body);
2429
+ }
2430
+ resolveKyc(providerId, level) {
2431
+ return resolveKyc(this._api, providerId, level);
2432
+ }
2433
+ pollKycStatus(providerId, opts) {
2434
+ return pollKycStatus(this._api, providerId, opts);
2435
+ }
2436
+ // ─── Ramps ────────────────────────────────────────────────────────────────
2437
+ getRampsQuote(query) {
2438
+ return getRampsQuote(this._api, query);
2439
+ }
2440
+ createOnRamp(body) {
2441
+ return createOnRamp(this._api, body);
2442
+ }
2443
+ createOffRamp(body) {
2444
+ return createOffRamp(this._api, body);
2445
+ }
2446
+ getRampTransaction(txId) {
2447
+ return getRampTransaction(this._api, txId);
2448
+ }
2449
+ pollRampTransaction(txId, opts) {
2450
+ return pollRampTransaction(this._api, txId, opts);
2451
+ }
2452
+ _setTxHistoryState(next) {
2453
+ this._txHistoryState = next;
2454
+ for (const cb of this._txHistoryStateListeners) cb(next);
2455
+ }
2456
+ _setWalletBalanceState(next) {
2457
+ this._walletBalanceState = next;
2458
+ for (const cb of this._walletBalanceStateListeners) cb(next);
2459
+ }
2460
+ // ─── Private ──────────────────────────────────────────────────────────────
2461
+ _newController() {
2462
+ this._loginController?.abort();
2463
+ this._loginController = new AbortController();
2464
+ return this._loginController;
2465
+ }
2466
+ _flowDeps(signal) {
2467
+ return {
2468
+ api: this._api,
2469
+ signal,
2470
+ setAuthState: this._setAuthState.bind(this),
2471
+ storeSession: this._storeSession.bind(this),
2472
+ clearSession: this._clearSession.bind(this),
2473
+ getPublicJwk: () => this._keyManager.getPublicJwk(),
2474
+ resolveWalletAdapter: (id) => this._resolveWalletAdapter(id),
2475
+ storeWalletAdapter: async (adapter, id) => {
2476
+ this._walletAdapter = adapter;
2477
+ await writeWalletType(this._storage, this.apiKeyHash, id);
2478
+ },
2479
+ ...this._deviceLabel ? { deviceLabel: this._deviceLabel } : {}
2480
+ };
2481
+ }
2482
+ /**
2483
+ * Resolves a wallet adapter for the requested id. Uses the consumer's
2484
+ * injected `walletAdapter` resolver when present; otherwise falls back to
2485
+ * the built-in `FreighterAdapter` / `AlbedoAdapter`. Throws if the id is
2486
+ * unknown and no resolver is configured.
2487
+ */
2488
+ async _resolveWalletAdapter(id) {
2489
+ if (this._walletAdapterResolver) {
2490
+ return Promise.resolve(this._walletAdapterResolver(id));
2491
+ }
2492
+ if (id === "freighter" /* FREIGHTER */) return new FreighterAdapter();
2493
+ if (id === "albedo" /* ALBEDO */) return new AlbedoAdapter();
2494
+ throw new Error(
2495
+ `[PollarClient] No wallet adapter configured for "${id}". Pass a walletAdapter resolver in PollarClientConfig.`
2496
+ );
2497
+ }
2498
+ _handleFlowError(error) {
2499
+ if (error instanceof Error && error.name === "AbortError") {
2500
+ console.info("[PollarClient] Login cancelled");
2501
+ this._setAuthState({ step: "idle" });
2502
+ return;
2503
+ }
2504
+ console.error("[PollarClient] Unexpected error in auth flow", error);
2505
+ this._setAuthState({
2506
+ step: "error",
2507
+ previousStep: this._authState.step,
2508
+ message: "An unexpected error occurred",
2509
+ errorCode: AUTH_ERROR_CODES.UNEXPECTED_ERROR
2510
+ });
2511
+ }
2512
+ async _restoreSession() {
2513
+ this._session = await readStorage(this._storage, this.apiKeyHash);
2514
+ if (this._session) {
2515
+ const storedType = await readWalletType(this._storage, this.apiKeyHash);
2516
+ if (storedType) {
2517
+ try {
2518
+ this._walletAdapter = await this._resolveWalletAdapter(storedType);
2519
+ } catch (err) {
2520
+ console.warn("[PollarClient] Could not restore wallet adapter for stored id", { id: storedType, err });
2521
+ }
2522
+ }
2523
+ console.info("[PollarClient] Session restored from storage");
2524
+ this._setAuthState({ step: "authenticated", session: this._session });
2525
+ } else {
2526
+ console.info("[PollarClient] No session in storage");
2527
+ }
2528
+ }
2529
+ async _storeSession(session) {
2530
+ console.info("[PollarClient] Session stored");
2531
+ const persisted = {
2532
+ clientSessionId: session.clientSessionId,
2533
+ userId: session.userId ?? null,
2534
+ status: session.status,
2535
+ token: session.token,
2536
+ user: session.user,
2537
+ wallet: session.wallet
2538
+ };
2539
+ this._session = persisted;
2540
+ if (session.data) {
2541
+ this._profile = {
2542
+ mail: session.data.mail,
2543
+ first_name: session.data.first_name,
2544
+ last_name: session.data.last_name,
2545
+ avatar: session.data.avatar,
2546
+ providers: session.data.providers
2547
+ };
2548
+ }
2549
+ await writeStorage(this._storage, this.apiKeyHash, persisted);
2550
+ this._setAuthState({ step: "authenticated", session: persisted });
2551
+ }
2552
+ async _clearSession() {
2553
+ console.info("[PollarClient] Session cleared");
2554
+ this._session = null;
2555
+ this._profile = null;
2556
+ this._walletAdapter = null;
2557
+ this._dpopNonce = null;
2558
+ try {
2559
+ await this._keyManager.reset();
2560
+ } catch (err) {
2561
+ console.warn("[PollarClient] KeyManager reset failed during clearSession", err);
2562
+ }
2563
+ await removeStorage(this._storage, this.apiKeyHash);
2564
+ this._transactionState = null;
2565
+ this._setAuthState({ step: "idle" });
2566
+ }
2567
+ _networkPassphrase() {
2568
+ return this.getNetwork() === "mainnet" ? "Public Global Stellar Network ; September 2015" : "Test SDF Network ; September 2015";
2569
+ }
2570
+ _setNetworkState(next) {
2571
+ this._networkState = next;
2572
+ const label = next.step === "connected" ? next.network : next.step;
2573
+ console.info(`[PollarClient] network:${label}`);
2574
+ for (const cb of this._networkStateListeners) cb(next);
2575
+ }
2576
+ _setAuthState(next) {
2577
+ this._authState = next;
2578
+ console.info(`[PollarClient] auth:${next.step}`);
2579
+ for (const cb of this._authStateListeners) cb(next);
2580
+ }
2581
+ _setTransactionState(next) {
2582
+ this._transactionState = next;
2583
+ console.info(`[PollarClient] transaction:${next.step}`);
2584
+ for (const cb of this._transactionStateListeners) cb(next);
2585
+ }
2586
+ };
2587
+
2588
+ // src/index.rn.ts
2589
+ _setDefaultKeyManagerFactory((storage, apiKey) => {
2590
+ const subtle = globalThis.crypto?.subtle;
2591
+ if (subtle && typeof subtle.generateKey === "function" && typeof subtle.sign === "function") {
2592
+ return new WebCryptoKeyManager(apiKey);
2593
+ }
2594
+ return new NobleKeyManager(storage, apiKey);
2595
+ });
2596
+
2597
+ export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, NobleKeyManager, PollarClient, StellarClient, WalletType, WebCryptoKeyManager, buildProof, canonicalEcJwk, computeJwkThumbprint, createLocalStorageAdapter, createMemoryAdapter, createOffRamp, createOnRamp, defaultKeyManager, defaultStorage, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, normalizeHtu, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
2598
+ //# sourceMappingURL=index.rn.mjs.map
2599
+ //# sourceMappingURL=index.rn.mjs.map