@accesly/react 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,835 @@
1
+ import { Environment, CognitoConfig, AuthClient, SessionStorage, DeviceStore, TelemetrySink, TokenManager, AccesslyEndpoints, AuthStatus, CredentialRecord, EncryptedEnvelope } from '@accesly/core';
2
+ import * as react from 'react';
3
+ import { ReactNode } from 'react';
4
+
5
+ /**
6
+ * `AcceslyProvider` — top-level React provider that creates the SDK instances
7
+ * once and exposes them through context. All hooks consume this.
8
+ *
9
+ * Apps wrap their tree:
10
+ * <AcceslyProvider appId="myapp" env="dev">
11
+ * <App />
12
+ * </AcceslyProvider>
13
+ *
14
+ * For advanced cases (custom IdP, custom storage), pass `overrides` to inject
15
+ * your own `AuthClient`, `SessionStorage`, or `DeviceStore`.
16
+ */
17
+
18
+ interface AcceslyProviderProps {
19
+ readonly appId: string;
20
+ readonly env: Environment;
21
+ readonly children: ReactNode;
22
+ /** Override the resolved API URL. */
23
+ readonly apiUrl?: string;
24
+ /** Override the resolved Cognito config. */
25
+ readonly cognitoConfig?: CognitoConfig;
26
+ /** Override SDK pieces — for tests or custom backends. */
27
+ readonly overrides?: {
28
+ readonly authClient?: AuthClient;
29
+ readonly sessionStorage?: SessionStorage;
30
+ readonly deviceStore?: DeviceStore;
31
+ };
32
+ /** Optional telemetry sink — surfaces every API request/response/retry. */
33
+ readonly telemetry?: TelemetrySink;
34
+ }
35
+ declare function AcceslyProvider(props: AcceslyProviderProps): JSX.Element;
36
+
37
+ interface AcceslyContextValue {
38
+ readonly appId: string;
39
+ readonly env: Environment;
40
+ readonly apiUrl: string;
41
+ readonly cognitoConfig: CognitoConfig;
42
+ readonly authClient: AuthClient;
43
+ readonly sessionStorage: SessionStorage;
44
+ readonly tokenManager: TokenManager;
45
+ readonly endpoints: AccesslyEndpoints;
46
+ readonly deviceStore: DeviceStore;
47
+ /** Current auth status — re-rendered whenever it changes. */
48
+ readonly status: AuthStatus;
49
+ readonly username: string | null;
50
+ /** Force a re-read of `tokenManager.getStatus()`. */
51
+ readonly refreshStatus: () => Promise<void>;
52
+ }
53
+ declare const AcceslyContext: react.Context<AcceslyContextValue | null>;
54
+
55
+ /**
56
+ * Per-environment defaults — currently the only public stage is `dev`. The
57
+ * others are placeholders so the SDK API doesn't change when `staging`/`prod`
58
+ * come online (Fase 7+ / Fase 10).
59
+ */
60
+
61
+ interface EnvironmentDefaults {
62
+ readonly apiUrl: string;
63
+ /**
64
+ * Lambda Function URL del `wallet-stream` Lambda — Server-Sent Events
65
+ * que multiplexa status + balance + activity en una sola conexión.
66
+ * Si está vacío, los hooks `useBalance` / `useWalletActivity` /
67
+ * `useWalletStatus` caen al polling fallback (mucho menos eficiente).
68
+ */
69
+ readonly walletStreamUrl: string;
70
+ readonly cognito: CognitoConfig;
71
+ readonly stellar: {
72
+ readonly networkPassphrase: string;
73
+ readonly horizonUrl: string;
74
+ readonly sorobanRpcUrl: string;
75
+ /**
76
+ * Stellar G-address of the account that invokes `CreateContract` for new
77
+ * Smart Accounts. Same account the backend Lambda uses, so the wallet
78
+ * address computed client-side via `wallet.computeAddress` matches
79
+ * exactly what the backend will (or did) deploy.
80
+ */
81
+ readonly deployerAddress: string;
82
+ /**
83
+ * Address del contrato `ed25519-verifier` desplegado en la red. Necesario
84
+ * cuando el SDK construye la entrada `Signer::External(verifier, pubkey)`
85
+ * dentro del `AuthPayload` que firma — el Smart Account compara con la
86
+ * misma address que tiene almacenada en su context rule.
87
+ */
88
+ readonly ed25519VerifierAddress: string;
89
+ };
90
+ }
91
+ declare const ENVIRONMENT_DEFAULTS: Record<Environment, EnvironmentDefaults>;
92
+
93
+ /**
94
+ * `useAccesly()` — single hook with namespaces:
95
+ * - auth — signUp / confirmSignUp / signIn / signOut / status
96
+ * - wallet — createWallet, getStoredWallet
97
+ * - tx — sendPayment, signRawXdr
98
+ * - kyc — start, status
99
+ *
100
+ * The hook returns stable references when the underlying SDK instances don't
101
+ * change. Each namespace is built lazily (memoised) so apps that only use
102
+ * `auth` don't bring `wallet` into their render.
103
+ */
104
+
105
+ interface AuthNamespace {
106
+ readonly status: AuthStatus;
107
+ readonly username: string | null;
108
+ signUp(email: string, password: string): Promise<{
109
+ userSub: string;
110
+ userConfirmed: boolean;
111
+ }>;
112
+ confirmSignUp(email: string, code: string): Promise<void>;
113
+ resendConfirmation(email: string): Promise<void>;
114
+ signIn(email: string, password: string): Promise<void>;
115
+ signOut(): Promise<void>;
116
+ }
117
+ interface CreateWalletInput {
118
+ readonly email: string;
119
+ readonly emailSalt: Uint8Array;
120
+ readonly encryptionKeys: readonly [Uint8Array, Uint8Array, Uint8Array];
121
+ readonly secp256r1Pubkey: Uint8Array;
122
+ /**
123
+ * Optional. When provided together with `prfSalt`, the SDK persists a
124
+ * `CredentialRecord` to the configured `DeviceStore` BEFORE calling
125
+ * `POST /wallets`. That way, if the network request fails (timeout, tab
126
+ * close, etc.) the encrypted F1 shard + passkey metadata survive locally
127
+ * and the wallet can be recovered via `wallet.ensureWallet` on the next
128
+ * session (the backend dedupes by Cognito user → returns the same
129
+ * walletAddress).
130
+ *
131
+ * Pass it. Omitting it means an orphaned wallet on POST failure is
132
+ * unrecoverable without a server-side query.
133
+ */
134
+ readonly credentialId?: Uint8Array;
135
+ /** Optional. See `credentialId`. */
136
+ readonly prfSalt?: Uint8Array;
137
+ /**
138
+ * Password de Cognito en plano (`Uint8Array` codificado UTF-8).
139
+ *
140
+ * Recovery v2 (Fase 1, 2026-06-15): si se provee, el SDK deriva
141
+ * `recoveryKey = PBKDF2(password, recoverySalt, 600k)` y la usa para
142
+ * cifrar F3 antes de enviarlo al backend, en vez de usar
143
+ * `encryptionKeys[2]`. El backend almacena F3 cifrado con esa key —
144
+ * SOLO descifrable client-side con el mismo password.
145
+ *
146
+ * Sin esta prop el wallet se crea pero NO podrá recuperarse vía OTP
147
+ * (F3 quedará cifrado con `encryptionKeys[2]`, igual que en 0.x).
148
+ *
149
+ * El caller es responsable de zeroizar este buffer tras `createWallet`
150
+ * (el SDK no lo retiene en memoria).
151
+ */
152
+ readonly cognitoPassword?: Uint8Array;
153
+ /**
154
+ * 32-byte salt para HKDF — si se persiste en el `CredentialRecord`,
155
+ * `wallet.unlockForSigning` lo recupera y re-deriva las mismas keys sin
156
+ * intervención del caller. Si se omite, el SDK genera uno random y lo
157
+ * persiste igualmente. Si la wallet ya existe en el `DeviceStore` con un
158
+ * `encryptionSalt`, este input se ignora a favor del salt persistido (para
159
+ * preservar la decriptabilidad de F1).
160
+ */
161
+ readonly encryptionSalt?: Uint8Array;
162
+ }
163
+ interface CreatedWalletInfo {
164
+ readonly walletAddress: string;
165
+ readonly publicKey: Uint8Array;
166
+ /**
167
+ * `'on-chain'` if the backend confirmed deploy; `'pending-deploy'` if the
168
+ * backend submitted to Soroban but the contract did not land (typically
169
+ * because the Smart Account constructor exceeds Soroban v26 resource caps
170
+ * — Phase 1 territory). When pending, the `walletAddress` is the
171
+ * client-side-predicted address: same address that will be live once the
172
+ * deploy succeeds (idempotent on the backend).
173
+ */
174
+ readonly status: WalletStatus;
175
+ /** When `status === 'pending-deploy'`, the backend's reason if available. */
176
+ readonly pendingReason?: string;
177
+ }
178
+
179
+ type WalletStatus =
180
+ /** Backend confirmed the contract is live on Soroban. Ready to use. */
181
+ 'on-chain'
182
+ /**
183
+ * Wallet record exists (locally and/or on backend) but the contract has NOT
184
+ * been observed on Soroban yet. Either deploy is still in flight, or
185
+ * landed in a ghost state. Re-run `wallet.ensureWallet` later (or call
186
+ * `wallet.retryDeploy(username)`).
187
+ */
188
+ | 'pending-deploy'
189
+ /**
190
+ * Backend has the record but its Soroban RPC check did not respond — the
191
+ * SDK treats the address as usable but flags the uncertainty.
192
+ */
193
+ | 'unknown';
194
+ interface EnsureWalletResult {
195
+ readonly walletAddress: string;
196
+ readonly status: WalletStatus;
197
+ /** True if this call generated a new keypair (first-time deploy path). */
198
+ readonly createdNow: boolean;
199
+ /** Present only when `createdNow === true`. */
200
+ readonly publicKey?: Uint8Array;
201
+ }
202
+ interface RemoteWalletInfo {
203
+ readonly walletAddress: string;
204
+ readonly appId: string;
205
+ readonly createdAt: string;
206
+ readonly onChain: boolean | null;
207
+ }
208
+ interface RetryDeployResult {
209
+ readonly walletAddress: string;
210
+ readonly status: WalletStatus;
211
+ }
212
+ /**
213
+ * Input para `wallet.bootstrap(...)` — el high-level "todo en uno" que cualquier
214
+ * integrador típico va a llamar como entry point. Hace **TODO**:
215
+ *
216
+ * 1. `sha256(email)` → userId opaco para el RP.
217
+ * 2. Genera `prfSalt` aleatorio (32 bytes).
218
+ * 3. `registerPasskey(...)` con extensión PRF — pide huella/face/Hello.
219
+ * 4. Genera `encryptionSalt` aleatorio (32 bytes).
220
+ * 5. HKDF(prfOutput, encryptionSalt, "accesly-{f1,f2,f3}-encryption") → 3 keys AES.
221
+ * 6. Genera `emailSalt` aleatorio (32 bytes).
222
+ * 7. `wallet.ensureWallet(...)` — el flujo idempotente get-or-create.
223
+ * 8. Persiste `encryptionSalt` en `CredentialRecord` para que `unlockForSigning`
224
+ * pueda re-derivar las mismas keys.
225
+ * 9. Zeroiza `prfOutput`, `f1Key`, `f2Key`, `f3Key`, `cognitoPasswordBytes`.
226
+ *
227
+ * Después de `bootstrap`, llamá `tx.send(...)` directamente con
228
+ * `wallet.unlockForSigning(email)` — no hace falta más wiring.
229
+ */
230
+ interface BootstrapWalletInput {
231
+ readonly email: string;
232
+ /**
233
+ * Password de Cognito en plano. Usado para:
234
+ * - Re-cifrar F3 (y F2_recovery) con `recoveryKey = PBKDF2(password,
235
+ * recoverySalt, 600k)` para el flujo de Recovery v2.
236
+ *
237
+ * El SDK lo zeroiza tras el ensureWallet.
238
+ */
239
+ readonly password: string;
240
+ /**
241
+ * Overrides opcionales para el `registerPasskey` interno. Caso típico:
242
+ * cambiar `rpName: 'MiApp'` para que el browser muestre tu marca en el
243
+ * prompt biométrico. `rpId` por default = `window.location.hostname`.
244
+ */
245
+ readonly passkey?: {
246
+ readonly rpId?: string;
247
+ readonly rpName?: string;
248
+ };
249
+ }
250
+ /**
251
+ * Material reconstruido por `wallet.unlockForSigning(...)` y listo para pasar
252
+ * a `tx.send(...)`. Las llaves se zeroizan tras la firma; el caller no debería
253
+ * retenerlas más allá del round-trip.
254
+ */
255
+ interface UnlockedSigningMaterial {
256
+ /** F1 share desencriptado (Shamir-encoded blob). Lo zero-iza `tx.send`. */
257
+ readonly fragmentF1Plain: Uint8Array;
258
+ /** AES key con la que el SDK desencripta el envelope F2 del backend. */
259
+ readonly fragmentF2Key: Uint8Array;
260
+ /** Pubkey ed25519 del owner del Smart Account (32 bytes). */
261
+ readonly ownerPubkey: Uint8Array;
262
+ /** Address C… del Smart Account (display only). */
263
+ readonly walletAddress: string;
264
+ }
265
+ interface WalletNamespace {
266
+ /**
267
+ * **Entry-point recomendado.** Registra passkey + deriva keys + crea-o-recupera
268
+ * wallet en una sola llamada. Sustituye al patrón histórico de
269
+ * `ensureWalletWithPasskey` que tenía que copiar-pegar cada integrador.
270
+ *
271
+ * Idempotente: si ya hay wallet en el backend para este Cognito user, devuelve
272
+ * `{createdNow: false}` sin re-registrar passkey. Si no, hace el flow completo.
273
+ */
274
+ bootstrap(input: BootstrapWalletInput): Promise<EnsureWalletResult>;
275
+ /**
276
+ * **Helper para `tx.send`.** Lee la credencial del DeviceStore, abre el
277
+ * passkey via WebAuthn (PIN/biométrico), re-deriva las AES keys con HKDF, y
278
+ * descifra F1 local. Devuelve los 3 materiales que `tx.send` necesita.
279
+ *
280
+ * Lanza con mensaje claro si:
281
+ * - no hay credential local (el user tiene que correr `recovery.finalize`
282
+ * en este device);
283
+ * - el passkey no devolvió PRF (browser sin soporte);
284
+ * - el envelope F1 falla al descifrar (corrupto / passkey distinto).
285
+ */
286
+ unlockForSigning(username: string): Promise<UnlockedSigningMaterial>;
287
+ /**
288
+ * End-to-end wallet creation:
289
+ * 1. Generate keypair + Shamir split + encrypt fragments (client-side).
290
+ * 2. Compute the Smart Account address client-side from the ed25519
291
+ * pubkey + the env-configured deployer (deterministic — matches what
292
+ * the backend will deploy).
293
+ * 3. If `credentialId` + `prfSalt` were provided, persist a full
294
+ * `CredentialRecord` (with all 3 encrypted fragments + computed
295
+ * walletAddress + `onChain: null`) to the `DeviceStore` BEFORE the
296
+ * network call — crash-safety + retry capability in one step.
297
+ * 4. POST /wallets with hex pubkeys + base64 fragments.
298
+ * 5. On success, mark the record `onChain: true` (cleared by the next
299
+ * `ensureWallet` call which queries Soroban via the backend).
300
+ *
301
+ * The caller is responsible for the encryption-key derivation (typically
302
+ * via WebAuthn PRF).
303
+ */
304
+ createWallet(input: CreateWalletInput): Promise<CreatedWalletInfo>;
305
+ /**
306
+ * Idempotent wallet bootstrap. The recommended entry-point at the top of
307
+ * every authenticated session:
308
+ *
309
+ * - `GET /wallets` → if `onChain === true`, returns `{ status: 'on-chain' }`
310
+ * and skips keypair generation entirely.
311
+ * - `GET /wallets` → if `onChain === false`, calls `retryDeploy` to
312
+ * re-submit the existing record (idempotent on the backend) and
313
+ * returns `{ status: 'pending-deploy' }` if the retry didn't surface
314
+ * success yet.
315
+ * - `GET /wallets` → if `onChain === null` (Soroban RPC unreachable),
316
+ * returns `{ status: 'unknown' }` — the address is usable but the
317
+ * SDK couldn't confirm on-chain presence.
318
+ * - `GET /wallets` → 404 → runs the full `createWallet` flow and returns
319
+ * `{ status: 'pending-deploy', createdNow: true }`. Subsequent calls
320
+ * will upgrade to `'on-chain'` once the backend's Soroban check passes.
321
+ */
322
+ ensureWallet(input: CreateWalletInput): Promise<EnsureWalletResult>;
323
+ /**
324
+ * Re-submits `POST /wallets` for an existing local `CredentialRecord`. Used
325
+ * to recover from ghost wallets (record exists but deploy did not land).
326
+ * Requires the record to have been persisted with `fragmentF2Encrypted`,
327
+ * `fragmentF3Encrypted`, `publicKey`, and `emailCommitment` (which
328
+ * `createWallet` does automatically when `credentialId` + `prfSalt` are
329
+ * provided).
330
+ *
331
+ * Backend dedupes by ownerPubkey — the returned address is guaranteed to
332
+ * equal the one originally stored.
333
+ */
334
+ retryDeploy(username: string): Promise<RetryDeployResult>;
335
+ /**
336
+ * Reads the user's wallet metadata from the backend. Returns null if the
337
+ * user has not yet created a wallet.
338
+ */
339
+ fetchRemote(): Promise<RemoteWalletInfo | null>;
340
+ /**
341
+ * Computes the deterministic Smart Account address that the backend will
342
+ * (or did) deploy for the given ed25519 owner pubkey. Same algorithm
343
+ * Stellar Core uses — pure client-side, no network call. Useful to show
344
+ * the address to the user instantly before any POST.
345
+ */
346
+ computeAddress(ownerPubkey: Uint8Array): Promise<string>;
347
+ /** Returns the locally-stored credential record, if any. */
348
+ getStoredCredential(username: string): Promise<CredentialRecord | null>;
349
+ /**
350
+ * Lists `CredentialRecord`s whose `walletAddress` is still `null` OR whose
351
+ * `onChain` flag is `false`. Diagnostic + recovery aid.
352
+ */
353
+ getPendingWallets(): Promise<readonly CredentialRecord[]>;
354
+ /** Removes a stored credential. Useful after a failed pending wallet is reconciled. */
355
+ clearStoredCredential(username: string): Promise<void>;
356
+ /**
357
+ * Testnet only — fondea el Smart Account con XLM via Stellar friendbot.
358
+ *
359
+ * Friendbot acepta directamente direcciones de contrato Soroban (`C…`):
360
+ * internamente arma una tx `invokeContract(XLM_SAC.transfer, ...)` desde
361
+ * la cuenta de la SDF y la submitea. Resultado: ~10,000 XLM testnet al
362
+ * Smart Account, sin necesidad de un G-account intermediario.
363
+ *
364
+ * Idempotente: el primer call exitoso marca `testnetFunded: true` en el
365
+ * `CredentialRecord` local; subsiguientes calls devuelven `alreadyFunded:
366
+ * true` sin hacer otro round-trip a friendbot.
367
+ *
368
+ * En `env: 'mainnet'` la función es un no-op (no existe friendbot en
369
+ * mainnet) y devuelve `{ funded: false, alreadyFunded: false, reason:
370
+ * 'mainnet-not-supported' }`. La UI tiene que mostrar opciones de onramp
371
+ * real (Etherfuse, MoonPay, transferencia externa).
372
+ *
373
+ * `ensureWallet` lo llama automáticamente fire-and-forget cuando el
374
+ * status final es `'on-chain'` — el caller solo necesita llamarlo
375
+ * explícitamente si quiere mostrar feedback en la UI durante el funding.
376
+ */
377
+ fundTestnet(walletAddress: string): Promise<FundTestnetResult>;
378
+ }
379
+ interface FundTestnetResult {
380
+ /** `true` si esta llamada disparó friendbot y fondeó la wallet ahora. */
381
+ readonly funded: boolean;
382
+ /**
383
+ * `true` si la wallet ya había sido fondeada antes (flag local o response
384
+ * de friendbot indicando que la cuenta ya existe). Igualmente válido —
385
+ * la UI puede mostrar "ya tienes XLM" sin pedir acción del user.
386
+ */
387
+ readonly alreadyFunded: boolean;
388
+ /** Texto explicativo para no-op cases (mainnet, missing record, etc.). */
389
+ readonly reason?: 'mainnet-not-supported' | 'friendbot-error' | 'already-funded' | 'funded-now';
390
+ }
391
+ /**
392
+ * Input para `tx.send(...)` — manda XLM desde el Smart Account del usuario a
393
+ * cualquier address Stellar (G… clásico o C… contrato).
394
+ *
395
+ * El SDK orquesta todo el flujo: simulate → ECDH F2 → reconstruct seed →
396
+ * sign auth entry → submit. El caller solo entrega los inputs sensibles que
397
+ * vienen de su flow de unlock (WebAuthn PRF + derivación de F2 key).
398
+ */
399
+ interface SendXlmInput {
400
+ /** Destinatario. G… (clásico) o C… (contrato). */
401
+ readonly destinationAddress: string;
402
+ /** Monto en STROOPS (1 XLM = 10_000_000 stroops). Base-10 string para evitar precisión. */
403
+ readonly amountStroops: string;
404
+ /**
405
+ * F1 (Shamir share encoded incluyendo el byte de índice) ya en plano —
406
+ * típicamente desencriptado client-side via WebAuthn PRF antes de llamar.
407
+ * El SDK lo zero-iza tras combinar con F2.
408
+ */
409
+ readonly fragmentF1Plain: Uint8Array;
410
+ /**
411
+ * Llave AES-256 con la que el SDK desencripta el F2 envelope que vino
412
+ * del backend. La derivación de esta llave es responsabilidad del caller
413
+ * (usualmente PBKDF2 sobre material derivado de credenciales del user).
414
+ * Se zero-iza al terminar.
415
+ */
416
+ readonly fragmentF2Key: Uint8Array;
417
+ /**
418
+ * Pubkey ed25519 (32 bytes) del owner del Smart Account. Se usa para:
419
+ * 1) Sanity-check de que la seed reconstruida deriva esta pubkey.
420
+ * 2) Empaquetarla dentro del `Signer::External(verifier, pubkey)` del
421
+ * AuthPayload.
422
+ */
423
+ readonly ownerPubkey: Uint8Array;
424
+ }
425
+ interface SendXlmResult {
426
+ readonly txHash: string;
427
+ readonly status: string;
428
+ readonly explorerUrl: string;
429
+ }
430
+ interface TxNamespace {
431
+ /**
432
+ * End-to-end XLM transfer desde el Smart Account del user.
433
+ *
434
+ * Flujo interno (no-custodial):
435
+ * 1) `POST /tx/simulate` con `{ amountStroops, destinationAddress }`.
436
+ * 2) Genera X25519 keypair efímero + `POST /fragments/2` con la pubkey.
437
+ * Backend devuelve F2 wrapped en una capa session-key. El SDK la
438
+ * descifra con ECDH + HKDF — la session key NO persiste en disco.
439
+ * 3) Desencripta el F2 envelope interno con `fragmentF2Key` → F2 plain.
440
+ * 4) Combina F1 + F2 vía Shamir → ed25519 seed (32 bytes).
441
+ * 5) Computa `auth_digest = sha256(signature_payload || rule_ids_xdr)`
442
+ * y lo firma con la seed → 64-byte ed25519 sig.
443
+ * 6) Empaqueta el `AuthPayload {signers, context_rule_ids}` ScVal,
444
+ * reemplaza `credentials.address.signature` en la placeholder entry.
445
+ * 7) `POST /tx/submit` con `{ unsignedXdr, signedAuthEntryXdr }`.
446
+ * 8) Devuelve `{ txHash, status, explorerUrl }`.
447
+ *
448
+ * Toda llave plana sale de scope tras la firma. Lanza si:
449
+ * - El backend rechaza simulate/submit.
450
+ * - La reconstrucción Shamir falla (fragmentos no compatibles).
451
+ * - La pubkey derivada de la seed no matchea `ownerPubkey`.
452
+ */
453
+ send(input: SendXlmInput): Promise<SendXlmResult>;
454
+ /**
455
+ * Bajo nivel: firma un envelope Stellar ya construido con una seed ed25519
456
+ * dada. Útil para flows custom que arman la tx fuera del SDK.
457
+ */
458
+ signRawXdr(input: {
459
+ transactionXdr: string;
460
+ ed25519Seed: Uint8Array;
461
+ expectedPublicKey?: Uint8Array;
462
+ }): Promise<{
463
+ signedXdr: string;
464
+ publicKey: Uint8Array;
465
+ }>;
466
+ }
467
+ interface KycNamespace {
468
+ start(): Promise<{
469
+ customerId: string;
470
+ status: string;
471
+ hostedUrl: string | null;
472
+ }>;
473
+ status(): Promise<{
474
+ customerId: string;
475
+ status: string;
476
+ hostedUrl: string | null;
477
+ }>;
478
+ }
479
+ interface SessionNamespace {
480
+ /** Create a temporary session key for unattended low-value tx (Soroban policy). */
481
+ create(_opts: {
482
+ readonly ttlSeconds: number;
483
+ readonly maxAmountStroops: string;
484
+ }): Promise<never>;
485
+ /** Revoke a previously-created session key. */
486
+ revoke(_sessionKeyId: string): Promise<never>;
487
+ }
488
+ interface SettingsNamespace {
489
+ /** Add a new device's passkey to an existing wallet (multi-device). */
490
+ addDevice(_secp256r1Pubkey: Uint8Array): Promise<never>;
491
+ /** Remove a device's passkey from the wallet. */
492
+ removeDevice(_secp256r1Pubkey: Uint8Array): Promise<never>;
493
+ /** List all device passkeys registered for the wallet. */
494
+ listDevices(): Promise<never>;
495
+ /** Change the spending limit policy. */
496
+ updateSpendingLimit(_opts: {
497
+ readonly limitStroops: string;
498
+ readonly perDayStroops?: string;
499
+ }): Promise<never>;
500
+ }
501
+ interface YieldNamespace {
502
+ /** Invest USDC into CETES via Etherfuse (50/50 yield share with Accesly). */
503
+ invest(_amountUsdc: string): Promise<never>;
504
+ /** Redeem CETES tokens back into USDC. */
505
+ redeem(_amountTokens: string): Promise<never>;
506
+ /** Read the user's current yield position. */
507
+ position(): Promise<never>;
508
+ }
509
+ /**
510
+ * Stub thrown by every method in the `session`, `settings`, `yield`
511
+ * namespaces. These features are designed but not implemented in v0.1.0 —
512
+ * they unblock with the dashboard work in Fase 7 (`session`/`settings`) and
513
+ * with Etherfuse activation (`yield`).
514
+ */
515
+ declare class NotImplementedYetError extends Error {
516
+ constructor(namespace: string, method: string);
517
+ }
518
+ /**
519
+ * Recovery v2 — OTP por email + password de Cognito (Fase 1, 2026-06-15).
520
+ *
521
+ * Flujo desde la UI:
522
+ * 1. `recovery.requestOtp({ email })` → manda el OTP por SES.
523
+ * 2. `recovery.verifyOtp({ email, code })` → devuelve `recoveryJwt`.
524
+ * 3. `recovery.finalize({ email, password, recoveryJwt })` orquesta todo:
525
+ * - GET /fragments/3 con el JWT
526
+ * - Deriva recoveryKey con el password + recoverySalt del backend
527
+ * - Decifra F3
528
+ * - Decifra F2 (vía session key ECDH)
529
+ * - Combina F2+F3 → seed ed25519 reconstruida
530
+ * - Genera new passkey + new Shamir split (F1', F2', F3')
531
+ * - Re-cifra F3' con la misma recoveryKey + nuevo salt
532
+ * - Firma la tx `rotate_signer` localmente
533
+ * - POST /recovery/finalize con todo
534
+ * - Persiste new CredentialRecord local
535
+ * - Zero-iza la seed
536
+ */
537
+ interface ReconstructedSeed {
538
+ /** 32-byte ed25519 seed reconstruida vía Shamir(F2_recovery + F3). CALLER ZEROIZE. */
539
+ readonly privateSeed: Uint8Array;
540
+ /** 32-byte ed25519 public key derivada. */
541
+ readonly publicKey: Uint8Array;
542
+ /** 32-byte recoveryKey derivada del password — útil para re-cifrar F2'/F3' nuevos. */
543
+ readonly recoveryKey: Uint8Array;
544
+ /** Base64 32-byte salt que vino del backend. */
545
+ readonly recoverySalt: string;
546
+ }
547
+ /**
548
+ * Input para `recovery.finalize(...)` — **orquestador end-to-end**. El integrador
549
+ * solo provee email + password + recoveryJwt; el SDK hace TODO el resto:
550
+ *
551
+ * 1. `reconstructSeed(...)` con el password → seed VIEJA + recoveryKey.
552
+ * 2. `registerPasskey(...)` con PRF → nuevo credentialId + secp256r1Pubkey + prfOutput.
553
+ * 3. HKDF(prfOutput, encryptionSalt, "accesly-{f1,f2}-encryption") → newF1Key + newF2Key.
554
+ * 4. Genera new ed25519 seed + Shamir 2-of-3 + cifra F1'/F2' con PRF keys, F3' con recoveryKey.
555
+ * 5. `POST /recovery/simulate-rotate-signer` → backend arma + simula.
556
+ * 6. Firma `SorobanAuthorizationEntry` con la SEED VIEJA contra `admin-cfg`.
557
+ * 7. `POST /recovery/finalize` con auth entry firmada + new fragments.
558
+ * 8. Persiste new `CredentialRecord` (con `encryptionSalt`) en `DeviceStore`.
559
+ * 9. Zeroiza TODAS las llaves intermedias (privateSeed vieja, recoveryKey, PRF output, password).
560
+ */
561
+ interface FinalizeRecoveryInput {
562
+ /** Email del usuario (case-insensitive, se normaliza). */
563
+ readonly email: string;
564
+ /**
565
+ * Password de Cognito (string). El SDK lo encoda a UTF-8 internamente y lo
566
+ * zeroiza tras la operación. No retengas referencias.
567
+ */
568
+ readonly password: string;
569
+ /** Token KMS-HMAC que devolvió `verifyOtp()` — TTL 5min. */
570
+ readonly recoveryJwt: string;
571
+ /**
572
+ * Overrides opcionales para el `registerPasskey` interno (mismo shape que
573
+ * `wallet.bootstrap`). Caso típico: `rpName: 'MiApp'`.
574
+ */
575
+ readonly passkey?: {
576
+ readonly rpId?: string;
577
+ readonly rpName?: string;
578
+ };
579
+ }
580
+ interface FinalizeRecoveryResult {
581
+ readonly walletAddress: string;
582
+ readonly txHash: string;
583
+ readonly status: string;
584
+ /** Pubkey ed25519 NUEVA (32 bytes). Útil para UI confirmación. */
585
+ readonly newPublicKey: Uint8Array;
586
+ /** Link al explorer. */
587
+ readonly explorerUrl: string;
588
+ }
589
+ interface RecoveryNamespace {
590
+ /** Pide OTP. Backend rate-limita; el caller debe respetar `cooldownSeconds`. */
591
+ requestOtp(input: {
592
+ email: string;
593
+ }): Promise<{
594
+ cooldownSeconds: number;
595
+ expiresInSeconds: number;
596
+ }>;
597
+ /** Verifica OTP. Devuelve `recoveryJwt` con TTL 5min. */
598
+ verifyOtp(input: {
599
+ email: string;
600
+ code: string;
601
+ }): Promise<{
602
+ recoveryJwt: string;
603
+ expiresAt: number;
604
+ }>;
605
+ /**
606
+ * Descarga `/fragments/3`, descifra F2_recovery + F3 con la `recoveryKey`
607
+ * derivada del password y reconstruye la seed via Shamir.
608
+ *
609
+ * El caller DEBE zero-izar `result.privateSeed` y `result.recoveryKey`
610
+ * tras firmar la rotación + cifrar las nuevas F1'/F2'/F3'.
611
+ *
612
+ * El caller también es responsable de zeroizar `cognitoPassword` después.
613
+ */
614
+ reconstructSeed(input: {
615
+ cognitoPassword: Uint8Array;
616
+ recoveryJwt: string;
617
+ }): Promise<ReconstructedSeed>;
618
+ /**
619
+ * Orquestador completo de la rotación de signers para Recovery v2.
620
+ * Ver `FinalizeRecoveryInput` para los pre-requisitos.
621
+ */
622
+ finalize(input: FinalizeRecoveryInput): Promise<FinalizeRecoveryResult>;
623
+ /**
624
+ * Bajo nivel: submitea la rotación al backend tras que el caller haya
625
+ * armado el body manualmente. `finalize(...)` es el wrapper recomendado.
626
+ */
627
+ submitFinalize(input: {
628
+ recoveryJwt: string;
629
+ unsignedXdr: string;
630
+ signedAuthEntryXdr: string;
631
+ newOwnerEd25519Pubkey: string;
632
+ newSecp256r1Pubkey: string;
633
+ newFragmentF1Encrypted: EncryptedEnvelope;
634
+ newFragmentF2Encrypted: EncryptedEnvelope;
635
+ newFragmentF2Recovery: EncryptedEnvelope;
636
+ newFragmentF3Encrypted: EncryptedEnvelope;
637
+ newRecoverySalt: string;
638
+ newEmailCommitment: string;
639
+ }): Promise<{
640
+ walletAddress: string;
641
+ txHash: string;
642
+ status: string;
643
+ }>;
644
+ }
645
+ interface AcceslyHook {
646
+ readonly auth: AuthNamespace;
647
+ readonly wallet: WalletNamespace;
648
+ readonly tx: TxNamespace;
649
+ readonly kyc: KycNamespace;
650
+ readonly recovery: RecoveryNamespace;
651
+ readonly session: SessionNamespace;
652
+ readonly settings: SettingsNamespace;
653
+ readonly yieldOps: YieldNamespace;
654
+ /** Raw context, for advanced use cases (custom telemetry, manual refresh). */
655
+ readonly _internal: AcceslyContextValue;
656
+ }
657
+ declare function useAccesly(): AcceslyHook;
658
+
659
+ /**
660
+ * `useWalletStatus()` — status on-chain del Smart Account del user actual con
661
+ * push real-time vía SSE.
662
+ *
663
+ * Reemplaza el polling cada 30s del legacy. Comportamiento:
664
+ *
665
+ * 1. SSE-first: si `walletStreamUrl` está configurado y `EventSource` existe,
666
+ * se suscribe al canal `status` del `wallet-stream` Lambda. Cero polling.
667
+ * 2. Fallback a polling backoff (1s → 30s) si SSE no está disponible.
668
+ * 3. Pausa cuando `document.hidden`, retoma al volver.
669
+ * 4. `refresh()` fuerza fetch HTTP inmediato.
670
+ *
671
+ * El status del Smart Account vive en DDB (lo que el backend reporta de su
672
+ * verificación contra Soroban). El backend push-ea cambios cuando los detecta
673
+ * (cada 5s en el loop del wallet-stream).
674
+ */
675
+ type WalletStatusValue = 'on-chain' | 'pending-deploy' | 'unknown' | 'no-wallet';
676
+ interface UseWalletStatusResult {
677
+ readonly status: WalletStatusValue;
678
+ readonly walletAddress: string | null;
679
+ readonly onChain: boolean | null;
680
+ readonly isStale: boolean;
681
+ refresh(): Promise<void>;
682
+ }
683
+ declare function useWalletStatus(): UseWalletStatusResult;
684
+
685
+ /**
686
+ * `useBalance(walletAddress?)` — devuelve el balance XLM del Smart Account
687
+ * con push real-time vía SSE.
688
+ *
689
+ * Si SSE está configurado (env tiene `walletStreamUrl` y `EventSource` existe),
690
+ * el hook se suscribe al canal `balance` del `wallet-stream` Lambda y se
691
+ * actualiza instantáneamente cuando cambia el balance on-chain.
692
+ *
693
+ * Fallback automático a polling cada 10s si SSE no está disponible (entorno
694
+ * que no lo soporta o backend self-hosteado sin el endpoint).
695
+ *
696
+ * El `walletAddress` se auto-resuelve desde el `DeviceStore` si no se pasa
697
+ * (cubrir el caso "wallet del user actual sin tener que pasarla a mano").
698
+ */
699
+ interface UseBalanceResult {
700
+ /** Stroops como string base-10. `null` mientras se carga o no hay address. */
701
+ readonly stroops: string | null;
702
+ /** Mismo balance como string decimal en XLM. `null` mientras se carga. */
703
+ readonly xlm: string | null;
704
+ readonly isLoading: boolean;
705
+ readonly error: Error | null;
706
+ /** Fuerza fetch HTTP inmediato (útil tras una operación del user). */
707
+ refresh(): Promise<void>;
708
+ }
709
+ declare function useBalance(walletAddress?: string | null): UseBalanceResult;
710
+
711
+ /**
712
+ * `WalletSubscription` — singleton manager por wallet address que abre UNA
713
+ * sola conexión `EventSource` al `wallet-stream` Lambda y desmultiplexa los
714
+ * eventos a múltiples subscribers (hooks).
715
+ *
716
+ * Beneficios:
717
+ * - 3 hooks (`useBalance`, `useWalletActivity`, `useWalletStatus`) sobre la
718
+ * misma wallet → 1 sola conexión TCP, no 3 polls separados.
719
+ * - Auto-reconnect cuando el server cierra (default de EventSource — `retry:`).
720
+ * - Ref-count: la conexión se abre con el primer subscriber y se cierra
721
+ * cuando no quedan listeners (`useEffect` cleanup en cada hook).
722
+ * - Eventos cacheados: el último valor recibido de cada tipo se replay-ea
723
+ * a nuevos subscribers para que no esperen el próximo push.
724
+ *
725
+ * Sin SSE configurado (`walletStreamUrl` vacío) o cuando `EventSource` no
726
+ * existe (SSR / workers), los hooks individuales detectan el flag
727
+ * `unavailable` y caen al polling fallback que ya existe.
728
+ */
729
+ type WalletStreamEventType = 'status' | 'balance' | 'activity';
730
+ interface WalletStreamStatusPayload {
731
+ readonly walletAddress: string | null;
732
+ readonly onChain: boolean | null;
733
+ }
734
+ interface WalletStreamBalancePayload {
735
+ readonly stroops: string;
736
+ readonly xlm: string;
737
+ }
738
+ /**
739
+ * Activity emitida por el backend ya **filtrada y tipada** — solo eventos
740
+ * relevantes para una wallet de Accesly (rotación de signers + transfers
741
+ * IN/OUT de XLM). Otros eventos on-chain (debug, errores host, eventos
742
+ * irrelevantes) se descartan en el server.
743
+ */
744
+ type WalletActivityItem = {
745
+ readonly type: 'signer-rotated';
746
+ readonly txHash: string;
747
+ readonly ledger: number;
748
+ readonly timestamp: string | null;
749
+ readonly newOwnerEd25519Hex: string;
750
+ } | {
751
+ readonly type: 'transfer-in';
752
+ readonly txHash: string;
753
+ readonly ledger: number;
754
+ readonly timestamp: string | null;
755
+ readonly from: string;
756
+ readonly amountStroops: string;
757
+ } | {
758
+ readonly type: 'transfer-out';
759
+ readonly txHash: string;
760
+ readonly ledger: number;
761
+ readonly timestamp: string | null;
762
+ readonly to: string;
763
+ readonly amountStroops: string;
764
+ };
765
+ interface WalletStreamActivityPayload {
766
+ readonly events: readonly WalletActivityItem[];
767
+ }
768
+ type Listener<T> = (payload: T) => void;
769
+ /**
770
+ * Suscribirse a un tipo de evento de la wallet. Devuelve una función
771
+ * de cleanup que el caller (típicamente `useEffect`) debe llamar para
772
+ * desuscribir y, eventualmente, cerrar la conexión SSE.
773
+ *
774
+ * Si SSE no está disponible (URL vacía, EventSource undefined), devuelve
775
+ * `null` para indicar al caller que use el fallback de polling.
776
+ */
777
+ declare function subscribeToWalletEvent<T extends WalletStreamEventType>(streamUrl: string, walletAddress: string, eventType: T, listener: T extends 'status' ? Listener<WalletStreamStatusPayload> : T extends 'balance' ? Listener<WalletStreamBalancePayload> : Listener<WalletStreamActivityPayload>): (() => void) | null;
778
+ /**
779
+ * Cierra TODAS las conexiones SSE activas. Útil para tests o cuando el user
780
+ * cierra sesión y querés limpiar conexiones colgadas. Llamar en `auth.signOut`
781
+ * lo tienen pendiente como mejora — por ahora el cleanup natural de los
782
+ * unmount basta.
783
+ */
784
+ declare function closeAllWalletSubscriptions(): void;
785
+
786
+ /**
787
+ * `useWalletActivity(walletAddress?, opts?)` — actividad on-chain relevante de
788
+ * la wallet (rotate_signer + transfers in/out de XLM) con push real-time vía SSE.
789
+ *
790
+ * El backend YA filtra eventos irrelevantes — el integrador solo recibe
791
+ * `WalletActivityItem` tipados (`signer-rotated` | `transfer-in` | `transfer-out`).
792
+ * Sin parsing manual de XDR ni lógica de filtrado client-side.
793
+ *
794
+ * Fallback: si SSE no está disponible, polling cada 25s al endpoint
795
+ * `/wallets/:address/activity`. Mucho menos eficiente pero garantiza data.
796
+ */
797
+
798
+ interface UseWalletActivityOptions {
799
+ /** Cantidad de eventos a mostrar (cap del buffer del cliente). Default 20. */
800
+ readonly limit?: number;
801
+ }
802
+ interface UseWalletActivityResult {
803
+ /** Eventos tipados, más recientes primero. */
804
+ readonly events: readonly WalletActivityItem[];
805
+ readonly isLoading: boolean;
806
+ readonly error: Error | null;
807
+ refresh(): Promise<void>;
808
+ }
809
+ declare function useWalletActivity(walletAddress?: string | null, opts?: UseWalletActivityOptions): UseWalletActivityResult;
810
+
811
+ /**
812
+ * @accesly/react — React Provider + `useAccesly` hook.
813
+ *
814
+ * Wraps `@accesly/core` for React 18+. Apps integrate with:
815
+ *
816
+ * import { AcceslyProvider, useAccesly } from '@accesly/react';
817
+ *
818
+ * function App() {
819
+ * return (
820
+ * <AcceslyProvider appId="my-app" env="dev">
821
+ * <YourApp />
822
+ * </AcceslyProvider>
823
+ * );
824
+ * }
825
+ *
826
+ * function Login() {
827
+ * const { auth } = useAccesly();
828
+ * return (
829
+ * <button onClick={() => auth.signIn(email, password)}>Sign in</button>
830
+ * );
831
+ * }
832
+ */
833
+ declare const REACT_ADAPTER_VERSION = "0.0.0";
834
+
835
+ export { AcceslyContext, type AcceslyContextValue, type AcceslyHook, AcceslyProvider, type AcceslyProviderProps, type AuthNamespace, type BootstrapWalletInput, type CreateWalletInput, type CreatedWalletInfo, ENVIRONMENT_DEFAULTS, type EnsureWalletResult, type EnvironmentDefaults, type FinalizeRecoveryInput, type FinalizeRecoveryResult, type KycNamespace, NotImplementedYetError, REACT_ADAPTER_VERSION, type ReconstructedSeed, type RecoveryNamespace, type RemoteWalletInfo, type RetryDeployResult, type SendXlmInput, type SendXlmResult, type SessionNamespace, type SettingsNamespace, type TxNamespace, type UnlockedSigningMaterial, type UseBalanceResult, type UseWalletActivityOptions, type UseWalletActivityResult, type UseWalletStatusResult, type WalletActivityItem, type WalletNamespace, type WalletStatus, type WalletStatusValue, type WalletStreamActivityPayload, type WalletStreamBalancePayload, type WalletStreamEventType, type WalletStreamStatusPayload, type YieldNamespace, closeAllWalletSubscriptions, subscribeToWalletEvent, useAccesly, useBalance, useWalletActivity, useWalletStatus };