feat: global decryption cache; userDecrypt refactors#195
feat: global decryption cache; userDecrypt refactors#195enitrat wants to merge 3 commits intoprereleasefrom
Conversation
|
Thank you for your pull request. We require contributors to sign our Contributor License Agreement / Terms and Conditions, and we don't seem to have the users @semantic-release-bot on file. In order for us to review and merge your code, please sign:
If you already signed one of this document, just wait to be added to the bot config. |
3d61921 to
6b899be
Compare
|
Thank you for your pull request. We require contributors to sign our Contributor License Agreement / Terms and Conditions, and we don't seem to have the users @semantic-release-bot on file. In order for us to review and merge your code, please sign:
If you already signed one of this document, just wait to be added to the bot config. |
6b899be to
b1833f3
Compare
b1833f3 to
6726c1e
Compare
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
6726c1e to
464387a
Compare
|
you'll likely want to follow-up doing the same thing for public decrypts, btw. |
Public API ChangesExpand to see full diffdiff -ru a/react-sdk.api.md b/react-sdk.api.md
--- a/react-sdk.api.md 2026-04-04 13:27:21.297096669 +0000
+++ b/react-sdk.api.md 2026-04-04 13:27:21.329632793 +0000
@@ -273,10 +273,9 @@
import { UseMutationOptions } from '@tanstack/react-query';
import { UseMutationResult } from '@tanstack/react-query';
import { UseQueryOptions } from '@tanstack/react-query';
-import { UserDecryptCallbacks } from '@zama-fhe/sdk/query';
-import { userDecryptMutationOptions } from '@zama-fhe/sdk/query';
-import { UserDecryptMutationParams } from '@zama-fhe/sdk/query';
import { UserDecryptParams } from '@zama-fhe/sdk';
+import { UserDecryptQueryConfig } from '@zama-fhe/sdk/query';
+import { userDecryptQueryOptions } from '@zama-fhe/sdk/query';
import { wrapContract } from '@zama-fhe/sdk';
import { wrapETHContract } from '@zama-fhe/sdk';
import { WrappedEvent } from '@zama-fhe/sdk';
@@ -1359,15 +1358,11 @@
// @public
export function usePublicParams(bits: number): _$_tanstack_react_query0.UseQueryResult<PublicParamsData | null, Error>;
-export { UserDecryptCallbacks as DecryptCallbacks }
-export { UserDecryptCallbacks }
-
-export { userDecryptMutationOptions }
+export { UserDecryptParams }
-export { UserDecryptMutationParams as DecryptParams }
-export { UserDecryptMutationParams }
+export { UserDecryptQueryConfig }
-export { UserDecryptParams }
+export { userDecryptQueryOptions }
// @public
export function useReadonlyToken(address: Address): _$_zama_fhe_sdk0.ReadonlyToken;
@@ -1468,19 +1463,20 @@
export function useUnwrapAll(config: UseZamaConfig, options?: UseMutationOptions<TransactionResult, Error, void, Address>): _$_tanstack_react_query0.UseMutationResult<TransactionResult, Error, void, `0x${string}`>;
// @public
-export function useUserDecrypt(config?: UseUserDecryptConfig): _$_tanstack_react_query0.UseMutationResult<Record<`0x${string}`, ClearValueType>, Error, UserDecryptMutationParams, unknown>;
+export function useUserDecrypt(config: UseUserDecryptConfig, options?: UseUserDecryptOptions): _$_tanstack_react_query0.UseQueryResult<Record<`0x${string}`, ClearValueType>, Error>;
// @public
-export type UseUserDecryptConfig = UserDecryptCallbacks;
+export interface UseUserDecryptConfig {
+ handles: DecryptHandle[];
+}
// @public
-export function useUserDecryptedValue(handle: Handle | undefined): _$_tanstack_react_query0.UseQueryResult<ClearValueType, Error>;
+export interface UseUserDecryptOptions extends Omit<UseQueryOptions<Record<Handle, ClearValueType>>, "queryKey" | "queryFn" | "enabled"> {
+ enabled?: boolean;
+}
-// @public
-export function useUserDecryptedValues(handles: Handle[]): {
- data: Record<`0x${string}`, ClearValueType | undefined>;
- results: _$_tanstack_react_query0.UseQueryResult<never, Error>[];
-};
+// @public (undocumented)
+export type UseUserDecryptResult = ReturnType<typeof useUserDecrypt>;
// @public
export function useWrapperDiscovery(config: UseWrapperDiscoveryConfig, options?: Omit<UseQueryOptions<Address | null>, "queryKey" | "queryFn">): _$_tanstack_react_query0.UseQueryResult<`0x${string}` | null, Error>;
diff -ru a/sdk-query.api.md b/sdk-query.api.md
--- a/sdk-query.api.md 2026-04-04 13:27:21.307096647 +0000
+++ b/sdk-query.api.md 2026-04-04 13:27:21.329142279 +0000
@@ -927,6 +927,9 @@
}
// @public (undocumented)
+export function removeDecryptionQueries(queryClient: QueryClientLike): void;
+
+// @public (undocumented)
export function requestZKProofVerificationMutationOptions(sdk: ZamaSDK): MutationFactoryOptions<readonly ["zama.requestZKProofVerification"], ZKProofLike, InputProofBytesType>;
// @public (undocumented)
@@ -1343,21 +1346,6 @@
}
// @public
-export interface UserDecryptCallbacks {
- onCredentialsReady?: () => void;
- onDecrypted?: (values: Record<Handle, ClearValueType>) => void;
-}
-
-// @public (undocumented)
-export function userDecryptMutationOptions(sdk: ZamaSDK, callbacks?: UserDecryptCallbacks): MutationFactoryOptions<readonly ["zama.userDecrypt"], UserDecryptMutationParams, Record<Handle, ClearValueType>>;
-
-// @public
-export interface UserDecryptMutationParams {
- // (undocumented)
- handles: DecryptHandle[];
-}
-
-// @public
export interface UserDecryptParams {
// (undocumented)
contractAddress: Address;
@@ -1380,6 +1368,19 @@
}
// @public
+export interface UserDecryptQueryConfig {
+ // (undocumented)
+ handles: DecryptHandle[];
+ // (undocumented)
+ query?: Record<string, unknown>;
+ // (undocumented)
+ requesterAddress: Address;
+}
+
+// @public
+export function userDecryptQueryOptions(sdk: ZamaSDK, config: UserDecryptQueryConfig): QueryFactoryOptions<Record<Handle, ClearValueType>, Error, Record<Handle, ClearValueType>, ReturnType<typeof zamaQueryKeys.decryption.batch>>;
+
+// @public
export interface WrappedEvent {
readonly amountIn: bigint;
// (undocumented)
@@ -1583,6 +1584,16 @@
readonly handle: (handle: string, contractAddress?: Address) => readonly ["zama.decryption", {
readonly contractAddress?: `0x${string}` | undefined;
readonly handle: string;
+ }]; /** Key for a batch decrypt query. Handles are sorted for deterministic hashing. */
+ readonly batch: (handles: readonly {
+ handle: Hex;
+ contractAddress: Address;
+ }[], account: Address) => readonly ["zama.decryption", {
+ readonly account: `0x${string}`;
+ readonly handles: {
+ handle: Hex;
+ contractAddress: `0x${string}`;
+ }[];
}];
};
readonly wrappersRegistry: {
@@ -1660,6 +1671,7 @@
// (undocumented)
readonly storage: GenericStorage;
terminate(): void;
+ userDecrypt(handles: readonly DecryptHandle[], requesterAddress?: Address): Promise<Record<Handle, ClearValueType>>;
}
// @public
@@ -1723,7 +1735,7 @@
// Warnings were encountered during analysis:
//
-// dist/esm/activity-DTBvolDB.d.ts:2034:3 - (ae-forgotten-export) The symbol "Handle" needs to be exported by the entry point index.d.ts
+// dist/esm/activity-0AjjsuhC.d.ts:2060:3 - (ae-forgotten-export) The symbol "Handle" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
diff -ru a/sdk.api.md b/sdk.api.md
--- a/sdk.api.md 2026-04-04 13:27:21.315096630 +0000
+++ b/sdk.api.md 2026-04-04 13:27:21.329240292 +0000
@@ -6965,6 +6965,14 @@
}
// @public
+export interface DecryptHandle {
+ // (undocumented)
+ contractAddress: Address;
+ // (undocumented)
+ handle: Handle;
+}
+
+// @public
export class DecryptionFailedError extends ZamaError {
constructor(message: string, options?: ErrorOptions);
}
@@ -30920,6 +30928,7 @@
// (undocumented)
readonly storage: GenericStorage;
terminate(): void;
+ userDecrypt(handles: readonly DecryptHandle[], requesterAddress?: Address): Promise<Record<Handle, ClearValueType>>;
}
// @public
|
|
@cla-bot check |
|
The cla-bot has been summoned, and re-checked this pull request! |
Summary
Replaces the token-specific
balance-cachewith a generic, handle-level decrypt cache and liftsuserDecryptto a first-class SDK method. This unifies all decryption paths (token balances, arbitrary handles, delegated decryption) through a single cache-enabled entry point.decrypt-cache.ts): replacesbalance-cache.ts. Any FHE handle from any contract can now be cached, not just token balances. Keyed by(requester, contractAddress, handle).ZamaSDK.userDecrypt(): new top-level method that checks cache → groups uncached handles by contract → batch-decrypts via relayer → writes results back to cache.useUserDecryptedValueanduseUserDecryptedValueshooks.useUserDecryptis now the single entry point, backed bystaleTime: Infinityand the persistent cache.revoke()andrevokeSession()now clear cached plaintext alongside credentials, preventing stale access after session/account changes.Design decisions
Why
(requester, contract, handle)as cache key?The decrypted plaintext for a given handle is always the same regardless of who requests it. However, there is an on-chain ACL system — only the handle owner (or their delegate) is authorized to decrypt. The
requesterdimension ensures that:Why
userDecryptis a query, not a mutationDecryption can trigger a wallet signature (via
credentials.allow), but the intended UX is that app developers callsdk.allow()upfront as a "global unlock". After that, all decryption happens in the background without re-signing. The operation is idempotent (same handles → same plaintext), so TanStack Query semantics (staleTime: Infinity,retry: false) are the right fit. The wallet prompt is a one-time credential acquisition, not a recurring side effect.Why remove
useUserDecryptedValue/useUserDecryptedValuesThese hooks were read-only lookups into the query cache by individual handle. With the persistent decrypt cache now built into
sdk.userDecrypt()andstaleTime: Infinityon the query,useUserDecryptitself serves this role — call it with the handles you need, and cached values are returned without RPCs.DecryptHandletypePairs a handle with its originating
contractAddress, enabling decryption of arbitrary handles from any contract (not just token balances where the contract was implicit).What changed
balance-cache.ts— token-specific, keyed by(token, owner, handle)decrypt-cache.ts— generic, keyed by(requester, contract, handle)ReadonlyToken.balanceOf()ZamaSDK.userDecrypt()+ token methods delegate to shared cacheuseUserDecrypt+useUserDecryptedValue+useUserDecryptedValuesuseUserDecryptonlydecryption.handle)decryption.batch) with sorted handles for deterministic hashingTest plan
decrypt-cache.test.ts— unit tests for load/save/clear/invalidationzama-sdk.test.ts—userDecryptcache hit/miss/mixed scenariosuse-user-decrypt.test.tsx— React hook integrationreadonly-token.test.ts— token balance decryption uses new cacheuseUserDecryptedValue/useUserDecryptedValuesremoved from exports