@accesly/react 1.2.0 → 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.js CHANGED
@@ -1,154 +1,522 @@
1
- import { CognitoAuthClient, defaultSessionStorage, InMemoryDeviceStore, TokenManager, AccesslyApiClient, AccesslyEndpoints, getRandomBytes, unlockPasskey, hkdfSha256, zeroize, decryptAesGcm, sha256, registerPasskey, normalizeSecp256r1Pubkey, createWallet, generateRecoverySalt, deriveRecoveryKey, encryptAesGcm, emailHashBytes, computeSmartAccountAddress, signTransaction, generateX25519Keypair, unwrapSessionFragment2, reconstructFromPlainAndEncrypted, signSorobanAuthEntry, reconstructKey, AccesslyApiError } from '@accesly/core';
2
- import { createContext, useMemo, useState, useRef, useCallback, useEffect, useContext } from 'react';
1
+ import { createContext, useState, useEffect, useCallback, useContext, useMemo, useRef } from 'react';
2
+ import { getRandomBytes, unlockPasskey, hkdfSha256, zeroize, decryptAesGcm, sha256, registerPasskey, normalizeSecp256r1Pubkey, createWallet, generateRecoverySalt, deriveRecoveryKey, encryptAesGcm, emailHashBytes, computeSmartAccountAddress, signTransaction, generateX25519Keypair, unwrapSessionFragment2, reconstructFromPlainAndEncrypted, signSorobanAuthEntry, reconstructKey, CognitoAuthClient, defaultSessionStorage, InMemoryDeviceStore, TokenManager, AccesslyApiClient, AccesslyEndpoints, AccesslyApiError } from '@accesly/core';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
5
- // src/provider.tsx
6
- var AcceslyContext = createContext(null);
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var AcceslyContext;
15
+ var init_context = __esm({
16
+ "src/context.ts"() {
17
+ AcceslyContext = createContext(null);
18
+ }
19
+ });
7
20
 
8
21
  // src/config.ts
9
- var ENVIRONMENT_DEFAULTS = {
10
- dev: {
11
- apiUrl: "https://3fki7eiio5.execute-api.us-east-1.amazonaws.com/dev",
12
- walletStreamUrl: "https://ajlmn37thw7fxen3oyykbfmlrm0eecue.lambda-url.us-east-1.on.aws/",
13
- cognito: {
14
- region: "us-east-1",
15
- userPoolId: "us-east-1_K2Nag1tB1",
16
- userPoolClientId: "6r64diep7pne50sender4557jt"
17
- },
18
- stellar: {
19
- networkPassphrase: "Test SDF Network ; September 2015",
20
- horizonUrl: "https://horizon-testnet.stellar.org",
21
- sorobanRpcUrl: "https://soroban-testnet.stellar.org",
22
- // OZ Relayer channels-fund see CloudServices-accesly/docs/Deployed_Resources_dev.md
23
- deployerAddress: "GDRHSVLY3VCEHCHCSR5MZR2ALYLCERDDFT3ULCUIELGFVYHTZFCMNU4E",
24
- // accesly-contracts Phase 1 deploy on Stellar testnet.
25
- ed25519VerifierAddress: "CALVIIGIOMODZMWTMKZLSD4PZFFEPWQBSYERHUFM6MH5FLWKCHW4E4G5"
22
+ var ENVIRONMENT_DEFAULTS;
23
+ var init_config = __esm({
24
+ "src/config.ts"() {
25
+ ENVIRONMENT_DEFAULTS = {
26
+ dev: {
27
+ apiUrl: "https://3fki7eiio5.execute-api.us-east-1.amazonaws.com/dev",
28
+ walletStreamUrl: "https://ajlmn37thw7fxen3oyykbfmlrm0eecue.lambda-url.us-east-1.on.aws/",
29
+ cognito: {
30
+ region: "us-east-1",
31
+ userPoolId: "us-east-1_K2Nag1tB1",
32
+ userPoolClientId: "6r64diep7pne50sender4557jt"
33
+ },
34
+ stellar: {
35
+ networkPassphrase: "Test SDF Network ; September 2015",
36
+ horizonUrl: "https://horizon-testnet.stellar.org",
37
+ sorobanRpcUrl: "https://soroban-testnet.stellar.org",
38
+ // OZ Relayer channels-fund — see CloudServices-accesly/docs/Deployed_Resources_dev.md
39
+ deployerAddress: "GDRHSVLY3VCEHCHCSR5MZR2ALYLCERDDFT3ULCUIELGFVYHTZFCMNU4E",
40
+ // accesly-contracts Phase 1 deploy on Stellar testnet.
41
+ ed25519VerifierAddress: "CALVIIGIOMODZMWTMKZLSD4PZFFEPWQBSYERHUFM6MH5FLWKCHW4E4G5"
42
+ }
43
+ },
44
+ staging: {
45
+ apiUrl: "https://api-staging.accesly.xyz",
46
+ walletStreamUrl: "",
47
+ cognito: {
48
+ region: "us-east-1",
49
+ userPoolId: "TBD-staging",
50
+ userPoolClientId: "TBD-staging"
51
+ },
52
+ stellar: {
53
+ networkPassphrase: "Test SDF Network ; September 2015",
54
+ horizonUrl: "https://horizon-testnet.stellar.org",
55
+ sorobanRpcUrl: "https://soroban-testnet.stellar.org",
56
+ deployerAddress: "GDRHSVLY3VCEHCHCSR5MZR2ALYLCERDDFT3ULCUIELGFVYHTZFCMNU4E",
57
+ ed25519VerifierAddress: "CALVIIGIOMODZMWTMKZLSD4PZFFEPWQBSYERHUFM6MH5FLWKCHW4E4G5"
58
+ }
59
+ },
60
+ prod: {
61
+ apiUrl: "https://api.accesly.xyz",
62
+ walletStreamUrl: "",
63
+ cognito: {
64
+ region: "us-east-1",
65
+ userPoolId: "TBD-prod",
66
+ userPoolClientId: "TBD-prod"
67
+ },
68
+ stellar: {
69
+ networkPassphrase: "Public Global Stellar Network ; September 2015",
70
+ horizonUrl: "https://horizon.stellar.org",
71
+ sorobanRpcUrl: "https://soroban-rpc.mainnet.stellar.org",
72
+ deployerAddress: "TBD-prod",
73
+ ed25519VerifierAddress: "TBD-prod"
74
+ }
75
+ }
76
+ };
77
+ }
78
+ });
79
+ function isSorobanDeployPendingError(err) {
80
+ if (!(err instanceof AccesslyApiError)) return false;
81
+ const haystack = `${err.message ?? ""} ${err.code ?? ""}`.toLowerCase();
82
+ return haystack.includes("txsorobaninvalid") || haystack.includes("soroban sendtransaction") || haystack.includes("soroban submit failed") || haystack.includes("scecexceededlimit") || haystack.includes("exceededlimit");
83
+ }
84
+ var init_sorobanDeployStatus = __esm({
85
+ "src/hooks/sorobanDeployStatus.ts"() {
86
+ }
87
+ });
88
+
89
+ // src/hooks/stellarExpert.ts
90
+ async function fetchContractEvents(contractId, opts) {
91
+ const params = new URLSearchParams();
92
+ params.set("limit", String(opts.limit ?? 50));
93
+ params.set("order", opts.order ?? "desc");
94
+ if (opts.cursor) params.set("cursor", opts.cursor);
95
+ if (opts.topics) params.set("topics", opts.topics);
96
+ const url = `${SE_BASE}/${opts.network}/contract/${contractId}/events?${params.toString()}`;
97
+ const res = await fetch(url, opts.signal ? { signal: opts.signal } : {});
98
+ if (!res.ok) {
99
+ throw new Error(`StellarExpert ${res.status} ${res.statusText}`);
100
+ }
101
+ return await res.json();
102
+ }
103
+ async function fetchContractMeta(contractId, network, signal) {
104
+ const url = `${SE_BASE}/${network}/contract/${contractId}`;
105
+ try {
106
+ const res = await fetch(url, signal ? { signal } : {});
107
+ if (!res.ok) return null;
108
+ return await res.json();
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+ function decodeTransferAmount(bodyXdr) {
114
+ if (!bodyXdr) return "0";
115
+ try {
116
+ const bytes = base64ToBytes(bodyXdr);
117
+ if (bytes.length < 20) return "0";
118
+ let hi = 0n;
119
+ for (let i = 8; i < 16; i += 1) hi = hi << 8n | BigInt(bytes[i] ?? 0);
120
+ let lo = 0n;
121
+ for (let i = 16; i < 24; i += 1) lo = lo << 8n | BigInt(bytes[i] ?? 0);
122
+ const value = hi << 64n | lo;
123
+ return value.toString();
124
+ } catch {
125
+ return "0";
126
+ }
127
+ }
128
+ function base64ToBytes(s) {
129
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(s, "base64"));
130
+ const bin = globalThis.atob(s);
131
+ const arr = new Uint8Array(bin.length);
132
+ for (let i = 0; i < bin.length; i += 1) arr[i] = bin.charCodeAt(i);
133
+ return arr;
134
+ }
135
+ function decodeSEEvent(ev, walletAddress, xlmSac, txHashByEventId) {
136
+ const t0 = ev.topics[0];
137
+ if (!t0) return null;
138
+ const timestamp = new Date(ev.ts * 1e3).toISOString();
139
+ const txHash = txHashByEventId.get(ev.id) ?? ev.id.split("-")[0] ?? "";
140
+ if (ev.contract === walletAddress) {
141
+ if (t0 === "signer_rotated") {
142
+ return {
143
+ type: "signer-rotated",
144
+ txHash,
145
+ ledger: ev.ts,
146
+ // SE no expone ledger directamente — usamos timestamp
147
+ timestamp,
148
+ newOwnerEd25519Hex: ""
149
+ };
26
150
  }
27
- },
28
- staging: {
29
- apiUrl: "https://api-staging.accesly.xyz",
30
- walletStreamUrl: "",
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"
151
+ return null;
152
+ }
153
+ if (ev.contract === xlmSac && t0 === "transfer") {
154
+ const from = ev.topics[1];
155
+ const to = ev.topics[2];
156
+ if (!from || !to) return null;
157
+ const amountStroops = decodeTransferAmount(ev.bodyXdr);
158
+ if (from === walletAddress && to !== walletAddress) {
159
+ return {
160
+ type: "transfer-out",
161
+ txHash,
162
+ ledger: ev.ts,
163
+ timestamp,
164
+ to,
165
+ amountStroops
166
+ };
42
167
  }
43
- },
44
- prod: {
45
- apiUrl: "https://api.accesly.xyz",
46
- walletStreamUrl: "",
47
- cognito: {
48
- region: "us-east-1",
49
- userPoolId: "TBD-prod",
50
- userPoolClientId: "TBD-prod"
51
- },
52
- stellar: {
53
- networkPassphrase: "Public Global Stellar Network ; September 2015",
54
- horizonUrl: "https://horizon.stellar.org",
55
- sorobanRpcUrl: "https://soroban-rpc.mainnet.stellar.org",
56
- deployerAddress: "TBD-prod",
57
- ed25519VerifierAddress: "TBD-prod"
168
+ if (to === walletAddress && from !== walletAddress) {
169
+ return {
170
+ type: "transfer-in",
171
+ txHash,
172
+ ledger: ev.ts,
173
+ timestamp,
174
+ from,
175
+ amountStroops
176
+ };
58
177
  }
59
178
  }
60
- };
61
- function AcceslyProvider(props) {
62
- const defaults = ENVIRONMENT_DEFAULTS[props.env];
63
- const apiUrl = props.apiUrl ?? defaults.apiUrl;
64
- const cognitoConfig = props.cognitoConfig ?? defaults.cognito;
65
- const telemetry = props.telemetry;
66
- const instances = useMemo(() => {
67
- const authClient = props.overrides?.authClient ?? new CognitoAuthClient(cognitoConfig);
68
- const sessionStorage = props.overrides?.sessionStorage ?? defaultSessionStorage();
69
- const deviceStore = props.overrides?.deviceStore ?? new InMemoryDeviceStore();
70
- const tokenManager = new TokenManager({ authClient, storage: sessionStorage });
71
- const apiClient = new AccesslyApiClient({
72
- baseUrl: apiUrl,
73
- getIdToken: () => tokenManager.getValidIdToken(),
74
- ...telemetry ? { telemetry } : {}
75
- });
76
- const endpoints = new AccesslyEndpoints(apiClient);
77
- return { authClient, sessionStorage, deviceStore, tokenManager, endpoints };
78
- }, [
79
- apiUrl,
80
- cognitoConfig.region,
81
- cognitoConfig.userPoolId,
82
- cognitoConfig.userPoolClientId,
83
- props.overrides?.authClient,
84
- props.overrides?.sessionStorage,
85
- props.overrides?.deviceStore
179
+ return null;
180
+ }
181
+ async function fetchWalletHistory(walletAddress, opts) {
182
+ const xlmSac = opts.network === "mainnet" ? XLM_SAC_MAINNET : XLM_SAC_TESTNET;
183
+ const limit = 50;
184
+ const transferLimit = opts.transferScanLimit ?? 50;
185
+ const [smartAccountResp, transfersResp] = await Promise.all([
186
+ fetchContractEvents(walletAddress, {
187
+ network: opts.network,
188
+ limit,
189
+ order: "desc",
190
+ ...opts.smartAccountCursor ? { cursor: opts.smartAccountCursor } : {},
191
+ ...opts.signal ? { signal: opts.signal } : {}
192
+ }).catch((err) => {
193
+ console.warn("[stellarExpert] smart account events failed", err);
194
+ return { _embedded: { records: [] } };
195
+ }),
196
+ fetchContractEvents(xlmSac, {
197
+ network: opts.network,
198
+ limit: transferLimit,
199
+ order: "desc",
200
+ topics: "transfer",
201
+ ...opts.signal ? { signal: opts.signal } : {}
202
+ }).catch((err) => {
203
+ console.warn("[stellarExpert] xlm sac events failed", err);
204
+ return { _embedded: { records: [] } };
205
+ })
86
206
  ]);
87
- const [status, setStatus] = useState(() => initialStatus(instances.sessionStorage));
88
- const [username, setUsername] = useState(
89
- () => initialUsername(instances.sessionStorage)
90
- );
91
- const mountedRef = useRef(true);
92
- const refreshStatus = useCallback(async () => {
93
- const next = await instances.tokenManager.getStatus();
94
- const tokens = await Promise.resolve(instances.sessionStorage.load());
95
- if (mountedRef.current) {
96
- setStatus(next);
97
- setUsername(tokens?.username ?? null);
207
+ const txHashByEventId = /* @__PURE__ */ new Map();
208
+ const items = [];
209
+ const saRecords = smartAccountResp._embedded?.records ?? [];
210
+ const txRecords = transfersResp._embedded?.records ?? [];
211
+ for (const ev of saRecords) {
212
+ const decoded = decodeSEEvent(ev, walletAddress, xlmSac, txHashByEventId);
213
+ if (decoded) items.push(decoded);
214
+ }
215
+ for (const ev of txRecords) {
216
+ const decoded = decodeSEEvent(ev, walletAddress, xlmSac, txHashByEventId);
217
+ if (decoded) items.push(decoded);
218
+ }
219
+ if (!opts.smartAccountCursor) {
220
+ const meta = await fetchContractMeta(walletAddress, opts.network, opts.signal);
221
+ if (meta) {
222
+ items.push({
223
+ type: "wallet-created",
224
+ txHash: "",
225
+ ledger: meta.created,
226
+ timestamp: new Date(meta.created * 1e3).toISOString()
227
+ });
98
228
  }
99
- }, [instances]);
229
+ }
230
+ items.sort((a, b) => (b.ledger ?? 0) - (a.ledger ?? 0));
231
+ return {
232
+ items,
233
+ cursors: {
234
+ smartAccount: extractCursor(smartAccountResp),
235
+ transfers: extractCursor(transfersResp)
236
+ }
237
+ };
238
+ }
239
+ function extractCursor(resp) {
240
+ const records = resp._embedded?.records;
241
+ if (!records || records.length === 0) return null;
242
+ return records[records.length - 1]?.paging_token ?? records[records.length - 1]?.id ?? null;
243
+ }
244
+ var SE_BASE, XLM_SAC_TESTNET, XLM_SAC_MAINNET;
245
+ var init_stellarExpert = __esm({
246
+ "src/hooks/stellarExpert.ts"() {
247
+ SE_BASE = "https://api.stellar.expert/explorer";
248
+ XLM_SAC_TESTNET = "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC";
249
+ XLM_SAC_MAINNET = "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA";
250
+ }
251
+ });
252
+
253
+ // src/hooks/useWalletHistory.ts
254
+ var useWalletHistory_exports = {};
255
+ __export(useWalletHistory_exports, {
256
+ historyClearOptimistic: () => historyClearOptimistic,
257
+ historyOptimisticPush: () => historyOptimisticPush,
258
+ useWalletHistory: () => useWalletHistory
259
+ });
260
+ function useStableRef(value) {
261
+ const ref = useRef(value);
262
+ ref.current = value;
263
+ return ref;
264
+ }
265
+ function loadCache(walletAddress) {
266
+ if (typeof localStorage === "undefined") return null;
267
+ try {
268
+ const raw = localStorage.getItem(CACHE_KEY_PREFIX + walletAddress);
269
+ if (!raw) return null;
270
+ const parsed = JSON.parse(raw);
271
+ if (Date.now() - parsed.storedAt > CACHE_TTL_MS) return null;
272
+ return parsed;
273
+ } catch {
274
+ return null;
275
+ }
276
+ }
277
+ function saveCache(walletAddress, entry) {
278
+ if (typeof localStorage === "undefined") return;
279
+ try {
280
+ localStorage.setItem(CACHE_KEY_PREFIX + walletAddress, JSON.stringify(entry));
281
+ } catch {
282
+ }
283
+ }
284
+ function historyOptimisticPush(walletAddress, item) {
285
+ const current = optimisticItems.get(walletAddress) ?? [];
286
+ optimisticItems.set(walletAddress, [item, ...current]);
287
+ const listeners = optimisticListeners.get(walletAddress);
288
+ if (listeners) {
289
+ for (const fn of listeners) fn(optimisticItems.get(walletAddress) ?? []);
290
+ }
291
+ }
292
+ function historyClearOptimistic(walletAddress) {
293
+ optimisticItems.delete(walletAddress);
294
+ const listeners = optimisticListeners.get(walletAddress);
295
+ if (listeners) {
296
+ for (const fn of listeners) fn([]);
297
+ }
298
+ }
299
+ function subscribeOptimistic(walletAddress, listener) {
300
+ let set = optimisticListeners.get(walletAddress);
301
+ if (!set) {
302
+ set = /* @__PURE__ */ new Set();
303
+ optimisticListeners.set(walletAddress, set);
304
+ }
305
+ set.add(listener);
306
+ return () => {
307
+ set?.delete(listener);
308
+ if (set && set.size === 0) optimisticListeners.delete(walletAddress);
309
+ };
310
+ }
311
+ function useWalletHistory(walletAddress, opts = {}) {
312
+ const { wallet, _internal } = useAccesly();
313
+ const username = _internal.username;
314
+ const network = opts.network ?? inferNetwork(_internal.env);
315
+ const [resolvedAddress, setResolvedAddress] = useState(walletAddress ?? null);
316
+ const [events, setEvents] = useState([]);
317
+ const [optimistic, setOptimistic] = useState([]);
318
+ const [cursors, setCursors] = useState({
319
+ smartAccount: null,
320
+ transfers: null
321
+ });
322
+ const [isLoading, setIsLoading] = useState(true);
323
+ const [error, setError] = useState(null);
324
+ const [hasMore, setHasMore] = useState(true);
325
+ const walletRef = useStableRef(wallet);
100
326
  useEffect(() => {
101
- mountedRef.current = true;
102
- void refreshStatus();
327
+ if (walletAddress) {
328
+ setResolvedAddress(walletAddress);
329
+ return;
330
+ }
331
+ if (!username) {
332
+ setResolvedAddress(null);
333
+ return;
334
+ }
335
+ let cancelled = false;
336
+ void (async () => {
337
+ try {
338
+ const stored = await walletRef.current.getStoredCredential(username);
339
+ if (cancelled) return;
340
+ setResolvedAddress(stored?.walletAddress ?? null);
341
+ } catch {
342
+ if (!cancelled) setResolvedAddress(null);
343
+ }
344
+ })();
103
345
  return () => {
104
- mountedRef.current = false;
346
+ cancelled = true;
105
347
  };
106
- }, [instances]);
107
- const value = useMemo(
108
- () => ({
109
- appId: props.appId,
110
- env: props.env,
111
- apiUrl,
112
- cognitoConfig,
113
- authClient: instances.authClient,
114
- sessionStorage: instances.sessionStorage,
115
- tokenManager: instances.tokenManager,
116
- endpoints: instances.endpoints,
117
- deviceStore: instances.deviceStore,
118
- status,
119
- username,
120
- refreshStatus
121
- }),
122
- [props.appId, props.env, apiUrl, cognitoConfig, instances, status, username, refreshStatus]
123
- );
124
- return /* @__PURE__ */ jsx(AcceslyContext.Provider, { value, children: props.children });
348
+ }, [walletAddress, username, walletRef]);
349
+ useEffect(() => {
350
+ if (!resolvedAddress) return void 0;
351
+ setOptimistic(optimisticItems.get(resolvedAddress) ?? []);
352
+ return subscribeOptimistic(resolvedAddress, setOptimistic);
353
+ }, [resolvedAddress]);
354
+ useEffect(() => {
355
+ if (!resolvedAddress) {
356
+ setIsLoading(false);
357
+ return void 0;
358
+ }
359
+ const cached = loadCache(resolvedAddress);
360
+ if (cached) {
361
+ setEvents(cached.items);
362
+ setCursors(cached.cursors);
363
+ setIsLoading(false);
364
+ }
365
+ let cancelled = false;
366
+ const channel = typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(BROADCAST_CHANNEL_PREFIX + resolvedAddress) : null;
367
+ if (channel) {
368
+ channel.onmessage = (ev) => {
369
+ const data = ev.data;
370
+ if (data && !cancelled) {
371
+ setEvents(data.items);
372
+ setCursors(data.cursors);
373
+ setIsLoading(false);
374
+ }
375
+ };
376
+ }
377
+ void (async () => {
378
+ try {
379
+ const result = await fetchWalletHistory(resolvedAddress, {
380
+ network,
381
+ ...opts.transferScanLimit ? { transferScanLimit: opts.transferScanLimit } : {}
382
+ });
383
+ if (cancelled) return;
384
+ const deduped = dedupItems(result.items);
385
+ setEvents(deduped);
386
+ setCursors(result.cursors);
387
+ setIsLoading(false);
388
+ setError(null);
389
+ setHasMore(result.cursors.smartAccount !== null || result.cursors.transfers !== null);
390
+ const entry = {
391
+ items: deduped,
392
+ cursors: result.cursors,
393
+ storedAt: Date.now()
394
+ };
395
+ saveCache(resolvedAddress, entry);
396
+ channel?.postMessage(entry);
397
+ } catch (err) {
398
+ if (!cancelled) {
399
+ setError(err);
400
+ setIsLoading(false);
401
+ }
402
+ }
403
+ })();
404
+ return () => {
405
+ cancelled = true;
406
+ channel?.close();
407
+ };
408
+ }, [resolvedAddress, network, opts.transferScanLimit]);
409
+ const interval = opts.pollIntervalMs ?? POLL_INTERVAL_MS;
410
+ useEffect(() => {
411
+ if (!resolvedAddress || interval === 0) return void 0;
412
+ const tick = async () => {
413
+ if (typeof document !== "undefined" && document.hidden) return;
414
+ try {
415
+ const result = await fetchWalletHistory(resolvedAddress, {
416
+ network,
417
+ ...opts.transferScanLimit ? { transferScanLimit: opts.transferScanLimit } : {}
418
+ });
419
+ setEvents((prev) => mergeAndDedup(prev, result.items));
420
+ if (resolvedAddress) {
421
+ const realTxHashes = new Set(result.items.map((it) => it.txHash));
422
+ const optimisticsRemaining = (optimisticItems.get(resolvedAddress) ?? []).filter(
423
+ (it) => !realTxHashes.has(it.txHash)
424
+ );
425
+ if (optimisticsRemaining.length !== (optimisticItems.get(resolvedAddress)?.length ?? 0)) {
426
+ optimisticItems.set(resolvedAddress, optimisticsRemaining);
427
+ const listeners = optimisticListeners.get(resolvedAddress);
428
+ if (listeners) for (const fn of listeners) fn(optimisticsRemaining);
429
+ }
430
+ }
431
+ } catch {
432
+ }
433
+ };
434
+ const id = setInterval(tick, interval);
435
+ return () => clearInterval(id);
436
+ }, [resolvedAddress, network, interval]);
437
+ const loadMoreImpl = useCallback(async () => {
438
+ if (!resolvedAddress) return;
439
+ if (!cursors.smartAccount && !cursors.transfers) {
440
+ setHasMore(false);
441
+ return;
442
+ }
443
+ try {
444
+ const result = await fetchWalletHistory(resolvedAddress, {
445
+ network,
446
+ ...cursors.smartAccount ? { smartAccountCursor: cursors.smartAccount } : {},
447
+ ...opts.transferScanLimit ? { transferScanLimit: opts.transferScanLimit } : {}
448
+ });
449
+ setEvents((prev) => mergeAndDedup(prev, result.items));
450
+ setCursors(result.cursors);
451
+ setHasMore(result.cursors.smartAccount !== null || result.cursors.transfers !== null);
452
+ } catch (err) {
453
+ setError(err);
454
+ }
455
+ }, [resolvedAddress, network, cursors, opts.transferScanLimit]);
456
+ const refreshImpl = useCallback(async () => {
457
+ if (!resolvedAddress) return;
458
+ setIsLoading(true);
459
+ try {
460
+ const result = await fetchWalletHistory(resolvedAddress, {
461
+ network,
462
+ ...opts.transferScanLimit ? { transferScanLimit: opts.transferScanLimit } : {}
463
+ });
464
+ const deduped = dedupItems(result.items);
465
+ setEvents(deduped);
466
+ setCursors(result.cursors);
467
+ setError(null);
468
+ saveCache(resolvedAddress, {
469
+ items: deduped,
470
+ cursors: result.cursors,
471
+ storedAt: Date.now()
472
+ });
473
+ } catch (err) {
474
+ setError(err);
475
+ } finally {
476
+ setIsLoading(false);
477
+ }
478
+ }, [resolvedAddress, network, opts.transferScanLimit]);
479
+ const combined = [...optimistic, ...events];
480
+ return {
481
+ events: combined,
482
+ isLoading,
483
+ error,
484
+ hasMore,
485
+ loadMore: loadMoreImpl,
486
+ refresh: refreshImpl
487
+ };
125
488
  }
126
- function initialStatus(storage) {
127
- const tokens = storage.load();
128
- if (tokens instanceof Promise) return "bootstrapping";
129
- if (!tokens) return "anonymous";
130
- return Date.now() + 5 * 60 * 1e3 >= tokens.expiresAt ? "expired" : "authenticated";
489
+ function inferNetwork(env) {
490
+ return env === "prod" ? "mainnet" : "testnet";
131
491
  }
132
- function initialUsername(storage) {
133
- const tokens = storage.load();
134
- if (tokens instanceof Promise) return null;
135
- return tokens?.username ?? null;
492
+ function dedupItems(items) {
493
+ const seen = /* @__PURE__ */ new Set();
494
+ const out = [];
495
+ for (const item of items) {
496
+ const key = `${item.type}:${item.txHash}:${item.ledger}`;
497
+ if (seen.has(key)) continue;
498
+ seen.add(key);
499
+ out.push(item);
500
+ }
501
+ out.sort((a, b) => (b.ledger ?? 0) - (a.ledger ?? 0));
502
+ return out;
136
503
  }
137
- function isSorobanDeployPendingError(err) {
138
- if (!(err instanceof AccesslyApiError)) return false;
139
- const haystack = `${err.message ?? ""} ${err.code ?? ""}`.toLowerCase();
140
- return haystack.includes("txsorobaninvalid") || haystack.includes("soroban sendtransaction") || haystack.includes("soroban submit failed") || haystack.includes("scecexceededlimit") || haystack.includes("exceededlimit");
504
+ function mergeAndDedup(prev, fresh) {
505
+ return dedupItems([...fresh, ...prev]);
141
506
  }
142
-
143
- // src/hooks/useAccesly.ts
144
- var NotImplementedYetError = class extends Error {
145
- constructor(namespace, method) {
146
- super(
147
- `${namespace}.${method}() is not implemented yet. This namespace ships in a later release; see docs/Handoff_Fase7.md for the roadmap.`
148
- );
149
- this.name = "NotImplementedYetError";
507
+ var POLL_INTERVAL_MS, CACHE_TTL_MS, CACHE_KEY_PREFIX, BROADCAST_CHANNEL_PREFIX, optimisticItems, optimisticListeners;
508
+ var init_useWalletHistory = __esm({
509
+ "src/hooks/useWalletHistory.ts"() {
510
+ init_useAccesly();
511
+ init_stellarExpert();
512
+ POLL_INTERVAL_MS = 3e4;
513
+ CACHE_TTL_MS = 12 * 60 * 60 * 1e3;
514
+ CACHE_KEY_PREFIX = "accesly:history:";
515
+ BROADCAST_CHANNEL_PREFIX = "accesly:history:";
516
+ optimisticItems = /* @__PURE__ */ new Map();
517
+ optimisticListeners = /* @__PURE__ */ new Map();
150
518
  }
151
- };
519
+ });
152
520
  function useAccesly() {
153
521
  const ctx = useContext(AcceslyContext);
154
522
  if (!ctx) {
@@ -613,8 +981,8 @@ function useAccesly() {
613
981
  const sessionPlaintext = unwrapSessionFragment2(wrappedF2, ephemeral.privateKey).plaintext;
614
982
  const fragmentF2Wire = JSON.parse(new TextDecoder().decode(sessionPlaintext));
615
983
  const fragmentF2Envelope = {
616
- nonce: base64ToBytes(fragmentF2Wire.nonce),
617
- ciphertext: base64ToBytes(fragmentF2Wire.ciphertext)
984
+ nonce: base64ToBytes2(fragmentF2Wire.nonce),
985
+ ciphertext: base64ToBytes2(fragmentF2Wire.ciphertext)
618
986
  };
619
987
  const reconstructed = reconstructFromPlainAndEncrypted({
620
988
  fragmentF1Plain: input.fragmentF1Plain,
@@ -632,6 +1000,24 @@ function useAccesly() {
632
1000
  unsignedXdr: sim.unsignedXdr,
633
1001
  signedAuthEntryXdr
634
1002
  });
1003
+ try {
1004
+ const username = ctx.username;
1005
+ if (username) {
1006
+ const stored = await ctx.deviceStore.loadCredential(username);
1007
+ if (stored?.walletAddress) {
1008
+ const { historyOptimisticPush: historyOptimisticPush2 } = await Promise.resolve().then(() => (init_useWalletHistory(), useWalletHistory_exports));
1009
+ historyOptimisticPush2(stored.walletAddress, {
1010
+ type: "transfer-out",
1011
+ txHash: submit.txHash,
1012
+ ledger: Math.floor(Date.now() / 1e3),
1013
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1014
+ to: input.destinationAddress,
1015
+ amountStroops: input.amountStroops
1016
+ });
1017
+ }
1018
+ }
1019
+ } catch {
1020
+ }
635
1021
  return {
636
1022
  txHash: submit.txHash,
637
1023
  status: submit.status,
@@ -675,18 +1061,18 @@ function useAccesly() {
675
1061
  "recovery.reconstructSeed: la wallet fue creada antes de Fase 1 y no tiene F2 cipher-bound a recoveryKey. No es recuperable v\xEDa OTP."
676
1062
  );
677
1063
  }
678
- const recoverySalt = base64ToBytes(frag.recoverySalt);
1064
+ const recoverySalt = base64ToBytes2(frag.recoverySalt);
679
1065
  const recoveryKey = deriveRecoveryKey({
680
1066
  password: input.cognitoPassword,
681
1067
  salt: recoverySalt
682
1068
  });
683
1069
  const f2Envelope = {
684
- ciphertext: base64ToBytes(frag.fragmentF2Recovery.ciphertext),
685
- nonce: base64ToBytes(frag.fragmentF2Recovery.nonce)
1070
+ ciphertext: base64ToBytes2(frag.fragmentF2Recovery.ciphertext),
1071
+ nonce: base64ToBytes2(frag.fragmentF2Recovery.nonce)
686
1072
  };
687
1073
  const f3Envelope = {
688
- ciphertext: base64ToBytes(frag.fragmentF3Encrypted.ciphertext),
689
- nonce: base64ToBytes(frag.fragmentF3Encrypted.nonce)
1074
+ ciphertext: base64ToBytes2(frag.fragmentF3Encrypted.ciphertext),
1075
+ nonce: base64ToBytes2(frag.fragmentF3Encrypted.nonce)
690
1076
  };
691
1077
  const seedResult = reconstructKey({
692
1078
  fragments: [
@@ -922,13 +1308,118 @@ function base64FromBytes(bytes) {
922
1308
  for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i] ?? 0);
923
1309
  return globalThis.btoa(bin);
924
1310
  }
925
- function base64ToBytes(s) {
1311
+ function base64ToBytes2(s) {
926
1312
  if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(s, "base64"));
927
1313
  const bin = globalThis.atob(s);
928
1314
  const arr = new Uint8Array(bin.length);
929
1315
  for (let i = 0; i < bin.length; i += 1) arr[i] = bin.charCodeAt(i);
930
1316
  return arr;
931
1317
  }
1318
+ var NotImplementedYetError;
1319
+ var init_useAccesly = __esm({
1320
+ "src/hooks/useAccesly.ts"() {
1321
+ init_context();
1322
+ init_config();
1323
+ init_sorobanDeployStatus();
1324
+ NotImplementedYetError = class extends Error {
1325
+ constructor(namespace, method) {
1326
+ super(
1327
+ `${namespace}.${method}() is not implemented yet. This namespace ships in a later release; see docs/Handoff_Fase7.md for the roadmap.`
1328
+ );
1329
+ this.name = "NotImplementedYetError";
1330
+ }
1331
+ };
1332
+ }
1333
+ });
1334
+
1335
+ // src/provider.tsx
1336
+ init_context();
1337
+ init_config();
1338
+ function AcceslyProvider(props) {
1339
+ const defaults = ENVIRONMENT_DEFAULTS[props.env];
1340
+ const apiUrl = props.apiUrl ?? defaults.apiUrl;
1341
+ const cognitoConfig = props.cognitoConfig ?? defaults.cognito;
1342
+ const telemetry = props.telemetry;
1343
+ const instances = useMemo(() => {
1344
+ const authClient = props.overrides?.authClient ?? new CognitoAuthClient(cognitoConfig);
1345
+ const sessionStorage = props.overrides?.sessionStorage ?? defaultSessionStorage();
1346
+ const deviceStore = props.overrides?.deviceStore ?? new InMemoryDeviceStore();
1347
+ const tokenManager = new TokenManager({ authClient, storage: sessionStorage });
1348
+ const apiClient = new AccesslyApiClient({
1349
+ baseUrl: apiUrl,
1350
+ getIdToken: () => tokenManager.getValidIdToken(),
1351
+ ...telemetry ? { telemetry } : {}
1352
+ });
1353
+ const endpoints = new AccesslyEndpoints(apiClient);
1354
+ return { authClient, sessionStorage, deviceStore, tokenManager, endpoints };
1355
+ }, [
1356
+ apiUrl,
1357
+ cognitoConfig.region,
1358
+ cognitoConfig.userPoolId,
1359
+ cognitoConfig.userPoolClientId,
1360
+ props.overrides?.authClient,
1361
+ props.overrides?.sessionStorage,
1362
+ props.overrides?.deviceStore
1363
+ ]);
1364
+ const [status, setStatus] = useState(() => initialStatus(instances.sessionStorage));
1365
+ const [username, setUsername] = useState(
1366
+ () => initialUsername(instances.sessionStorage)
1367
+ );
1368
+ const mountedRef = useRef(true);
1369
+ const refreshStatus = useCallback(async () => {
1370
+ const next = await instances.tokenManager.getStatus();
1371
+ const tokens = await Promise.resolve(instances.sessionStorage.load());
1372
+ if (mountedRef.current) {
1373
+ setStatus(next);
1374
+ setUsername(tokens?.username ?? null);
1375
+ }
1376
+ }, [instances]);
1377
+ useEffect(() => {
1378
+ mountedRef.current = true;
1379
+ void refreshStatus();
1380
+ return () => {
1381
+ mountedRef.current = false;
1382
+ };
1383
+ }, [instances]);
1384
+ const value = useMemo(
1385
+ () => ({
1386
+ appId: props.appId,
1387
+ env: props.env,
1388
+ apiUrl,
1389
+ cognitoConfig,
1390
+ authClient: instances.authClient,
1391
+ sessionStorage: instances.sessionStorage,
1392
+ tokenManager: instances.tokenManager,
1393
+ endpoints: instances.endpoints,
1394
+ deviceStore: instances.deviceStore,
1395
+ status,
1396
+ username,
1397
+ refreshStatus
1398
+ }),
1399
+ [props.appId, props.env, apiUrl, cognitoConfig, instances, status, username, refreshStatus]
1400
+ );
1401
+ return /* @__PURE__ */ jsx(AcceslyContext.Provider, { value, children: props.children });
1402
+ }
1403
+ function initialStatus(storage) {
1404
+ const tokens = storage.load();
1405
+ if (tokens instanceof Promise) return "bootstrapping";
1406
+ if (!tokens) return "anonymous";
1407
+ return Date.now() + 5 * 60 * 1e3 >= tokens.expiresAt ? "expired" : "authenticated";
1408
+ }
1409
+ function initialUsername(storage) {
1410
+ const tokens = storage.load();
1411
+ if (tokens instanceof Promise) return null;
1412
+ return tokens?.username ?? null;
1413
+ }
1414
+
1415
+ // src/index.ts
1416
+ init_context();
1417
+ init_config();
1418
+ init_useAccesly();
1419
+
1420
+ // src/hooks/useWalletStatus.ts
1421
+ init_useAccesly();
1422
+ init_config();
932
1423
 
933
1424
  // src/hooks/walletSubscription.ts
934
1425
  var ACTIVITY_BUFFER_MAX = 50;
@@ -1046,7 +1537,7 @@ function closeAllWalletSubscriptions() {
1046
1537
  // src/hooks/useWalletStatus.ts
1047
1538
  var POLL_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
1048
1539
  var STALE_THRESHOLD_MS = 6e4;
1049
- function useStableRef(value) {
1540
+ function useStableRef2(value) {
1050
1541
  const ref = useRef(value);
1051
1542
  ref.current = value;
1052
1543
  return ref;
@@ -1066,7 +1557,7 @@ function useWalletStatus() {
1066
1557
  const [isStale, setIsStale] = useState(false);
1067
1558
  const envDefaults = ENVIRONMENT_DEFAULTS[_internal.env];
1068
1559
  const streamUrl = envDefaults.walletStreamUrl;
1069
- const walletRef = useStableRef(wallet);
1560
+ const walletRef = useStableRef2(wallet);
1070
1561
  const doFetch = useCallback(async () => {
1071
1562
  if (!username) return null;
1072
1563
  try {
@@ -1090,7 +1581,7 @@ function useWalletStatus() {
1090
1581
  return null;
1091
1582
  }
1092
1583
  }, [username, walletRef]);
1093
- const doFetchRef = useStableRef(doFetch);
1584
+ const doFetchRef = useStableRef2(doFetch);
1094
1585
  useEffect(() => {
1095
1586
  if (!username) {
1096
1587
  setStatus("unknown");
@@ -1158,8 +1649,12 @@ function useWalletStatus() {
1158
1649
  }, [doFetchRef]);
1159
1650
  return { status, walletAddress, onChain, isStale, refresh };
1160
1651
  }
1652
+
1653
+ // src/hooks/useBalance.ts
1654
+ init_useAccesly();
1655
+ init_config();
1161
1656
  var POLL_FALLBACK_MS = 1e4;
1162
- function useStableRef2(value) {
1657
+ function useStableRef3(value) {
1163
1658
  const ref = useRef(value);
1164
1659
  ref.current = value;
1165
1660
  return ref;
@@ -1174,7 +1669,7 @@ function useBalance(walletAddress) {
1174
1669
  const [xlm, setXlm] = useState(null);
1175
1670
  const [isLoading, setIsLoading] = useState(true);
1176
1671
  const [error, setError] = useState(null);
1177
- const walletRef = useStableRef2(wallet);
1672
+ const walletRef = useStableRef3(wallet);
1178
1673
  useEffect(() => {
1179
1674
  if (walletAddress) {
1180
1675
  setResolvedAddress(walletAddress);
@@ -1200,7 +1695,7 @@ function useBalance(walletAddress) {
1200
1695
  }, [walletAddress, username, walletRef]);
1201
1696
  const envDefaults = ENVIRONMENT_DEFAULTS[_internal.env];
1202
1697
  const streamUrl = envDefaults.walletStreamUrl;
1203
- const endpointsRef = useStableRef2(_internal.endpoints);
1698
+ const endpointsRef = useStableRef3(_internal.endpoints);
1204
1699
  const doFetchOnce = useCallback(async () => {
1205
1700
  if (!resolvedAddress) return;
1206
1701
  try {
@@ -1214,7 +1709,7 @@ function useBalance(walletAddress) {
1214
1709
  setIsLoading(false);
1215
1710
  }
1216
1711
  }, [resolvedAddress, endpointsRef]);
1217
- const doFetchRef = useStableRef2(doFetchOnce);
1712
+ const doFetchRef = useStableRef3(doFetchOnce);
1218
1713
  useEffect(() => {
1219
1714
  if (!resolvedAddress) {
1220
1715
  setIsLoading(false);
@@ -1259,9 +1754,13 @@ function useBalance(walletAddress) {
1259
1754
  }, [doFetchRef]);
1260
1755
  return { stroops, xlm, isLoading, error, refresh };
1261
1756
  }
1757
+
1758
+ // src/hooks/useWalletActivity.ts
1759
+ init_useAccesly();
1760
+ init_config();
1262
1761
  var POLL_FALLBACK_MS2 = 25e3;
1263
1762
  var DEFAULT_LIMIT = 20;
1264
- function useStableRef3(value) {
1763
+ function useStableRef4(value) {
1265
1764
  const ref = useRef(value);
1266
1765
  ref.current = value;
1267
1766
  return ref;
@@ -1276,7 +1775,7 @@ function useWalletActivity(walletAddress, opts = {}) {
1276
1775
  const [events, setEvents] = useState([]);
1277
1776
  const [isLoading, setIsLoading] = useState(true);
1278
1777
  const [error, setError] = useState(null);
1279
- const walletRef = useStableRef3(wallet);
1778
+ const walletRef = useStableRef4(wallet);
1280
1779
  useEffect(() => {
1281
1780
  if (walletAddress) {
1282
1781
  setResolvedAddress(walletAddress);
@@ -1302,7 +1801,7 @@ function useWalletActivity(walletAddress, opts = {}) {
1302
1801
  }, [walletAddress, username, walletRef]);
1303
1802
  const envDefaults = ENVIRONMENT_DEFAULTS[_internal.env];
1304
1803
  const streamUrl = envDefaults.walletStreamUrl;
1305
- const endpointsRef = useStableRef3(_internal.endpoints);
1804
+ const endpointsRef = useStableRef4(_internal.endpoints);
1306
1805
  const doFetchOnce = useCallback(async () => {
1307
1806
  if (!resolvedAddress) return;
1308
1807
  try {
@@ -1320,7 +1819,7 @@ function useWalletActivity(walletAddress, opts = {}) {
1320
1819
  setIsLoading(false);
1321
1820
  }
1322
1821
  }, [resolvedAddress, limit, endpointsRef]);
1323
- const doFetchRef = useStableRef3(doFetchOnce);
1822
+ const doFetchRef = useStableRef4(doFetchOnce);
1324
1823
  useEffect(() => {
1325
1824
  if (!resolvedAddress) {
1326
1825
  setIsLoading(false);
@@ -1368,8 +1867,9 @@ function adaptRestEvent(ev) {
1368
1867
  }
1369
1868
 
1370
1869
  // src/index.ts
1870
+ init_useWalletHistory();
1371
1871
  var REACT_ADAPTER_VERSION = "0.0.0";
1372
1872
 
1373
- export { AcceslyContext, AcceslyProvider, ENVIRONMENT_DEFAULTS, NotImplementedYetError, REACT_ADAPTER_VERSION, closeAllWalletSubscriptions, subscribeToWalletEvent, useAccesly, useBalance, useWalletActivity, useWalletStatus };
1873
+ export { AcceslyContext, AcceslyProvider, ENVIRONMENT_DEFAULTS, NotImplementedYetError, REACT_ADAPTER_VERSION, closeAllWalletSubscriptions, historyClearOptimistic, historyOptimisticPush, subscribeToWalletEvent, useAccesly, useBalance, useWalletActivity, useWalletHistory, useWalletStatus };
1374
1874
  //# sourceMappingURL=index.js.map
1375
1875
  //# sourceMappingURL=index.js.map