@accesly/react 1.1.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,155 +1,526 @@
1
1
  'use strict';
2
2
 
3
- var core = require('@accesly/core');
4
3
  var react = require('react');
4
+ var core = require('@accesly/core');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
- // src/provider.tsx
8
- var AcceslyContext = react.createContext(null);
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ exports.AcceslyContext = void 0;
17
+ var init_context = __esm({
18
+ "src/context.ts"() {
19
+ exports.AcceslyContext = react.createContext(null);
20
+ }
21
+ });
9
22
 
10
23
  // src/config.ts
11
- var ENVIRONMENT_DEFAULTS = {
12
- dev: {
13
- apiUrl: "https://3fki7eiio5.execute-api.us-east-1.amazonaws.com/dev",
14
- cognito: {
15
- region: "us-east-1",
16
- userPoolId: "us-east-1_K2Nag1tB1",
17
- userPoolClientId: "6r64diep7pne50sender4557jt"
18
- },
19
- stellar: {
20
- networkPassphrase: "Test SDF Network ; September 2015",
21
- horizonUrl: "https://horizon-testnet.stellar.org",
22
- sorobanRpcUrl: "https://soroban-testnet.stellar.org",
23
- // OZ Relayer channels-fund — see CloudServices-accesly/docs/Deployed_Resources_dev.md
24
- deployerAddress: "GDRHSVLY3VCEHCHCSR5MZR2ALYLCERDDFT3ULCUIELGFVYHTZFCMNU4E",
25
- // accesly-contracts Phase 1 deploy on Stellar testnet.
26
- ed25519VerifierAddress: "CALVIIGIOMODZMWTMKZLSD4PZFFEPWQBSYERHUFM6MH5FLWKCHW4E4G5"
24
+ exports.ENVIRONMENT_DEFAULTS = void 0;
25
+ var init_config = __esm({
26
+ "src/config.ts"() {
27
+ exports.ENVIRONMENT_DEFAULTS = {
28
+ dev: {
29
+ apiUrl: "https://3fki7eiio5.execute-api.us-east-1.amazonaws.com/dev",
30
+ walletStreamUrl: "https://ajlmn37thw7fxen3oyykbfmlrm0eecue.lambda-url.us-east-1.on.aws/",
31
+ cognito: {
32
+ region: "us-east-1",
33
+ userPoolId: "us-east-1_K2Nag1tB1",
34
+ userPoolClientId: "6r64diep7pne50sender4557jt"
35
+ },
36
+ stellar: {
37
+ networkPassphrase: "Test SDF Network ; September 2015",
38
+ horizonUrl: "https://horizon-testnet.stellar.org",
39
+ sorobanRpcUrl: "https://soroban-testnet.stellar.org",
40
+ // OZ Relayer channels-fund — see CloudServices-accesly/docs/Deployed_Resources_dev.md
41
+ deployerAddress: "GDRHSVLY3VCEHCHCSR5MZR2ALYLCERDDFT3ULCUIELGFVYHTZFCMNU4E",
42
+ // accesly-contracts Phase 1 deploy on Stellar testnet.
43
+ ed25519VerifierAddress: "CALVIIGIOMODZMWTMKZLSD4PZFFEPWQBSYERHUFM6MH5FLWKCHW4E4G5"
44
+ }
45
+ },
46
+ staging: {
47
+ apiUrl: "https://api-staging.accesly.xyz",
48
+ walletStreamUrl: "",
49
+ cognito: {
50
+ region: "us-east-1",
51
+ userPoolId: "TBD-staging",
52
+ userPoolClientId: "TBD-staging"
53
+ },
54
+ stellar: {
55
+ networkPassphrase: "Test SDF Network ; September 2015",
56
+ horizonUrl: "https://horizon-testnet.stellar.org",
57
+ sorobanRpcUrl: "https://soroban-testnet.stellar.org",
58
+ deployerAddress: "GDRHSVLY3VCEHCHCSR5MZR2ALYLCERDDFT3ULCUIELGFVYHTZFCMNU4E",
59
+ ed25519VerifierAddress: "CALVIIGIOMODZMWTMKZLSD4PZFFEPWQBSYERHUFM6MH5FLWKCHW4E4G5"
60
+ }
61
+ },
62
+ prod: {
63
+ apiUrl: "https://api.accesly.xyz",
64
+ walletStreamUrl: "",
65
+ cognito: {
66
+ region: "us-east-1",
67
+ userPoolId: "TBD-prod",
68
+ userPoolClientId: "TBD-prod"
69
+ },
70
+ stellar: {
71
+ networkPassphrase: "Public Global Stellar Network ; September 2015",
72
+ horizonUrl: "https://horizon.stellar.org",
73
+ sorobanRpcUrl: "https://soroban-rpc.mainnet.stellar.org",
74
+ deployerAddress: "TBD-prod",
75
+ ed25519VerifierAddress: "TBD-prod"
76
+ }
77
+ }
78
+ };
79
+ }
80
+ });
81
+ function isSorobanDeployPendingError(err) {
82
+ if (!(err instanceof core.AccesslyApiError)) return false;
83
+ const haystack = `${err.message ?? ""} ${err.code ?? ""}`.toLowerCase();
84
+ return haystack.includes("txsorobaninvalid") || haystack.includes("soroban sendtransaction") || haystack.includes("soroban submit failed") || haystack.includes("scecexceededlimit") || haystack.includes("exceededlimit");
85
+ }
86
+ var init_sorobanDeployStatus = __esm({
87
+ "src/hooks/sorobanDeployStatus.ts"() {
88
+ }
89
+ });
90
+
91
+ // src/hooks/stellarExpert.ts
92
+ async function fetchContractEvents(contractId, opts) {
93
+ const params = new URLSearchParams();
94
+ params.set("limit", String(opts.limit ?? 50));
95
+ params.set("order", opts.order ?? "desc");
96
+ if (opts.cursor) params.set("cursor", opts.cursor);
97
+ if (opts.topics) params.set("topics", opts.topics);
98
+ const url = `${SE_BASE}/${opts.network}/contract/${contractId}/events?${params.toString()}`;
99
+ const res = await fetch(url, opts.signal ? { signal: opts.signal } : {});
100
+ if (!res.ok) {
101
+ throw new Error(`StellarExpert ${res.status} ${res.statusText}`);
102
+ }
103
+ return await res.json();
104
+ }
105
+ async function fetchContractMeta(contractId, network, signal) {
106
+ const url = `${SE_BASE}/${network}/contract/${contractId}`;
107
+ try {
108
+ const res = await fetch(url, signal ? { signal } : {});
109
+ if (!res.ok) return null;
110
+ return await res.json();
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+ function decodeTransferAmount(bodyXdr) {
116
+ if (!bodyXdr) return "0";
117
+ try {
118
+ const bytes = base64ToBytes(bodyXdr);
119
+ if (bytes.length < 20) return "0";
120
+ let hi = 0n;
121
+ for (let i = 8; i < 16; i += 1) hi = hi << 8n | BigInt(bytes[i] ?? 0);
122
+ let lo = 0n;
123
+ for (let i = 16; i < 24; i += 1) lo = lo << 8n | BigInt(bytes[i] ?? 0);
124
+ const value = hi << 64n | lo;
125
+ return value.toString();
126
+ } catch {
127
+ return "0";
128
+ }
129
+ }
130
+ function base64ToBytes(s) {
131
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(s, "base64"));
132
+ const bin = globalThis.atob(s);
133
+ const arr = new Uint8Array(bin.length);
134
+ for (let i = 0; i < bin.length; i += 1) arr[i] = bin.charCodeAt(i);
135
+ return arr;
136
+ }
137
+ function decodeSEEvent(ev, walletAddress, xlmSac, txHashByEventId) {
138
+ const t0 = ev.topics[0];
139
+ if (!t0) return null;
140
+ const timestamp = new Date(ev.ts * 1e3).toISOString();
141
+ const txHash = txHashByEventId.get(ev.id) ?? ev.id.split("-")[0] ?? "";
142
+ if (ev.contract === walletAddress) {
143
+ if (t0 === "signer_rotated") {
144
+ return {
145
+ type: "signer-rotated",
146
+ txHash,
147
+ ledger: ev.ts,
148
+ // SE no expone ledger directamente — usamos timestamp
149
+ timestamp,
150
+ newOwnerEd25519Hex: ""
151
+ };
27
152
  }
28
- },
29
- staging: {
30
- apiUrl: "https://api-staging.accesly.xyz",
31
- cognito: {
32
- region: "us-east-1",
33
- userPoolId: "TBD-staging",
34
- userPoolClientId: "TBD-staging"
35
- },
36
- stellar: {
37
- networkPassphrase: "Test SDF Network ; September 2015",
38
- horizonUrl: "https://horizon-testnet.stellar.org",
39
- sorobanRpcUrl: "https://soroban-testnet.stellar.org",
40
- deployerAddress: "GDRHSVLY3VCEHCHCSR5MZR2ALYLCERDDFT3ULCUIELGFVYHTZFCMNU4E",
41
- ed25519VerifierAddress: "CALVIIGIOMODZMWTMKZLSD4PZFFEPWQBSYERHUFM6MH5FLWKCHW4E4G5"
153
+ return null;
154
+ }
155
+ if (ev.contract === xlmSac && t0 === "transfer") {
156
+ const from = ev.topics[1];
157
+ const to = ev.topics[2];
158
+ if (!from || !to) return null;
159
+ const amountStroops = decodeTransferAmount(ev.bodyXdr);
160
+ if (from === walletAddress && to !== walletAddress) {
161
+ return {
162
+ type: "transfer-out",
163
+ txHash,
164
+ ledger: ev.ts,
165
+ timestamp,
166
+ to,
167
+ amountStroops
168
+ };
42
169
  }
43
- },
44
- prod: {
45
- apiUrl: "https://api.accesly.xyz",
46
- cognito: {
47
- region: "us-east-1",
48
- userPoolId: "TBD-prod",
49
- userPoolClientId: "TBD-prod"
50
- },
51
- stellar: {
52
- networkPassphrase: "Public Global Stellar Network ; September 2015",
53
- horizonUrl: "https://horizon.stellar.org",
54
- sorobanRpcUrl: "https://soroban-rpc.mainnet.stellar.org",
55
- deployerAddress: "TBD-prod",
56
- ed25519VerifierAddress: "TBD-prod"
170
+ if (to === walletAddress && from !== walletAddress) {
171
+ return {
172
+ type: "transfer-in",
173
+ txHash,
174
+ ledger: ev.ts,
175
+ timestamp,
176
+ from,
177
+ amountStroops
178
+ };
57
179
  }
58
180
  }
59
- };
60
- function AcceslyProvider(props) {
61
- const defaults = ENVIRONMENT_DEFAULTS[props.env];
62
- const apiUrl = props.apiUrl ?? defaults.apiUrl;
63
- const cognitoConfig = props.cognitoConfig ?? defaults.cognito;
64
- const telemetry = props.telemetry;
65
- const instances = react.useMemo(() => {
66
- const authClient = props.overrides?.authClient ?? new core.CognitoAuthClient(cognitoConfig);
67
- const sessionStorage = props.overrides?.sessionStorage ?? core.defaultSessionStorage();
68
- const deviceStore = props.overrides?.deviceStore ?? new core.InMemoryDeviceStore();
69
- const tokenManager = new core.TokenManager({ authClient, storage: sessionStorage });
70
- const apiClient = new core.AccesslyApiClient({
71
- baseUrl: apiUrl,
72
- getIdToken: () => tokenManager.getValidIdToken(),
73
- ...telemetry ? { telemetry } : {}
74
- });
75
- const endpoints = new core.AccesslyEndpoints(apiClient);
76
- return { authClient, sessionStorage, deviceStore, tokenManager, endpoints };
77
- }, [
78
- apiUrl,
79
- cognitoConfig.region,
80
- cognitoConfig.userPoolId,
81
- cognitoConfig.userPoolClientId,
82
- props.overrides?.authClient,
83
- props.overrides?.sessionStorage,
84
- props.overrides?.deviceStore
181
+ return null;
182
+ }
183
+ async function fetchWalletHistory(walletAddress, opts) {
184
+ const xlmSac = opts.network === "mainnet" ? XLM_SAC_MAINNET : XLM_SAC_TESTNET;
185
+ const limit = 50;
186
+ const transferLimit = opts.transferScanLimit ?? 50;
187
+ const [smartAccountResp, transfersResp] = await Promise.all([
188
+ fetchContractEvents(walletAddress, {
189
+ network: opts.network,
190
+ limit,
191
+ order: "desc",
192
+ ...opts.smartAccountCursor ? { cursor: opts.smartAccountCursor } : {},
193
+ ...opts.signal ? { signal: opts.signal } : {}
194
+ }).catch((err) => {
195
+ console.warn("[stellarExpert] smart account events failed", err);
196
+ return { _embedded: { records: [] } };
197
+ }),
198
+ fetchContractEvents(xlmSac, {
199
+ network: opts.network,
200
+ limit: transferLimit,
201
+ order: "desc",
202
+ topics: "transfer",
203
+ ...opts.signal ? { signal: opts.signal } : {}
204
+ }).catch((err) => {
205
+ console.warn("[stellarExpert] xlm sac events failed", err);
206
+ return { _embedded: { records: [] } };
207
+ })
85
208
  ]);
86
- const [status, setStatus] = react.useState(() => initialStatus(instances.sessionStorage));
87
- const [username, setUsername] = react.useState(
88
- () => initialUsername(instances.sessionStorage)
89
- );
90
- const mountedRef = react.useRef(true);
91
- const refreshStatus = react.useCallback(async () => {
92
- const next = await instances.tokenManager.getStatus();
93
- const tokens = await Promise.resolve(instances.sessionStorage.load());
94
- if (mountedRef.current) {
95
- setStatus(next);
96
- setUsername(tokens?.username ?? null);
209
+ const txHashByEventId = /* @__PURE__ */ new Map();
210
+ const items = [];
211
+ const saRecords = smartAccountResp._embedded?.records ?? [];
212
+ const txRecords = transfersResp._embedded?.records ?? [];
213
+ for (const ev of saRecords) {
214
+ const decoded = decodeSEEvent(ev, walletAddress, xlmSac, txHashByEventId);
215
+ if (decoded) items.push(decoded);
216
+ }
217
+ for (const ev of txRecords) {
218
+ const decoded = decodeSEEvent(ev, walletAddress, xlmSac, txHashByEventId);
219
+ if (decoded) items.push(decoded);
220
+ }
221
+ if (!opts.smartAccountCursor) {
222
+ const meta = await fetchContractMeta(walletAddress, opts.network, opts.signal);
223
+ if (meta) {
224
+ items.push({
225
+ type: "wallet-created",
226
+ txHash: "",
227
+ ledger: meta.created,
228
+ timestamp: new Date(meta.created * 1e3).toISOString()
229
+ });
97
230
  }
98
- }, [instances]);
231
+ }
232
+ items.sort((a, b) => (b.ledger ?? 0) - (a.ledger ?? 0));
233
+ return {
234
+ items,
235
+ cursors: {
236
+ smartAccount: extractCursor(smartAccountResp),
237
+ transfers: extractCursor(transfersResp)
238
+ }
239
+ };
240
+ }
241
+ function extractCursor(resp) {
242
+ const records = resp._embedded?.records;
243
+ if (!records || records.length === 0) return null;
244
+ return records[records.length - 1]?.paging_token ?? records[records.length - 1]?.id ?? null;
245
+ }
246
+ var SE_BASE, XLM_SAC_TESTNET, XLM_SAC_MAINNET;
247
+ var init_stellarExpert = __esm({
248
+ "src/hooks/stellarExpert.ts"() {
249
+ SE_BASE = "https://api.stellar.expert/explorer";
250
+ XLM_SAC_TESTNET = "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC";
251
+ XLM_SAC_MAINNET = "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA";
252
+ }
253
+ });
254
+
255
+ // src/hooks/useWalletHistory.ts
256
+ var useWalletHistory_exports = {};
257
+ __export(useWalletHistory_exports, {
258
+ historyClearOptimistic: () => historyClearOptimistic,
259
+ historyOptimisticPush: () => historyOptimisticPush,
260
+ useWalletHistory: () => useWalletHistory
261
+ });
262
+ function useStableRef(value) {
263
+ const ref = react.useRef(value);
264
+ ref.current = value;
265
+ return ref;
266
+ }
267
+ function loadCache(walletAddress) {
268
+ if (typeof localStorage === "undefined") return null;
269
+ try {
270
+ const raw = localStorage.getItem(CACHE_KEY_PREFIX + walletAddress);
271
+ if (!raw) return null;
272
+ const parsed = JSON.parse(raw);
273
+ if (Date.now() - parsed.storedAt > CACHE_TTL_MS) return null;
274
+ return parsed;
275
+ } catch {
276
+ return null;
277
+ }
278
+ }
279
+ function saveCache(walletAddress, entry) {
280
+ if (typeof localStorage === "undefined") return;
281
+ try {
282
+ localStorage.setItem(CACHE_KEY_PREFIX + walletAddress, JSON.stringify(entry));
283
+ } catch {
284
+ }
285
+ }
286
+ function historyOptimisticPush(walletAddress, item) {
287
+ const current = optimisticItems.get(walletAddress) ?? [];
288
+ optimisticItems.set(walletAddress, [item, ...current]);
289
+ const listeners = optimisticListeners.get(walletAddress);
290
+ if (listeners) {
291
+ for (const fn of listeners) fn(optimisticItems.get(walletAddress) ?? []);
292
+ }
293
+ }
294
+ function historyClearOptimistic(walletAddress) {
295
+ optimisticItems.delete(walletAddress);
296
+ const listeners = optimisticListeners.get(walletAddress);
297
+ if (listeners) {
298
+ for (const fn of listeners) fn([]);
299
+ }
300
+ }
301
+ function subscribeOptimistic(walletAddress, listener) {
302
+ let set = optimisticListeners.get(walletAddress);
303
+ if (!set) {
304
+ set = /* @__PURE__ */ new Set();
305
+ optimisticListeners.set(walletAddress, set);
306
+ }
307
+ set.add(listener);
308
+ return () => {
309
+ set?.delete(listener);
310
+ if (set && set.size === 0) optimisticListeners.delete(walletAddress);
311
+ };
312
+ }
313
+ function useWalletHistory(walletAddress, opts = {}) {
314
+ const { wallet, _internal } = useAccesly();
315
+ const username = _internal.username;
316
+ const network = opts.network ?? inferNetwork(_internal.env);
317
+ const [resolvedAddress, setResolvedAddress] = react.useState(walletAddress ?? null);
318
+ const [events, setEvents] = react.useState([]);
319
+ const [optimistic, setOptimistic] = react.useState([]);
320
+ const [cursors, setCursors] = react.useState({
321
+ smartAccount: null,
322
+ transfers: null
323
+ });
324
+ const [isLoading, setIsLoading] = react.useState(true);
325
+ const [error, setError] = react.useState(null);
326
+ const [hasMore, setHasMore] = react.useState(true);
327
+ const walletRef = useStableRef(wallet);
99
328
  react.useEffect(() => {
100
- mountedRef.current = true;
101
- void refreshStatus();
329
+ if (walletAddress) {
330
+ setResolvedAddress(walletAddress);
331
+ return;
332
+ }
333
+ if (!username) {
334
+ setResolvedAddress(null);
335
+ return;
336
+ }
337
+ let cancelled = false;
338
+ void (async () => {
339
+ try {
340
+ const stored = await walletRef.current.getStoredCredential(username);
341
+ if (cancelled) return;
342
+ setResolvedAddress(stored?.walletAddress ?? null);
343
+ } catch {
344
+ if (!cancelled) setResolvedAddress(null);
345
+ }
346
+ })();
102
347
  return () => {
103
- mountedRef.current = false;
348
+ cancelled = true;
104
349
  };
105
- }, [instances]);
106
- const value = react.useMemo(
107
- () => ({
108
- appId: props.appId,
109
- env: props.env,
110
- apiUrl,
111
- cognitoConfig,
112
- authClient: instances.authClient,
113
- sessionStorage: instances.sessionStorage,
114
- tokenManager: instances.tokenManager,
115
- endpoints: instances.endpoints,
116
- deviceStore: instances.deviceStore,
117
- status,
118
- username,
119
- refreshStatus
120
- }),
121
- [props.appId, props.env, apiUrl, cognitoConfig, instances, status, username, refreshStatus]
122
- );
123
- return /* @__PURE__ */ jsxRuntime.jsx(AcceslyContext.Provider, { value, children: props.children });
350
+ }, [walletAddress, username, walletRef]);
351
+ react.useEffect(() => {
352
+ if (!resolvedAddress) return void 0;
353
+ setOptimistic(optimisticItems.get(resolvedAddress) ?? []);
354
+ return subscribeOptimistic(resolvedAddress, setOptimistic);
355
+ }, [resolvedAddress]);
356
+ react.useEffect(() => {
357
+ if (!resolvedAddress) {
358
+ setIsLoading(false);
359
+ return void 0;
360
+ }
361
+ const cached = loadCache(resolvedAddress);
362
+ if (cached) {
363
+ setEvents(cached.items);
364
+ setCursors(cached.cursors);
365
+ setIsLoading(false);
366
+ }
367
+ let cancelled = false;
368
+ const channel = typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(BROADCAST_CHANNEL_PREFIX + resolvedAddress) : null;
369
+ if (channel) {
370
+ channel.onmessage = (ev) => {
371
+ const data = ev.data;
372
+ if (data && !cancelled) {
373
+ setEvents(data.items);
374
+ setCursors(data.cursors);
375
+ setIsLoading(false);
376
+ }
377
+ };
378
+ }
379
+ void (async () => {
380
+ try {
381
+ const result = await fetchWalletHistory(resolvedAddress, {
382
+ network,
383
+ ...opts.transferScanLimit ? { transferScanLimit: opts.transferScanLimit } : {}
384
+ });
385
+ if (cancelled) return;
386
+ const deduped = dedupItems(result.items);
387
+ setEvents(deduped);
388
+ setCursors(result.cursors);
389
+ setIsLoading(false);
390
+ setError(null);
391
+ setHasMore(result.cursors.smartAccount !== null || result.cursors.transfers !== null);
392
+ const entry = {
393
+ items: deduped,
394
+ cursors: result.cursors,
395
+ storedAt: Date.now()
396
+ };
397
+ saveCache(resolvedAddress, entry);
398
+ channel?.postMessage(entry);
399
+ } catch (err) {
400
+ if (!cancelled) {
401
+ setError(err);
402
+ setIsLoading(false);
403
+ }
404
+ }
405
+ })();
406
+ return () => {
407
+ cancelled = true;
408
+ channel?.close();
409
+ };
410
+ }, [resolvedAddress, network, opts.transferScanLimit]);
411
+ const interval = opts.pollIntervalMs ?? POLL_INTERVAL_MS;
412
+ react.useEffect(() => {
413
+ if (!resolvedAddress || interval === 0) return void 0;
414
+ const tick = async () => {
415
+ if (typeof document !== "undefined" && document.hidden) return;
416
+ try {
417
+ const result = await fetchWalletHistory(resolvedAddress, {
418
+ network,
419
+ ...opts.transferScanLimit ? { transferScanLimit: opts.transferScanLimit } : {}
420
+ });
421
+ setEvents((prev) => mergeAndDedup(prev, result.items));
422
+ if (resolvedAddress) {
423
+ const realTxHashes = new Set(result.items.map((it) => it.txHash));
424
+ const optimisticsRemaining = (optimisticItems.get(resolvedAddress) ?? []).filter(
425
+ (it) => !realTxHashes.has(it.txHash)
426
+ );
427
+ if (optimisticsRemaining.length !== (optimisticItems.get(resolvedAddress)?.length ?? 0)) {
428
+ optimisticItems.set(resolvedAddress, optimisticsRemaining);
429
+ const listeners = optimisticListeners.get(resolvedAddress);
430
+ if (listeners) for (const fn of listeners) fn(optimisticsRemaining);
431
+ }
432
+ }
433
+ } catch {
434
+ }
435
+ };
436
+ const id = setInterval(tick, interval);
437
+ return () => clearInterval(id);
438
+ }, [resolvedAddress, network, interval]);
439
+ const loadMoreImpl = react.useCallback(async () => {
440
+ if (!resolvedAddress) return;
441
+ if (!cursors.smartAccount && !cursors.transfers) {
442
+ setHasMore(false);
443
+ return;
444
+ }
445
+ try {
446
+ const result = await fetchWalletHistory(resolvedAddress, {
447
+ network,
448
+ ...cursors.smartAccount ? { smartAccountCursor: cursors.smartAccount } : {},
449
+ ...opts.transferScanLimit ? { transferScanLimit: opts.transferScanLimit } : {}
450
+ });
451
+ setEvents((prev) => mergeAndDedup(prev, result.items));
452
+ setCursors(result.cursors);
453
+ setHasMore(result.cursors.smartAccount !== null || result.cursors.transfers !== null);
454
+ } catch (err) {
455
+ setError(err);
456
+ }
457
+ }, [resolvedAddress, network, cursors, opts.transferScanLimit]);
458
+ const refreshImpl = react.useCallback(async () => {
459
+ if (!resolvedAddress) return;
460
+ setIsLoading(true);
461
+ try {
462
+ const result = await fetchWalletHistory(resolvedAddress, {
463
+ network,
464
+ ...opts.transferScanLimit ? { transferScanLimit: opts.transferScanLimit } : {}
465
+ });
466
+ const deduped = dedupItems(result.items);
467
+ setEvents(deduped);
468
+ setCursors(result.cursors);
469
+ setError(null);
470
+ saveCache(resolvedAddress, {
471
+ items: deduped,
472
+ cursors: result.cursors,
473
+ storedAt: Date.now()
474
+ });
475
+ } catch (err) {
476
+ setError(err);
477
+ } finally {
478
+ setIsLoading(false);
479
+ }
480
+ }, [resolvedAddress, network, opts.transferScanLimit]);
481
+ const combined = [...optimistic, ...events];
482
+ return {
483
+ events: combined,
484
+ isLoading,
485
+ error,
486
+ hasMore,
487
+ loadMore: loadMoreImpl,
488
+ refresh: refreshImpl
489
+ };
124
490
  }
125
- function initialStatus(storage) {
126
- const tokens = storage.load();
127
- if (tokens instanceof Promise) return "bootstrapping";
128
- if (!tokens) return "anonymous";
129
- return Date.now() + 5 * 60 * 1e3 >= tokens.expiresAt ? "expired" : "authenticated";
491
+ function inferNetwork(env) {
492
+ return env === "prod" ? "mainnet" : "testnet";
130
493
  }
131
- function initialUsername(storage) {
132
- const tokens = storage.load();
133
- if (tokens instanceof Promise) return null;
134
- return tokens?.username ?? null;
494
+ function dedupItems(items) {
495
+ const seen = /* @__PURE__ */ new Set();
496
+ const out = [];
497
+ for (const item of items) {
498
+ const key = `${item.type}:${item.txHash}:${item.ledger}`;
499
+ if (seen.has(key)) continue;
500
+ seen.add(key);
501
+ out.push(item);
502
+ }
503
+ out.sort((a, b) => (b.ledger ?? 0) - (a.ledger ?? 0));
504
+ return out;
135
505
  }
136
- function isSorobanDeployPendingError(err) {
137
- if (!(err instanceof core.AccesslyApiError)) return false;
138
- const haystack = `${err.message ?? ""} ${err.code ?? ""}`.toLowerCase();
139
- return haystack.includes("txsorobaninvalid") || haystack.includes("soroban sendtransaction") || haystack.includes("soroban submit failed") || haystack.includes("scecexceededlimit") || haystack.includes("exceededlimit");
506
+ function mergeAndDedup(prev, fresh) {
507
+ return dedupItems([...fresh, ...prev]);
140
508
  }
141
-
142
- // src/hooks/useAccesly.ts
143
- var NotImplementedYetError = class extends Error {
144
- constructor(namespace, method) {
145
- super(
146
- `${namespace}.${method}() is not implemented yet. This namespace ships in a later release; see docs/Handoff_Fase7.md for the roadmap.`
147
- );
148
- this.name = "NotImplementedYetError";
509
+ var POLL_INTERVAL_MS, CACHE_TTL_MS, CACHE_KEY_PREFIX, BROADCAST_CHANNEL_PREFIX, optimisticItems, optimisticListeners;
510
+ var init_useWalletHistory = __esm({
511
+ "src/hooks/useWalletHistory.ts"() {
512
+ init_useAccesly();
513
+ init_stellarExpert();
514
+ POLL_INTERVAL_MS = 3e4;
515
+ CACHE_TTL_MS = 12 * 60 * 60 * 1e3;
516
+ CACHE_KEY_PREFIX = "accesly:history:";
517
+ BROADCAST_CHANNEL_PREFIX = "accesly:history:";
518
+ optimisticItems = /* @__PURE__ */ new Map();
519
+ optimisticListeners = /* @__PURE__ */ new Map();
149
520
  }
150
- };
521
+ });
151
522
  function useAccesly() {
152
- const ctx = react.useContext(AcceslyContext);
523
+ const ctx = react.useContext(exports.AcceslyContext);
153
524
  if (!ctx) {
154
525
  throw new Error(
155
526
  "useAccesly: missing <AcceslyProvider>. Wrap your app with <AcceslyProvider appId env>."
@@ -181,7 +552,7 @@ function useAccesly() {
181
552
  [ctx]
182
553
  );
183
554
  const { hexToBytes, hexFromBytes } = react.useMemo(() => coderHelpers(), []);
184
- const stellarConfig = ENVIRONMENT_DEFAULTS[ctx.env].stellar;
555
+ const stellarConfig = exports.ENVIRONMENT_DEFAULTS[ctx.env].stellar;
185
556
  const wallet = react.useMemo(() => {
186
557
  const c = ctx;
187
558
  const postWallet = async (params) => {
@@ -612,8 +983,8 @@ function useAccesly() {
612
983
  const sessionPlaintext = core.unwrapSessionFragment2(wrappedF2, ephemeral.privateKey).plaintext;
613
984
  const fragmentF2Wire = JSON.parse(new TextDecoder().decode(sessionPlaintext));
614
985
  const fragmentF2Envelope = {
615
- nonce: base64ToBytes(fragmentF2Wire.nonce),
616
- ciphertext: base64ToBytes(fragmentF2Wire.ciphertext)
986
+ nonce: base64ToBytes2(fragmentF2Wire.nonce),
987
+ ciphertext: base64ToBytes2(fragmentF2Wire.ciphertext)
617
988
  };
618
989
  const reconstructed = core.reconstructFromPlainAndEncrypted({
619
990
  fragmentF1Plain: input.fragmentF1Plain,
@@ -631,6 +1002,24 @@ function useAccesly() {
631
1002
  unsignedXdr: sim.unsignedXdr,
632
1003
  signedAuthEntryXdr
633
1004
  });
1005
+ try {
1006
+ const username = ctx.username;
1007
+ if (username) {
1008
+ const stored = await ctx.deviceStore.loadCredential(username);
1009
+ if (stored?.walletAddress) {
1010
+ const { historyOptimisticPush: historyOptimisticPush2 } = await Promise.resolve().then(() => (init_useWalletHistory(), useWalletHistory_exports));
1011
+ historyOptimisticPush2(stored.walletAddress, {
1012
+ type: "transfer-out",
1013
+ txHash: submit.txHash,
1014
+ ledger: Math.floor(Date.now() / 1e3),
1015
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1016
+ to: input.destinationAddress,
1017
+ amountStroops: input.amountStroops
1018
+ });
1019
+ }
1020
+ }
1021
+ } catch {
1022
+ }
634
1023
  return {
635
1024
  txHash: submit.txHash,
636
1025
  status: submit.status,
@@ -674,18 +1063,18 @@ function useAccesly() {
674
1063
  "recovery.reconstructSeed: la wallet fue creada antes de Fase 1 y no tiene F2 cipher-bound a recoveryKey. No es recuperable v\xEDa OTP."
675
1064
  );
676
1065
  }
677
- const recoverySalt = base64ToBytes(frag.recoverySalt);
1066
+ const recoverySalt = base64ToBytes2(frag.recoverySalt);
678
1067
  const recoveryKey = core.deriveRecoveryKey({
679
1068
  password: input.cognitoPassword,
680
1069
  salt: recoverySalt
681
1070
  });
682
1071
  const f2Envelope = {
683
- ciphertext: base64ToBytes(frag.fragmentF2Recovery.ciphertext),
684
- nonce: base64ToBytes(frag.fragmentF2Recovery.nonce)
1072
+ ciphertext: base64ToBytes2(frag.fragmentF2Recovery.ciphertext),
1073
+ nonce: base64ToBytes2(frag.fragmentF2Recovery.nonce)
685
1074
  };
686
1075
  const f3Envelope = {
687
- ciphertext: base64ToBytes(frag.fragmentF3Encrypted.ciphertext),
688
- nonce: base64ToBytes(frag.fragmentF3Encrypted.nonce)
1076
+ ciphertext: base64ToBytes2(frag.fragmentF3Encrypted.ciphertext),
1077
+ nonce: base64ToBytes2(frag.fragmentF3Encrypted.nonce)
689
1078
  };
690
1079
  const seedResult = core.reconstructKey({
691
1080
  fragments: [
@@ -848,10 +1237,10 @@ function useAccesly() {
848
1237
  const session = react.useMemo(
849
1238
  () => ({
850
1239
  async create() {
851
- throw new NotImplementedYetError("session", "create");
1240
+ throw new exports.NotImplementedYetError("session", "create");
852
1241
  },
853
1242
  async revoke() {
854
- throw new NotImplementedYetError("session", "revoke");
1243
+ throw new exports.NotImplementedYetError("session", "revoke");
855
1244
  }
856
1245
  }),
857
1246
  []
@@ -859,16 +1248,16 @@ function useAccesly() {
859
1248
  const settings = react.useMemo(
860
1249
  () => ({
861
1250
  async addDevice() {
862
- throw new NotImplementedYetError("settings", "addDevice");
1251
+ throw new exports.NotImplementedYetError("settings", "addDevice");
863
1252
  },
864
1253
  async removeDevice() {
865
- throw new NotImplementedYetError("settings", "removeDevice");
1254
+ throw new exports.NotImplementedYetError("settings", "removeDevice");
866
1255
  },
867
1256
  async listDevices() {
868
- throw new NotImplementedYetError("settings", "listDevices");
1257
+ throw new exports.NotImplementedYetError("settings", "listDevices");
869
1258
  },
870
1259
  async updateSpendingLimit() {
871
- throw new NotImplementedYetError("settings", "updateSpendingLimit");
1260
+ throw new exports.NotImplementedYetError("settings", "updateSpendingLimit");
872
1261
  }
873
1262
  }),
874
1263
  []
@@ -876,13 +1265,13 @@ function useAccesly() {
876
1265
  const yieldOps = react.useMemo(
877
1266
  () => ({
878
1267
  async invest() {
879
- throw new NotImplementedYetError("yield", "invest");
1268
+ throw new exports.NotImplementedYetError("yield", "invest");
880
1269
  },
881
1270
  async redeem() {
882
- throw new NotImplementedYetError("yield", "redeem");
1271
+ throw new exports.NotImplementedYetError("yield", "redeem");
883
1272
  },
884
1273
  async position() {
885
- throw new NotImplementedYetError("yield", "position");
1274
+ throw new exports.NotImplementedYetError("yield", "position");
886
1275
  }
887
1276
  }),
888
1277
  []
@@ -921,15 +1310,245 @@ function base64FromBytes(bytes) {
921
1310
  for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i] ?? 0);
922
1311
  return globalThis.btoa(bin);
923
1312
  }
924
- function base64ToBytes(s) {
1313
+ function base64ToBytes2(s) {
925
1314
  if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(s, "base64"));
926
1315
  const bin = globalThis.atob(s);
927
1316
  const arr = new Uint8Array(bin.length);
928
1317
  for (let i = 0; i < bin.length; i += 1) arr[i] = bin.charCodeAt(i);
929
1318
  return arr;
930
1319
  }
1320
+ exports.NotImplementedYetError = void 0;
1321
+ var init_useAccesly = __esm({
1322
+ "src/hooks/useAccesly.ts"() {
1323
+ init_context();
1324
+ init_config();
1325
+ init_sorobanDeployStatus();
1326
+ exports.NotImplementedYetError = class extends Error {
1327
+ constructor(namespace, method) {
1328
+ super(
1329
+ `${namespace}.${method}() is not implemented yet. This namespace ships in a later release; see docs/Handoff_Fase7.md for the roadmap.`
1330
+ );
1331
+ this.name = "NotImplementedYetError";
1332
+ }
1333
+ };
1334
+ }
1335
+ });
1336
+
1337
+ // src/provider.tsx
1338
+ init_context();
1339
+ init_config();
1340
+ function AcceslyProvider(props) {
1341
+ const defaults = exports.ENVIRONMENT_DEFAULTS[props.env];
1342
+ const apiUrl = props.apiUrl ?? defaults.apiUrl;
1343
+ const cognitoConfig = props.cognitoConfig ?? defaults.cognito;
1344
+ const telemetry = props.telemetry;
1345
+ const instances = react.useMemo(() => {
1346
+ const authClient = props.overrides?.authClient ?? new core.CognitoAuthClient(cognitoConfig);
1347
+ const sessionStorage = props.overrides?.sessionStorage ?? core.defaultSessionStorage();
1348
+ const deviceStore = props.overrides?.deviceStore ?? new core.InMemoryDeviceStore();
1349
+ const tokenManager = new core.TokenManager({ authClient, storage: sessionStorage });
1350
+ const apiClient = new core.AccesslyApiClient({
1351
+ baseUrl: apiUrl,
1352
+ getIdToken: () => tokenManager.getValidIdToken(),
1353
+ ...telemetry ? { telemetry } : {}
1354
+ });
1355
+ const endpoints = new core.AccesslyEndpoints(apiClient);
1356
+ return { authClient, sessionStorage, deviceStore, tokenManager, endpoints };
1357
+ }, [
1358
+ apiUrl,
1359
+ cognitoConfig.region,
1360
+ cognitoConfig.userPoolId,
1361
+ cognitoConfig.userPoolClientId,
1362
+ props.overrides?.authClient,
1363
+ props.overrides?.sessionStorage,
1364
+ props.overrides?.deviceStore
1365
+ ]);
1366
+ const [status, setStatus] = react.useState(() => initialStatus(instances.sessionStorage));
1367
+ const [username, setUsername] = react.useState(
1368
+ () => initialUsername(instances.sessionStorage)
1369
+ );
1370
+ const mountedRef = react.useRef(true);
1371
+ const refreshStatus = react.useCallback(async () => {
1372
+ const next = await instances.tokenManager.getStatus();
1373
+ const tokens = await Promise.resolve(instances.sessionStorage.load());
1374
+ if (mountedRef.current) {
1375
+ setStatus(next);
1376
+ setUsername(tokens?.username ?? null);
1377
+ }
1378
+ }, [instances]);
1379
+ react.useEffect(() => {
1380
+ mountedRef.current = true;
1381
+ void refreshStatus();
1382
+ return () => {
1383
+ mountedRef.current = false;
1384
+ };
1385
+ }, [instances]);
1386
+ const value = react.useMemo(
1387
+ () => ({
1388
+ appId: props.appId,
1389
+ env: props.env,
1390
+ apiUrl,
1391
+ cognitoConfig,
1392
+ authClient: instances.authClient,
1393
+ sessionStorage: instances.sessionStorage,
1394
+ tokenManager: instances.tokenManager,
1395
+ endpoints: instances.endpoints,
1396
+ deviceStore: instances.deviceStore,
1397
+ status,
1398
+ username,
1399
+ refreshStatus
1400
+ }),
1401
+ [props.appId, props.env, apiUrl, cognitoConfig, instances, status, username, refreshStatus]
1402
+ );
1403
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.AcceslyContext.Provider, { value, children: props.children });
1404
+ }
1405
+ function initialStatus(storage) {
1406
+ const tokens = storage.load();
1407
+ if (tokens instanceof Promise) return "bootstrapping";
1408
+ if (!tokens) return "anonymous";
1409
+ return Date.now() + 5 * 60 * 1e3 >= tokens.expiresAt ? "expired" : "authenticated";
1410
+ }
1411
+ function initialUsername(storage) {
1412
+ const tokens = storage.load();
1413
+ if (tokens instanceof Promise) return null;
1414
+ return tokens?.username ?? null;
1415
+ }
1416
+
1417
+ // src/index.ts
1418
+ init_context();
1419
+ init_config();
1420
+ init_useAccesly();
1421
+
1422
+ // src/hooks/useWalletStatus.ts
1423
+ init_useAccesly();
1424
+ init_config();
1425
+
1426
+ // src/hooks/walletSubscription.ts
1427
+ var ACTIVITY_BUFFER_MAX = 50;
1428
+ var subscriptions = /* @__PURE__ */ new Map();
1429
+ function buildSubscriptionUrl(streamUrl, walletAddress) {
1430
+ const base = streamUrl.replace(/\/$/, "");
1431
+ return `${base}/?walletAddress=${encodeURIComponent(walletAddress)}`;
1432
+ }
1433
+ function openConnection(state) {
1434
+ if (state.eventSource) return;
1435
+ if (typeof EventSource === "undefined") return;
1436
+ let es;
1437
+ try {
1438
+ es = new EventSource(state.url, { withCredentials: false });
1439
+ } catch (err) {
1440
+ console.warn("[walletSubscription] EventSource construction failed", err);
1441
+ return;
1442
+ }
1443
+ state.eventSource = es;
1444
+ es.addEventListener("status", (ev) => {
1445
+ try {
1446
+ const data = JSON.parse(ev.data);
1447
+ state.lastStatus = data;
1448
+ for (const listener of state.listeners.status) listener(data);
1449
+ } catch {
1450
+ }
1451
+ });
1452
+ es.addEventListener("balance", (ev) => {
1453
+ try {
1454
+ const data = JSON.parse(ev.data);
1455
+ state.lastBalance = data;
1456
+ for (const listener of state.listeners.balance) listener(data);
1457
+ } catch {
1458
+ }
1459
+ });
1460
+ es.addEventListener("activity", (ev) => {
1461
+ try {
1462
+ const data = JSON.parse(ev.data);
1463
+ const merged = [...data.events, ...state.activityBuffer];
1464
+ const seen = /* @__PURE__ */ new Set();
1465
+ const deduped = [];
1466
+ for (const item of merged) {
1467
+ const key = `${item.txHash}:${item.ledger}`;
1468
+ if (seen.has(key)) continue;
1469
+ seen.add(key);
1470
+ deduped.push(item);
1471
+ if (deduped.length >= ACTIVITY_BUFFER_MAX) break;
1472
+ }
1473
+ state.activityBuffer = deduped;
1474
+ for (const listener of state.listeners.activity) {
1475
+ listener({ events: state.activityBuffer });
1476
+ }
1477
+ } catch {
1478
+ }
1479
+ });
1480
+ es.addEventListener("close", () => {
1481
+ });
1482
+ es.onerror = () => {
1483
+ };
1484
+ }
1485
+ function closeConnection(state) {
1486
+ if (!state.eventSource) return;
1487
+ state.eventSource.close();
1488
+ state.eventSource = null;
1489
+ }
1490
+ function getOrCreateSubscription(streamUrl, walletAddress) {
1491
+ const existing = subscriptions.get(walletAddress);
1492
+ if (existing) return existing;
1493
+ const state = {
1494
+ walletAddress,
1495
+ url: buildSubscriptionUrl(streamUrl, walletAddress),
1496
+ eventSource: null,
1497
+ listeners: {
1498
+ status: /* @__PURE__ */ new Set(),
1499
+ balance: /* @__PURE__ */ new Set(),
1500
+ activity: /* @__PURE__ */ new Set()
1501
+ },
1502
+ lastStatus: null,
1503
+ lastBalance: null,
1504
+ activityBuffer: [],
1505
+ refCount: 0
1506
+ };
1507
+ subscriptions.set(walletAddress, state);
1508
+ return state;
1509
+ }
1510
+ function subscribeToWalletEvent(streamUrl, walletAddress, eventType, listener) {
1511
+ if (!streamUrl || typeof EventSource === "undefined") return null;
1512
+ const state = getOrCreateSubscription(streamUrl, walletAddress);
1513
+ state.refCount += 1;
1514
+ state.listeners[eventType].add(listener);
1515
+ if (eventType === "status" && state.lastStatus) {
1516
+ listener(state.lastStatus);
1517
+ } else if (eventType === "balance" && state.lastBalance) {
1518
+ listener(state.lastBalance);
1519
+ } else if (eventType === "activity" && state.activityBuffer.length > 0) {
1520
+ listener({
1521
+ events: state.activityBuffer
1522
+ });
1523
+ }
1524
+ if (!state.eventSource) openConnection(state);
1525
+ return () => {
1526
+ state.listeners[eventType].delete(listener);
1527
+ state.refCount -= 1;
1528
+ if (state.refCount <= 0) {
1529
+ closeConnection(state);
1530
+ subscriptions.delete(walletAddress);
1531
+ }
1532
+ };
1533
+ }
1534
+ function closeAllWalletSubscriptions() {
1535
+ for (const state of subscriptions.values()) closeConnection(state);
1536
+ subscriptions.clear();
1537
+ }
1538
+
1539
+ // src/hooks/useWalletStatus.ts
931
1540
  var POLL_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
932
1541
  var STALE_THRESHOLD_MS = 6e4;
1542
+ function useStableRef2(value) {
1543
+ const ref = react.useRef(value);
1544
+ ref.current = value;
1545
+ return ref;
1546
+ }
1547
+ function deriveStatus(onChain) {
1548
+ if (onChain === true) return "on-chain";
1549
+ if (onChain === false) return "pending-deploy";
1550
+ return "unknown";
1551
+ }
933
1552
  function useWalletStatus() {
934
1553
  const { wallet, _internal } = useAccesly();
935
1554
  const username = _internal.username;
@@ -938,147 +1557,75 @@ function useWalletStatus() {
938
1557
  const [onChain, setOnChain] = react.useState(null);
939
1558
  const [lastSuccessAt, setLastSuccessAt] = react.useState(0);
940
1559
  const [isStale, setIsStale] = react.useState(false);
941
- const refreshRef = react.useRef(() => Promise.resolve());
942
- const apply = react.useCallback((res) => {
943
- if (res.kind === "not-found") {
944
- setStatus("no-wallet");
945
- setWalletAddress(null);
946
- setOnChain(null);
947
- setLastSuccessAt(Date.now());
948
- setIsStale(false);
949
- return "no-wallet";
950
- }
951
- const next = res.onChain === true ? "on-chain" : res.onChain === false ? "pending-deploy" : "unknown";
952
- setStatus(next);
953
- setWalletAddress(res.walletAddress);
954
- setOnChain(res.onChain);
955
- setLastSuccessAt(Date.now());
956
- setIsStale(false);
957
- return next;
958
- }, []);
1560
+ const envDefaults = exports.ENVIRONMENT_DEFAULTS[_internal.env];
1561
+ const streamUrl = envDefaults.walletStreamUrl;
1562
+ const walletRef = useStableRef2(wallet);
959
1563
  const doFetch = react.useCallback(async () => {
960
1564
  if (!username) return null;
961
1565
  try {
962
- const remote = await wallet.fetchRemote();
963
- if (!remote) return apply({ kind: "not-found" });
964
- return apply({
965
- kind: "ok",
966
- walletAddress: remote.walletAddress,
967
- onChain: remote.onChain
968
- });
1566
+ const remote = await walletRef.current.fetchRemote();
1567
+ if (!remote) {
1568
+ setStatus("no-wallet");
1569
+ setWalletAddress(null);
1570
+ setOnChain(null);
1571
+ setLastSuccessAt(Date.now());
1572
+ setIsStale(false);
1573
+ return "no-wallet";
1574
+ }
1575
+ const next = deriveStatus(remote.onChain);
1576
+ setStatus(next);
1577
+ setWalletAddress(remote.walletAddress);
1578
+ setOnChain(remote.onChain);
1579
+ setLastSuccessAt(Date.now());
1580
+ setIsStale(false);
1581
+ return next;
969
1582
  } catch {
970
1583
  return null;
971
1584
  }
972
- }, [username, wallet, apply]);
973
- react.useEffect(() => {
974
- if (!username || typeof BroadcastChannel === "undefined") return void 0;
975
- const channel = new BroadcastChannel(`accesly:wallet:${username}`);
976
- channel.onmessage = (ev) => {
977
- const data = ev.data;
978
- if (data && (data.kind === "ok" || data.kind === "not-found")) {
979
- apply(data);
980
- }
981
- };
982
- return () => channel.close();
983
- }, [username, apply]);
984
- const broadcastIfAvailable = react.useCallback(
985
- (res) => {
986
- if (!username || typeof BroadcastChannel === "undefined") return;
987
- try {
988
- const channel = new BroadcastChannel(`accesly:wallet:${username}`);
989
- channel.postMessage(res);
990
- channel.close();
991
- } catch {
992
- }
993
- },
994
- [username]
995
- );
1585
+ }, [username, walletRef]);
1586
+ const doFetchRef = useStableRef2(doFetch);
996
1587
  react.useEffect(() => {
997
1588
  if (!username) {
998
1589
  setStatus("unknown");
999
1590
  return void 0;
1000
1591
  }
1001
1592
  let cancelled = false;
1593
+ let unsubscribe = null;
1002
1594
  let pollTimer = null;
1003
1595
  let backoffIndex = 0;
1004
- let eventSource = null;
1005
1596
  const schedulePoll = (delayMs) => {
1006
1597
  if (cancelled) return;
1007
1598
  pollTimer = setTimeout(async () => {
1008
1599
  if (cancelled) return;
1009
- if (typeof document !== "undefined" && document.hidden) {
1010
- return;
1011
- }
1012
- const next = await doFetch();
1013
- if (next !== null) {
1014
- broadcastIfAvailable({
1015
- kind: next === "no-wallet" ? "not-found" : "ok",
1016
- walletAddress: walletAddress ?? "",
1017
- onChain
1018
- });
1019
- }
1020
- if (next !== "on-chain" && next !== "no-wallet") {
1021
- backoffIndex = Math.min(backoffIndex + 1, POLL_BACKOFF_MS.length - 1);
1022
- schedulePoll(POLL_BACKOFF_MS[backoffIndex]);
1023
- }
1600
+ if (typeof document !== "undefined" && document.hidden) return;
1601
+ const next = await doFetchRef.current();
1602
+ if (next === "on-chain" || next === "no-wallet") return;
1603
+ backoffIndex = Math.min(backoffIndex + 1, POLL_BACKOFF_MS.length - 1);
1604
+ schedulePoll(POLL_BACKOFF_MS[backoffIndex]);
1024
1605
  }, delayMs);
1025
1606
  };
1026
- const startPolling = () => {
1027
- backoffIndex = 0;
1028
- schedulePoll(POLL_BACKOFF_MS[0]);
1029
- };
1030
- const stopPolling = () => {
1031
- if (pollTimer) {
1032
- clearTimeout(pollTimer);
1033
- pollTimer = null;
1034
- }
1035
- };
1036
- const tryOpenSse = () => {
1037
- if (typeof EventSource === "undefined") return false;
1038
- try {
1039
- const url = `${_internal.apiUrl.replace(/\/$/, "")}/wallets/stream`;
1040
- eventSource = new EventSource(url, { withCredentials: false });
1041
- eventSource.onmessage = (ev) => {
1042
- try {
1043
- const data = JSON.parse(ev.data);
1044
- if (typeof data.walletAddress === "string") {
1045
- const res = {
1046
- kind: "ok",
1047
- walletAddress: data.walletAddress,
1048
- onChain: data.onChain ?? null
1049
- };
1050
- apply(res);
1051
- broadcastIfAvailable(res);
1052
- }
1053
- } catch {
1054
- }
1055
- };
1056
- eventSource.onerror = () => {
1057
- eventSource?.close();
1058
- eventSource = null;
1059
- startPolling();
1060
- };
1061
- return true;
1062
- } catch {
1063
- return false;
1064
- }
1065
- };
1066
1607
  void (async () => {
1067
- const next = await doFetch();
1608
+ const initial = await doFetchRef.current();
1068
1609
  if (cancelled) return;
1069
- if (next === "on-chain" || next === "no-wallet") {
1070
- return;
1610
+ if (initial === "on-chain" || initial === "no-wallet") return;
1611
+ if (walletAddress) {
1612
+ unsubscribe = subscribeToWalletEvent(streamUrl, walletAddress, "status", (data) => {
1613
+ setWalletAddress(data.walletAddress);
1614
+ setOnChain(data.onChain);
1615
+ setStatus(deriveStatus(data.onChain));
1616
+ setLastSuccessAt(Date.now());
1617
+ setIsStale(false);
1618
+ });
1619
+ }
1620
+ if (!unsubscribe) {
1621
+ backoffIndex = 0;
1622
+ schedulePoll(POLL_BACKOFF_MS[0]);
1071
1623
  }
1072
- const sseOpen = tryOpenSse();
1073
- if (!sseOpen) startPolling();
1074
1624
  })();
1075
1625
  const onVisibilityChange = () => {
1076
1626
  if (typeof document === "undefined") return;
1077
- if (document.hidden) {
1078
- stopPolling();
1079
- } else if (!eventSource && status !== "on-chain" && status !== "no-wallet") {
1080
- void doFetch();
1081
- startPolling();
1627
+ if (!document.hidden && !unsubscribe) {
1628
+ void doFetchRef.current();
1082
1629
  }
1083
1630
  };
1084
1631
  if (typeof document !== "undefined") {
@@ -1089,33 +1636,27 @@ function useWalletStatus() {
1089
1636
  setIsStale(true);
1090
1637
  }
1091
1638
  }, 3e4);
1092
- refreshRef.current = async () => {
1093
- const next = await doFetch();
1094
- if (next === "on-chain" || next === "no-wallet") {
1095
- stopPolling();
1096
- } else {
1097
- backoffIndex = 0;
1098
- stopPolling();
1099
- startPolling();
1100
- }
1101
- };
1102
1639
  return () => {
1103
1640
  cancelled = true;
1104
- stopPolling();
1641
+ if (unsubscribe) unsubscribe();
1642
+ if (pollTimer) clearTimeout(pollTimer);
1105
1643
  clearInterval(staleTimer);
1106
- eventSource?.close();
1107
1644
  if (typeof document !== "undefined") {
1108
1645
  document.removeEventListener("visibilitychange", onVisibilityChange);
1109
1646
  }
1110
1647
  };
1111
- }, [username, _internal.apiUrl, doFetch, apply, broadcastIfAvailable]);
1648
+ }, [username, streamUrl, walletAddress]);
1112
1649
  const refresh = react.useCallback(async () => {
1113
- await refreshRef.current();
1114
- }, []);
1650
+ await doFetchRef.current();
1651
+ }, [doFetchRef]);
1115
1652
  return { status, walletAddress, onChain, isStale, refresh };
1116
1653
  }
1117
- var POLL_INTERVAL_MS = 8e3;
1118
- function useStableRef(value) {
1654
+
1655
+ // src/hooks/useBalance.ts
1656
+ init_useAccesly();
1657
+ init_config();
1658
+ var POLL_FALLBACK_MS = 1e4;
1659
+ function useStableRef3(value) {
1119
1660
  const ref = react.useRef(value);
1120
1661
  ref.current = value;
1121
1662
  return ref;
@@ -1130,8 +1671,7 @@ function useBalance(walletAddress) {
1130
1671
  const [xlm, setXlm] = react.useState(null);
1131
1672
  const [isLoading, setIsLoading] = react.useState(true);
1132
1673
  const [error, setError] = react.useState(null);
1133
- const fetchInFlight = react.useRef(false);
1134
- const walletRef = useStableRef(wallet);
1674
+ const walletRef = useStableRef3(wallet);
1135
1675
  react.useEffect(() => {
1136
1676
  if (walletAddress) {
1137
1677
  setResolvedAddress(walletAddress);
@@ -1155,11 +1695,11 @@ function useBalance(walletAddress) {
1155
1695
  cancelled = true;
1156
1696
  };
1157
1697
  }, [walletAddress, username, walletRef]);
1158
- const endpointsRef = useStableRef(_internal.endpoints);
1159
- const doFetch = react.useCallback(async () => {
1698
+ const envDefaults = exports.ENVIRONMENT_DEFAULTS[_internal.env];
1699
+ const streamUrl = envDefaults.walletStreamUrl;
1700
+ const endpointsRef = useStableRef3(_internal.endpoints);
1701
+ const doFetchOnce = react.useCallback(async () => {
1160
1702
  if (!resolvedAddress) return;
1161
- if (fetchInFlight.current) return;
1162
- fetchInFlight.current = true;
1163
1703
  try {
1164
1704
  const res = await endpointsRef.current.walletBalance(resolvedAddress);
1165
1705
  setStroops(res.xlm.stroops);
@@ -1169,58 +1709,60 @@ function useBalance(walletAddress) {
1169
1709
  setError(err);
1170
1710
  } finally {
1171
1711
  setIsLoading(false);
1172
- fetchInFlight.current = false;
1173
1712
  }
1174
1713
  }, [resolvedAddress, endpointsRef]);
1175
- const doFetchRef = useStableRef(doFetch);
1714
+ const doFetchRef = useStableRef3(doFetchOnce);
1176
1715
  react.useEffect(() => {
1177
1716
  if (!resolvedAddress) {
1178
1717
  setIsLoading(false);
1179
1718
  return void 0;
1180
1719
  }
1181
- void doFetchRef.current();
1182
- let interval = null;
1183
- const startInterval = () => {
1184
- if (interval) return;
1185
- interval = setInterval(() => {
1186
- if (typeof document !== "undefined" && document.hidden) return;
1187
- void doFetchRef.current();
1188
- }, POLL_INTERVAL_MS);
1189
- };
1190
- const stopInterval = () => {
1191
- if (interval) {
1192
- clearInterval(interval);
1193
- interval = null;
1720
+ const unsubscribe = subscribeToWalletEvent(
1721
+ streamUrl,
1722
+ resolvedAddress,
1723
+ "balance",
1724
+ (data) => {
1725
+ setStroops(data.stroops);
1726
+ setXlm(data.xlm);
1727
+ setError(null);
1728
+ setIsLoading(false);
1194
1729
  }
1195
- };
1196
- startInterval();
1730
+ );
1731
+ if (unsubscribe) {
1732
+ void doFetchRef.current();
1733
+ return unsubscribe;
1734
+ }
1735
+ void doFetchRef.current();
1736
+ const interval = setInterval(() => {
1737
+ if (typeof document !== "undefined" && document.hidden) return;
1738
+ void doFetchRef.current();
1739
+ }, POLL_FALLBACK_MS);
1197
1740
  const onVisibilityChange = () => {
1198
1741
  if (typeof document === "undefined") return;
1199
- if (document.hidden) {
1200
- stopInterval();
1201
- } else {
1202
- void doFetchRef.current();
1203
- startInterval();
1204
- }
1742
+ if (!document.hidden) void doFetchRef.current();
1205
1743
  };
1206
1744
  if (typeof document !== "undefined") {
1207
1745
  document.addEventListener("visibilitychange", onVisibilityChange);
1208
1746
  }
1209
1747
  return () => {
1210
- stopInterval();
1748
+ clearInterval(interval);
1211
1749
  if (typeof document !== "undefined") {
1212
1750
  document.removeEventListener("visibilitychange", onVisibilityChange);
1213
1751
  }
1214
1752
  };
1215
- }, [resolvedAddress, doFetchRef]);
1753
+ }, [resolvedAddress, streamUrl, doFetchRef]);
1216
1754
  const refresh = react.useCallback(async () => {
1217
1755
  await doFetchRef.current();
1218
1756
  }, [doFetchRef]);
1219
1757
  return { stroops, xlm, isLoading, error, refresh };
1220
1758
  }
1221
- var POLL_INTERVAL_MS2 = 2e4;
1759
+
1760
+ // src/hooks/useWalletActivity.ts
1761
+ init_useAccesly();
1762
+ init_config();
1763
+ var POLL_FALLBACK_MS2 = 25e3;
1222
1764
  var DEFAULT_LIMIT = 20;
1223
- function useStableRef2(value) {
1765
+ function useStableRef4(value) {
1224
1766
  const ref = react.useRef(value);
1225
1767
  ref.current = value;
1226
1768
  return ref;
@@ -1235,8 +1777,7 @@ function useWalletActivity(walletAddress, opts = {}) {
1235
1777
  const [events, setEvents] = react.useState([]);
1236
1778
  const [isLoading, setIsLoading] = react.useState(true);
1237
1779
  const [error, setError] = react.useState(null);
1238
- const fetchInFlight = react.useRef(false);
1239
- const walletRef = useStableRef2(wallet);
1780
+ const walletRef = useStableRef4(wallet);
1240
1781
  react.useEffect(() => {
1241
1782
  if (walletAddress) {
1242
1783
  setResolvedAddress(walletAddress);
@@ -1260,79 +1801,87 @@ function useWalletActivity(walletAddress, opts = {}) {
1260
1801
  cancelled = true;
1261
1802
  };
1262
1803
  }, [walletAddress, username, walletRef]);
1263
- const endpointsRef = useStableRef2(_internal.endpoints);
1264
- const doFetch = react.useCallback(async () => {
1804
+ const envDefaults = exports.ENVIRONMENT_DEFAULTS[_internal.env];
1805
+ const streamUrl = envDefaults.walletStreamUrl;
1806
+ const endpointsRef = useStableRef4(_internal.endpoints);
1807
+ const doFetchOnce = react.useCallback(async () => {
1265
1808
  if (!resolvedAddress) return;
1266
- if (fetchInFlight.current) return;
1267
- fetchInFlight.current = true;
1268
1809
  try {
1269
1810
  const res = await endpointsRef.current.walletActivity(resolvedAddress, limit);
1270
- setEvents(res.events);
1811
+ const adapted = [];
1812
+ for (const ev of res.events) {
1813
+ const conv = adaptRestEvent(ev);
1814
+ if (conv) adapted.push(conv);
1815
+ }
1816
+ setEvents(adapted.slice(0, limit));
1271
1817
  setError(null);
1272
1818
  } catch (err) {
1273
1819
  setError(err);
1274
1820
  } finally {
1275
1821
  setIsLoading(false);
1276
- fetchInFlight.current = false;
1277
1822
  }
1278
1823
  }, [resolvedAddress, limit, endpointsRef]);
1279
- const doFetchRef = useStableRef2(doFetch);
1824
+ const doFetchRef = useStableRef4(doFetchOnce);
1280
1825
  react.useEffect(() => {
1281
1826
  if (!resolvedAddress) {
1282
1827
  setIsLoading(false);
1283
1828
  return void 0;
1284
1829
  }
1285
- void doFetchRef.current();
1286
- let interval = null;
1287
- const start = () => {
1288
- if (interval) return;
1289
- interval = setInterval(() => {
1290
- if (typeof document !== "undefined" && document.hidden) return;
1291
- void doFetchRef.current();
1292
- }, POLL_INTERVAL_MS2);
1293
- };
1294
- const stop = () => {
1295
- if (interval) {
1296
- clearInterval(interval);
1297
- interval = null;
1830
+ const unsubscribe = subscribeToWalletEvent(
1831
+ streamUrl,
1832
+ resolvedAddress,
1833
+ "activity",
1834
+ (data) => {
1835
+ setEvents(data.events.slice(0, limit));
1836
+ setError(null);
1837
+ setIsLoading(false);
1298
1838
  }
1299
- };
1300
- start();
1301
- const onVisibilityChange = () => {
1302
- if (typeof document === "undefined") return;
1303
- if (document.hidden) stop();
1304
- else {
1305
- void doFetchRef.current();
1306
- start();
1307
- }
1308
- };
1309
- if (typeof document !== "undefined") {
1310
- document.addEventListener("visibilitychange", onVisibilityChange);
1839
+ );
1840
+ if (unsubscribe) {
1841
+ void doFetchRef.current();
1842
+ return unsubscribe;
1311
1843
  }
1312
- return () => {
1313
- stop();
1314
- if (typeof document !== "undefined") {
1315
- document.removeEventListener("visibilitychange", onVisibilityChange);
1316
- }
1317
- };
1318
- }, [resolvedAddress, doFetchRef]);
1844
+ void doFetchRef.current();
1845
+ const interval = setInterval(() => {
1846
+ if (typeof document !== "undefined" && document.hidden) return;
1847
+ void doFetchRef.current();
1848
+ }, POLL_FALLBACK_MS2);
1849
+ return () => clearInterval(interval);
1850
+ }, [resolvedAddress, streamUrl, limit, doFetchRef]);
1319
1851
  const refresh = react.useCallback(async () => {
1320
1852
  await doFetchRef.current();
1321
1853
  }, [doFetchRef]);
1322
1854
  return { events, isLoading, error, refresh };
1323
1855
  }
1856
+ function adaptRestEvent(ev) {
1857
+ const t0 = ev.topics[0];
1858
+ if (typeof t0 !== "string") return null;
1859
+ if (t0 === "SignerRotated") {
1860
+ return {
1861
+ type: "signer-rotated",
1862
+ txHash: ev.txHash,
1863
+ ledger: ev.ledger,
1864
+ timestamp: ev.timestamp,
1865
+ newOwnerEd25519Hex: typeof ev.value === "string" ? ev.value : ""
1866
+ };
1867
+ }
1868
+ return null;
1869
+ }
1324
1870
 
1325
1871
  // src/index.ts
1872
+ init_useWalletHistory();
1326
1873
  var REACT_ADAPTER_VERSION = "0.0.0";
1327
1874
 
1328
- exports.AcceslyContext = AcceslyContext;
1329
1875
  exports.AcceslyProvider = AcceslyProvider;
1330
- exports.ENVIRONMENT_DEFAULTS = ENVIRONMENT_DEFAULTS;
1331
- exports.NotImplementedYetError = NotImplementedYetError;
1332
1876
  exports.REACT_ADAPTER_VERSION = REACT_ADAPTER_VERSION;
1877
+ exports.closeAllWalletSubscriptions = closeAllWalletSubscriptions;
1878
+ exports.historyClearOptimistic = historyClearOptimistic;
1879
+ exports.historyOptimisticPush = historyOptimisticPush;
1880
+ exports.subscribeToWalletEvent = subscribeToWalletEvent;
1333
1881
  exports.useAccesly = useAccesly;
1334
1882
  exports.useBalance = useBalance;
1335
1883
  exports.useWalletActivity = useWalletActivity;
1884
+ exports.useWalletHistory = useWalletHistory;
1336
1885
  exports.useWalletStatus = useWalletStatus;
1337
1886
  //# sourceMappingURL=index.cjs.map
1338
1887
  //# sourceMappingURL=index.cjs.map