diff --git a/ctx.md b/ctx.md new file mode 100644 index 000000000..ab7cfbbd4 --- /dev/null +++ b/ctx.md @@ -0,0 +1,167 @@ +# Lit Protocol JS SDK Refactor Plan: Decoupling and Separation of Concerns + +## Goal + +Refactor the SDK to decouple the `LitNodeClient` from higher-level logic and improve separation of concerns among modules. This aims to simplify `LitNodeClient` to a pure request/response handler, move orchestration and state management to `LitClient`, dismantle the existing `LitCore`, and make the SDK more modular and easier to maintain for different network versions. + +## `LitCore` Dismantling + +The existing `LitCore` class will be dismantled. Its responsibilities will be redistributed as follows: +* **Low-level node communication & promise handling**: Moved to the new, simplified `LitNodeClient`. +* **High-level connection lifecycle, state management (epoch, blockhash, network keys), and orchestration**: Moved to `LitClient`. +* **Utilities (crypto, etc.)**: Moved to dedicated utility modules or alongside their primary users. + +## Proposed Module Structure & Responsibilities + +1. **`LitAuthManager`**: + * Central hub for authentication operations. + * Manages registered instances of `LitAuthProvider`. + * Uses a configured `LitAuthStorageProvider` for persistence. + * Contains logic for generating `AuthenticationContext` (for EOA & PKP flows). + * Orchestrates calls to `LitAuthProvider.authenticate()`. + * For PKPs, houses the session signing logic (previously `_signSessionKey`), using `LitClient` (and its `LitNodeClient` / `LitNetwork`) for node interactions. + * Fetches necessary dynamic data (like `nonce`) via `LitClient` to pass to `LitAuthProvider.authenticate()`. + * Handles PKP-specific auth actions like claiming/minting (potentially delegating relay interaction to `LitClient`). + * Manages session key utilities (`_getSessionKey`, `_getSessionKeyUri`). + +2. **`LitAuthProvider` (Interface & Implementations)**: + * **Purpose**: Abstract interaction with a *specific external authentication system* (e.g., Google, Metamask). + * **Focus**: Solely on performing the external authentication step and deriving the Auth Method ID. + * **Responsibilities**: + * `authenticate(options?: AuthenticateOptions)`: Implement the specific auth flow (e.g., OAuth redirect/popup, wallet signature). Receives dynamic data like `nonce` from `LitAuthManager` via `options`. Returns a standardized `LitAuthMethod`. + * `getAuthMethodId(authMethod: LitAuthMethod)`: Implement the logic to calculate the unique Lit Protocol ID for the given auth method. + * **Dependencies**: Should *not* depend on `LitNodeClient` or `IRelay`. Provider-specific configuration (e.g., OAuth client IDs) is passed via constructor options. + * **PKP Logic**: Does *not* contain PKP management logic (minting, fetching, claiming). + +3. **`LitAuthStorageProvider` (Interface & Implementations)**: + * **Purpose**: Provide a generic interface for persisting and retrieving authentication state (`LitAuthData`) managed by `LitAuthManager`. + * **Responsibilities**: Implement `get`, `set`, `delete`, `clear` methods for a specific storage mechanism (localStorage, sessionStorage, server-side, in-memory, etc.). + * **`LitAuthData` Structure (Conceptual)**: Contains `authMethod`, potentially `pkpAuthSig`/`SessionSigs`, `sessionKey`, `expiration`. + +4. **`LitNodeClient` (Simplified)**: + * **Purely low-level node communication handler.** + * Sends raw requests (`_sendCommandToNode`, `generatePromise`). + * Performs raw node handshakes (`_handshakeWithNode`). + * Manages node response promises (`_getNodePromises`, `_handleNodePromises`). + * Handles low-level node errors (`_throwNodeError`). + * *Unaware* of network state, connection lifecycle, request formatting, or response processing logic. + +5. **`LitNetwork`**: + * Encapsulates specifics of a Lit Network version. + * Holds network configuration (`LitChainConfig`, endpoints, keys). + * **Creates network-specific request bodies**. + * **Processes raw node responses** specific to the network. + +6. **`LitChainClient`**: + * Handles direct blockchain interactions. + +7. **`LitClient` (Orchestrator & State Manager)**: + * Main high-level developer API & central orchestrator. + * **Manages overall connection lifecycle**: `connect`, `disconnect`, `ready` state. + * **Holds SDK state**: `networkPubKeySet`, `subnetPubKey`, `currentEpochNumber`, `latestBlockhash`, `serverKeys`, `connectedNodes`, bootstrap URLs, min node count. + * **Handles network state updates**: Epoch changes, blockhash syncing, validator data fetching. + * Holds and coordinates instances of `LitAuthManager`, `LitNetwork`, simplified `LitNodeClient`, `LitChainClient`. + * Exposes primary functions (`pkpSign`, `encrypt`, `decrypt`, `runLitAction`), orchestrating the flow: Get Context -> Create Request -> Send Request -> Process Response -> Return Result. + * Manages high-level configuration (e.g., `setDefaultMaxPrice`). + +## Refactoring Tasks + +1. **Dismantle `LitCore` & Simplify `LitNodeClient`**: + * Extract all logic from `LitCore` and the current `LitNodeClient`. + * Create the new, simplified `LitNodeClient` containing only the low-level methods identified below. + * Relocate the remaining extracted logic to `LitClient`, `LitAuthManager`, `LitNetwork`, or utility modules as detailed below. + * **Detailed Breakdown for Logic Relocation:** + * **Implement in Simplified `LitNodeClient`**: `_sendCommandToNode`, `generatePromise`, `_getNodePromises`, `_handleNodePromises`, `_throwNodeError`, `_getMostCommonNodeResponse` (helper for promise handling), `_handshakeWithNode` (raw communication part), minimal constructor. + * **Move to `LitClient` (Connection/State/Orchestration)**: `connect`, `disconnect`, `ready` (state property), `config` (high-level parts), `networkPubKeySet`, `subnetPubKey`, `currentEpochNumber`, `latestBlockhash`, `lastBlockHashRetrieved`, `serverKeys`, `connectedNodes`, `hdRootPubkeys` (state properties), `_getValidatorData`, `_listenForNewEpoch`, `_stopListeningForNewEpoch`, `_handleStakingContractStateChange`, `_fetchCurrentEpochState`, `_epochState` (getter/setter), `_syncBlockhash`, `_runHandshakeWithBootstrapUrls`, `_getCoreNodeConfigFromHandshakeResults`, `_getProviderWithFallback`, `setDefaultMaxPrice`, `computeHDPubKey`, `computeHDKeyId`, `executeJs`, `pkpSign`, `encrypt`, `decrypt` (as orchestrator methods), `_getThreshold` (state-dependent utility). + * **Move to `LitAuthManager`**: `defaultAuthCallback`, `createCapacityDelegationAuthSig`, `_getSessionKey`, `_getWalletSig`, `_authCallbackAndUpdateStorageItem`, `_checkNeedToResignSessionKey`, `_signSessionKey`, `_validateSignSessionKeyResponseData`, `getSignSessionKeyShares`, `_getSessionSigs`, `getPkpAuthContext`, `_getSessionKeyUri`, `claimKeyId`. + * **Move to `LitNetwork` Implementations**: `_getNodePrices`, `getMaxPricesForNodeProduct`, `executeJsNodeRequest` helper, Network-specific request formatting and response processing within orchestrator methods (including share combination, response parsing), `_getIdentityParamForEncryption`, `_decryptWithSignatureShares`, `_getFallbackIpfsCode`. + * **Utilities**: Move general helpers (`normalizeAndStringify`, crypto functions, etc.) to dedicated util modules. + +2. **Define/Refine `LitNetwork` Abstraction**: + * Review and potentially extend the existing `LitNetwork` abstract class (`packages/networks/src/lib/LitNetwork.ts`) to ensure it includes methods for all network-specific logic (request creation, response processing, pricing, etc.). + * Implement/update concrete `LitNetwork` classes (e.g., `HabaneroNetwork`) with the logic extracted in Step 1. + +3. **Implement `LitAuthManager`, `LitAuthProvider` & `LitAuthStorageProvider` Interfaces**: + * Define the interfaces for `LitAuthProvider` and `LitAuthStorageProvider` based on the refined responsibilities. + * Create the `LitAuthManager` class. + * Implement `LitAuthManager`'s core logic: + * Provider/storage management. + * Move the PKP session signing logic (`_signSessionKey`) and session utilities here. + * Implement `getAuthContext` orchestration. + * Move PKP auth/claim logic here. + * Refactor existing authenticators (e.g., `MetamaskAuthenticator`, `GoogleAuthenticator`) to implement the new `LitAuthProvider` interface, removing disallowed dependencies and logic. + +4. **Refactor `LitClient`**: + * Update `LitClient` to hold and orchestrate the new modules (`LitAuthManager`, `LitNetwork`, `LitNodeClient`, `LitChainClient`). + * Rewrite public methods (`pkpSign`, `executeJs`, etc.) to use the new orchestration flow. + +5. **Clean Up**: + * Remove or merge old helper files (like `preparePkpAuthContext.ts`) into the new structure. + * Update imports and types across the codebase. + * Add tests for the new structure. + + +Revised LitAuthProvider Interface (Conceptual): +```ts +// Base options for all providers +interface LitAuthProviderOptions { + // e.g., specific RPC URLs for certain chains if needed by the provider, + // OAuth client IDs, etc. + // Note: No LitNodeClient or IRelay here. +} + +// Options passed dynamically during authentication +interface AuthenticateOptions { + // Common options that might be needed, supplied by LitAuthManager + nonce?: string; // e.g., for SIWE messages + expiration?: string; // If desired session duration differs from default + + // Provider-specific dynamic options (if any) + // e.g., for EthWallet: + address?: string; + chain?: string; + domain?: string; // Potentially sourced from LitClient/AuthManager config + origin?: string; // Potentially sourced from LitClient/AuthManager config + statement?: string; // Optional SIWE statement addition +} + +// The core interface for all auth providers +interface LitAuthProvider { + readonly authMethodType: AUTH_METHOD_TYPE_VALUES; + + // Constructor takes LitAuthProviderOptions + + // Performs authentication with the external service. + // Receives dynamic options (like nonce) from LitAuthManager. + // Returns the standardized authentication proof. + authenticate(options?: AuthenticateOptions): Promise; + + // Calculates the unique ID used by Lit Protocol for this auth method + credential. + getAuthMethodId(authMethod: LitAuthMethod): Promise; + + // Optional: signOut() or disconnect() method if applicable for the provider's session management? +} +``` + +Revised LitAuthStorageProvider Interface (Conceptual): + + +```ts +// Data structure stored by the provider +interface LitAuthData { + authMethod: LitAuthMethod; + // For PKPs, this would also include the generated AuthSig/SessionSigs + pkpAuthSig?: AuthSig; // Or SessionSigsMap equivalent if we store that directly + sessionKey?: SessionKeyPair; // If managing session keys here + expiration?: string; + // Other relevant metadata +} + +// Interface for the storage mechanism +interface LitAuthStorageProvider { + get(key: string): Promise; + set(key: string, value: LitAuthData): Promise; + delete(key: string): Promise; + clear(): Promise; +} +``` \ No newline at end of file diff --git a/local-tests/setup/session-sigs/get-eoa-session-sigs.ts b/local-tests/setup/session-sigs/get-eoa-session-sigs.ts index f6eb04997..a2c2d477e 100644 --- a/local-tests/setup/session-sigs/get-eoa-session-sigs.ts +++ b/local-tests/setup/session-sigs/get-eoa-session-sigs.ts @@ -74,7 +74,6 @@ export const getEoaAuthContext = ( return authSig; }, - ...(centralisation === 'decentralised' && { capabilityAuthSigs: [devEnv.superCapacityDelegationAuthSig], }), diff --git a/packages/auth-helpers/src/lib/generate-auth-sig.ts b/packages/auth-helpers/src/lib/generate-auth-sig.ts index 4cb56c3a5..be18cd3e8 100644 --- a/packages/auth-helpers/src/lib/generate-auth-sig.ts +++ b/packages/auth-helpers/src/lib/generate-auth-sig.ts @@ -22,7 +22,14 @@ export const generateAuthSig = async ({ address, algo, }: { - signer: ethers.Wallet | ethers.Signer | SignerLike; + signer: + | ethers.Wallet + | ethers.Signer + | SignerLike + | { + signMessage: (message: any) => Promise; + getAddress?: () => Promise; + }; toSign: string; address?: string; algo?: 'ed25519'; @@ -44,7 +51,9 @@ export const generateAuthSig = async ({ // If address is not provided, derive it from the signer if (!address) { - address = await signer.getAddress(); + address = await ( + signer as { getAddress: () => Promise } + ).getAddress(); } // checksum the address diff --git a/packages/auth-helpers/src/lib/recap/resource-builder.spec.ts b/packages/auth-helpers/src/lib/recap/resource-builder.spec.ts index c7a6adbda..87fce6326 100644 --- a/packages/auth-helpers/src/lib/recap/resource-builder.spec.ts +++ b/packages/auth-helpers/src/lib/recap/resource-builder.spec.ts @@ -5,13 +5,13 @@ import { LitPaymentDelegationResource, LitPKPResource, } from '../resources'; -import { ResourceAbilityRequestBuilder } from './resource-builder'; +import { createResourceBuilder } from './resource-builder'; -describe('ResourceAbilityRequestBuilder', () => { - let builder: ResourceAbilityRequestBuilder; +describe('createResourceBuilder', () => { + let builder: ReturnType; beforeEach(() => { - builder = new ResourceAbilityRequestBuilder(); + builder = createResourceBuilder(); }); it('should build an array of resource ability requests', () => { @@ -21,7 +21,7 @@ describe('ResourceAbilityRequestBuilder', () => { .addPKPSigningRequest(resourceId1) .addLitActionExecutionRequest(resourceId2); - const requests = builder.build(); + const requests = builder.requests; expect(JSON.stringify(requests)).toBe( JSON.stringify([ { @@ -44,7 +44,7 @@ describe('ResourceAbilityRequestBuilder', () => { .addAccessControlConditionDecryptionRequest('abc') // ACC Decryption .addPaymentDelegationRequest('def'); // Payment Delegation - const requests = builder.build(); + const requests = builder.requests; expect(JSON.stringify(requests)).toBe( JSON.stringify([ { diff --git a/packages/auth-helpers/src/lib/recap/resource-builder.ts b/packages/auth-helpers/src/lib/recap/resource-builder.ts index 91e9023ec..34ed7ba05 100644 --- a/packages/auth-helpers/src/lib/recap/resource-builder.ts +++ b/packages/auth-helpers/src/lib/recap/resource-builder.ts @@ -8,12 +8,12 @@ import { } from '../resources'; /** - * Lit resrouce ability request builder for creating resource ability requests. + * Creates a resource ability request builder for creating resource ability requests. * * @example - * import { ResourceAbilityRequestBuilder } from '@lit-protocol/auth-helpers'; + * import { createResourceBuilder } from '@lit-protocol/auth-helpers'; -const builder = new ResourceAbilityRequestBuilder(); +const builder = createResourceBuilder(); builder .addPKPSigningRequest('*') // PKP Signing @@ -25,82 +25,87 @@ builder const requests = builder.build(); */ -export class ResourceAbilityRequestBuilder { - private requests: Array<{ +export const createResourceBuilder = () => { + const requests: Array<{ resource: ILitResource; ability: LIT_ABILITY_VALUES; }> = []; - /** - * Adds a PKP signing request to the builder. - * @param resourceId - The ID of the resource. - * @returns The builder instance. - */ - addPKPSigningRequest(resourceId: string): this { - this.requests.push({ - resource: new LitPKPResource(resourceId), - ability: LIT_ABILITY.PKPSigning, - }); - return this; - } + return { + /** + * Adds a PKP signing request to the builder. + * @param resourceId - The ID of the resource. + * @returns The builder instance. + */ + addPKPSigningRequest(resourceId: string) { + requests.push({ + resource: new LitPKPResource(resourceId), + ability: LIT_ABILITY.PKPSigning, + }); + return this; + }, - /** - * Adds a Lit action execution request to the builder. - * @param resourceId - The ID of the resource. - * @returns The builder instance. - */ - addLitActionExecutionRequest(resourceId: string): this { - this.requests.push({ - resource: new LitActionResource(resourceId), - ability: LIT_ABILITY.LitActionExecution, - }); - return this; - } + /** + * Adds a Lit action execution request to the builder. + * @param resourceId - The ID of the resource. + * @returns The builder instance. + */ + addLitActionExecutionRequest(resourceId: string) { + requests.push({ + resource: new LitActionResource(resourceId), + ability: LIT_ABILITY.LitActionExecution, + }); + return this; + }, - /** - * Adds an access control condition signing request to the builder. - * @param resourceId - The ID of the resource. - * @returns The builder instance. - */ - addAccessControlConditionSigningRequest(resourceId: string): this { - this.requests.push({ - resource: new LitAccessControlConditionResource(resourceId), - ability: LIT_ABILITY.AccessControlConditionSigning, - }); - return this; - } + /** + * Adds an access control condition signing request to the builder. + * @param resourceId - The ID of the resource. + * @returns The builder instance. + */ + addAccessControlConditionSigningRequest(resourceId: string) { + requests.push({ + resource: new LitAccessControlConditionResource(resourceId), + ability: LIT_ABILITY.AccessControlConditionSigning, + }); + return this; + }, - /** - * Adds an access control condition decryption request to the builder. - * @param resourceId - The ID of the resource. - * @returns The builder instance. - */ - addAccessControlConditionDecryptionRequest(resourceId: string): this { - this.requests.push({ - resource: new LitAccessControlConditionResource(resourceId), - ability: LIT_ABILITY.AccessControlConditionDecryption, - }); - return this; - } + /** + * Adds an access control condition decryption request to the builder. + * @param resourceId - The ID of the resource. + * @returns The builder instance. + */ + addAccessControlConditionDecryptionRequest(resourceId: string) { + requests.push({ + resource: new LitAccessControlConditionResource(resourceId), + ability: LIT_ABILITY.AccessControlConditionDecryption, + }); + return this; + }, - /** - * Adds a rate limit increase authentication request to the builder. - * @param resourceId - The ID of the resource. - * @returns The builder instance. - */ - addPaymentDelegationRequest(resourceId: string): this { - this.requests.push({ - resource: new LitPaymentDelegationResource(resourceId), - ability: LIT_ABILITY.PaymentDelegation, - }); - return this; - } + /** + * Adds a rate limit increase authentication request to the builder. + * @param resourceId - The ID of the resource. + * @returns The builder instance. + */ + addPaymentDelegationRequest(resourceId: string) { + requests.push({ + resource: new LitPaymentDelegationResource(resourceId), + ability: LIT_ABILITY.PaymentDelegation, + }); + return this; + }, - /** - * Builds the array of resource ability requests. - * @returns The array of resource ability requests. - */ - build(): Array<{ resource: ILitResource; ability: LIT_ABILITY_VALUES }> { - return this.requests; - } -} + /** + * Return the array of resource ability requests. + * @returns The array of resource ability requests. + */ + get requests(): Array<{ + resource: ILitResource; + ability: LIT_ABILITY_VALUES; + }> { + return requests; + }, + }; +}; diff --git a/packages/auth-helpers/src/lib/siwe/create-siwe-message.ts b/packages/auth-helpers/src/lib/siwe/create-siwe-message.ts index c50faace1..ba7a44f30 100644 --- a/packages/auth-helpers/src/lib/siwe/create-siwe-message.ts +++ b/packages/auth-helpers/src/lib/siwe/create-siwe-message.ts @@ -83,21 +83,9 @@ export const createSiweMessage = async ( // -- add recap resources if needed if (params.resources) { - if (!params.litNodeClient) { - throw new InvalidArgumentException( - { - info: { - params, - }, - }, - 'litNodeClient is required' - ); - } - siweMessage = await addRecapToSiweMessage({ siweMessage, resources: params.resources, - litNodeClient: params.litNodeClient, }); } @@ -127,17 +115,6 @@ export const createSiweMessageWithRecaps = async ( export const createSiweMessageWithCapacityDelegation = async ( params: WithCapacityDelegation ) => { - if (!params.litNodeClient) { - throw new InvalidArgumentException( - { - info: { - params, - }, - }, - 'litNodeClient is required' - ); - } - return createSiweMessage({ ...params, }); diff --git a/packages/auth-helpers/src/lib/siwe/siwe-helper.ts b/packages/auth-helpers/src/lib/siwe/siwe-helper.ts index 918d2bc6d..55987b0bc 100644 --- a/packages/auth-helpers/src/lib/siwe/siwe-helper.ts +++ b/packages/auth-helpers/src/lib/siwe/siwe-helper.ts @@ -77,7 +77,6 @@ export const generateSessionCapabilityObjectWithWildcards = async ( * Adds recap capabilities to a SiweMessage. * @param siweMessage - The SiweMessage to add recap capabilities to. * @param resources - An array of LitResourceAbilityRequest objects representing the resources and abilities to add. - * @param litNodeClient - The LitNodeClient interface * @returns The updated SiweMessage with recap capabilities added. * @throws An error if the resources array is empty or if litNodeClient is not provided. * @throws An error if the generated capabilities fail to verify for any resource and ability. @@ -85,11 +84,9 @@ export const generateSessionCapabilityObjectWithWildcards = async ( export const addRecapToSiweMessage = async ({ siweMessage, resources, - litNodeClient, }: { siweMessage: SiweMessage; resources: LitResourceAbilityRequest[]; - litNodeClient: ILitNodeClient; }) => { if (!resources || resources.length < 1) { throw new InvalidArgumentException( @@ -103,18 +100,6 @@ export const addRecapToSiweMessage = async ({ ); } - if (!litNodeClient) { - throw new InvalidArgumentException( - { - info: { - resources, - siweMessage, - }, - }, - 'litNodeClient is required' - ); - } - for (const request of resources) { const recapObject = await generateSessionCapabilityObjectWithWildcards([ request.resource, diff --git a/packages/auth/src/lib/AuthManager/authContexts/BaseAuthContextType.ts b/packages/auth/src/lib/AuthManager/authContexts/BaseAuthContextType.ts new file mode 100644 index 000000000..ee14630c3 --- /dev/null +++ b/packages/auth/src/lib/AuthManager/authContexts/BaseAuthContextType.ts @@ -0,0 +1,16 @@ +import { LitNodeClient } from '@lit-protocol/lit-node-client'; +import { AuthSig, LitResourceAbilityRequest } from '@lit-protocol/types'; + +export interface BaseIdentity { + pkpPublicKey: string; +} + +/** + * Any auth context type must implement this interface. + */ +export interface BaseAuthContextType { + litNodeClient: LitNodeClient; + resources: LitResourceAbilityRequest[]; + capabilityAuthSigs?: AuthSig[]; + identity: T; +} diff --git a/packages/auth/src/lib/AuthManager/authContexts/prepareEoaAuthContext.ts b/packages/auth/src/lib/AuthManager/authContexts/prepareEoaAuthContext.ts new file mode 100644 index 000000000..3b54d373a --- /dev/null +++ b/packages/auth/src/lib/AuthManager/authContexts/prepareEoaAuthContext.ts @@ -0,0 +1,97 @@ +import { + createSiweMessageWithRecaps, + generateAuthSig, +} from '@lit-protocol/auth-helpers'; +import { AuthCallbackParams } from '@lit-protocol/types'; +import { BaseAuthContextType, BaseIdentity } from './BaseAuthContextType'; + +interface EoaIdentity extends BaseIdentity { + signer: { + signMessage: (message: any) => Promise; + getAddress?: () => Promise; + }; + signerAddress: `0x${string}`; +} + +export interface PrepareEoaAuthContextParams + extends BaseAuthContextType { + identity: EoaIdentity; +} + +export const prepareEoaAuthContext = async ( + params: PrepareEoaAuthContextParams +) => { + return { + pkpPublicKey: params.identity.pkpPublicKey, + chain: 'ethereum', + resourceAbilityRequests: params.resources, + authNeededCallback: async ({ + uri, + expiration, + resourceAbilityRequests, + }: AuthCallbackParams) => { + if (!expiration) { + throw new Error('expiration is required'); + } + + if (!resourceAbilityRequests) { + throw new Error('resourceAbilityRequests is required'); + } + + if (!uri) { + throw new Error('uri is required'); + } + + const toSign = await createSiweMessageWithRecaps({ + uri: uri, + expiration: expiration, + resources: resourceAbilityRequests, + walletAddress: params.identity.signerAddress, + nonce: await params.litNodeClient.getLatestBlockhash(), + }); + + const authSig = await generateAuthSig({ + signer: params.identity.signer, + toSign, + }); + + return authSig; + }, + ...(params.capabilityAuthSigs && { + capabilityAuthSigs: [...params.capabilityAuthSigs], + }), + }; +}; + +// if (import.meta.main) { +// (async () => { +// const { LitNodeClient } = await import('@lit-protocol/lit-node-client'); +// const { createResourceBuilder } = await import( +// '@lit-protocol/auth-helpers' +// ); + +// const litNodeClient = new LitNodeClient({ +// litNetwork: 'naga-dev', +// debug: true, +// }); + +// await litNodeClient.connect(); + +// const resourceRequests = +// createResourceBuilder().addPKPSigningRequest('*').requests; + +// console.log('resourceRequests', JSON.stringify(resourceRequests, null, 2)); + +// const authContext = await prepareEoaAuthContext({ +// litNodeClient: litNodeClient, +// identity: { +// pkpPublicKey: '0x123', +// signer: { signMessage: async () => '0x123' }, +// signerAddress: '0x123', +// }, +// resources: resourceRequests, +// }); + +// console.log('authContext', authContext); +// })(); +// } diff --git a/packages/auth/src/lib/AuthManager/authContexts/preparePkpAuthContext.ts b/packages/auth/src/lib/AuthManager/authContexts/preparePkpAuthContext.ts new file mode 100644 index 000000000..0c0b8a6fa --- /dev/null +++ b/packages/auth/src/lib/AuthManager/authContexts/preparePkpAuthContext.ts @@ -0,0 +1,46 @@ +import { LitNodeClient } from '@lit-protocol/lit-node-client'; +import { + AuthMethod, + AuthSig, + AuthenticationContext, + LitResourceAbilityRequest, +} from '@lit-protocol/types'; +import { Hex } from 'viem'; +import { BaseIdentity } from './BaseAuthContextType'; + +interface PkpIdentity extends BaseIdentity { + pkpPublicKey: Hex; + authMethods: AuthMethod[]; +} + +/** + * Interface for parameters required to get the native auth context. + */ +export interface PreparePkpAuthContextParams { + litNodeClient: LitNodeClient; + identity: PkpIdentity; + resources: LitResourceAbilityRequest[]; + capabilityAuthSigs?: AuthSig[]; + expiration?: string; +} + +/** + * Get the auth context for a Lit supported native auth method (eg. WebAuthn, Discord, Google). + * This context is needed for requesting session signatures with PKP-based authentication. + * + * @param {PreparePkpAuthContextParams} params - Parameters for getting the native auth context. + * @returns {AuthenticationContext} The authentication context object. + */ +export const preparePkpAuthContext = ( + params: PreparePkpAuthContextParams +): AuthenticationContext => { + const authContext = params.litNodeClient.getPkpAuthContext({ + pkpPublicKey: params.identity.pkpPublicKey, + authMethods: params.identity.authMethods, + expiration: params.expiration, + resourceAbilityRequests: params.resources, + capabilityAuthSigs: params.capabilityAuthSigs, + }); + + return authContext; +}; diff --git a/packages/auth/src/lib/AuthManager/getAuthContext.ts b/packages/auth/src/lib/AuthManager/getAuthContext.ts new file mode 100644 index 000000000..e3cf9b1b6 --- /dev/null +++ b/packages/auth/src/lib/AuthManager/getAuthContext.ts @@ -0,0 +1,53 @@ +import { createResourceBuilder } from '@lit-protocol/auth-helpers'; +import { LitNodeClient } from '@lit-protocol/lit-node-client'; +import { setLoggerOptions } from '@lit-protocol/logger'; +import { prepareEoaAuthContext } from './authContexts/prepareEoaAuthContext'; +import { preparePkpAuthContext } from './authContexts/preparePkpAuthContext'; + +const logger = setLoggerOptions({ + name: 'AuthManagerFunctions', + level: 'debug', +}); + +/** + * If you are using EOA (Externally Owned Account) authentication, you will want to choose the `fromEOA` method. + * If you are using Lit Native Auth Methods (eg. Google, Discord, WebAuthn, Stytch, etc.) you will want to choose the `fromPKP` method. + */ +export const getAuthContext = { + fromEOA: prepareEoaAuthContext, + fromPKP: preparePkpAuthContext, +}; + +if (import.meta.main) { + (async () => { + // -- imports + const { ethers } = await import('ethers'); + const { privateKeyToAccount } = await import('viem/accounts'); + const litNodeClient = new LitNodeClient({ + litNetwork: 'naga-dev', + debug: false, + }); + + await litNodeClient.connect(); + + const anvilPrivateKey = + '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'; + + const ethersWallet = new ethers.Wallet(anvilPrivateKey); + const viemAccount = privateKeyToAccount(anvilPrivateKey); + + const authContext = await getAuthContext.fromEOA({ + litNodeClient: litNodeClient, + identity: { + signer: ethersWallet, + signerAddress: viemAccount.address, + pkpPublicKey: + '0x04e5603fe1cc5ce207c12950939738583b599f22a152c3672a4c0eee887d75dd405246ac3ed2430283935a99733eac9520581af9923c0fc04fad1d67d60908ce18', + }, + resources: createResourceBuilder().addPKPSigningRequest('*').requests, + }); + + console.log('authContext', authContext); + process.exit(); + })(); +} diff --git a/packages/auth/src/lib/auth-manager.ts b/packages/auth/src/lib/auth-manager.ts index 8ce1d540d..e5934d594 100644 --- a/packages/auth/src/lib/auth-manager.ts +++ b/packages/auth/src/lib/auth-manager.ts @@ -1,5 +1,4 @@ -import { generateSessionKeyPair } from '@lit-protocol/crypto'; - +import { getAuthContext } from './AuthManager/getAuthContext'; import type { LitAuthStorageProvider } from './storage/types'; import type { LitAuthData } from './types'; @@ -26,6 +25,6 @@ async function signSessionKey({ storage }: LitAuthManagerConfig) { export function getAuthManager({ storage }: LitAuthManagerConfig) { return { - getAuthContext() {}, + getAuthContext, }; } diff --git a/packages/auth/src/lib/authentication.ts b/packages/auth/src/lib/authentication.ts new file mode 100644 index 000000000..bb7124ec6 --- /dev/null +++ b/packages/auth/src/lib/authentication.ts @@ -0,0 +1,18 @@ +import { getAuthManager } from './auth-manager'; + +export const authentication = { + /** + * Main helper function to create an auth manager + */ + getAuthManager: getAuthManager, + + /** + * a collection of all the authenticators / auth providers + */ + authenticators: null, + + /** + * a collection of all the storage providers / solutions + */ + storagePlugins: null, +}; diff --git a/packages/lit-client/src/lib/lit-client.ts b/packages/lit-client/src/lib/lit-client.ts new file mode 100644 index 000000000..64e9fbcbc --- /dev/null +++ b/packages/lit-client/src/lib/lit-client.ts @@ -0,0 +1,156 @@ +import { ethers } from 'ethers'; // Added import + +// Import necessary types and potentially classes when defined +// import { LitAuthManager } from '@lit-protocol/auth-client'; // Assuming this path +// import { LitNetwork } from '@lit-protocol/network'; // Assuming this path +// import { LitNodeClient } from '@lit-protocol/node-client'; // Assuming this path (the NEW simplified one) +// import { LitChainClient } from '@lit-protocol/chain-client'; // Assuming this path +import { + AuthenticationContext, + SigResponse /* Corrected type */, +} from '@lit-protocol/types'; + +// Placeholder for params type until defined +type PKPSignParams = { + toSign: Uint8Array; + pubKey: string; + authContext: AuthenticationContext; + // Add other potential params +}; + +// Placeholder types until modules are created +type LitAuthManager = any; +type LitNetwork = any; // Placeholder type +type LitNodeClient = any; +type LitChainClient = any; + +// Define LitClient configuration interface +interface LitClientConfig { + litNetwork: LitNetwork; // Requires a concrete LitNetwork instance (e.g., Habanero) + authManager?: LitAuthManager; // Optional, can be instantiated internally + nodeClient?: LitNodeClient; // Optional, can be instantiated internally + chainClient?: LitChainClient; // Optional, can be instantiated internally + debug?: boolean; + // Add other high-level config options +} + +export class LitClient { + private readonly config: LitClientConfig; + private readonly litNetwork: LitNetwork; + public readonly authManager: LitAuthManager; // Expose AuthManager + private readonly nodeClient: LitNodeClient; // Internal use + private readonly chainClient: LitChainClient; // Internal use + + // State Properties + private _ready: boolean = false; + private _connectedNodes: Set = new Set(); + private _serverKeys: Record = {}; // Type from handshake response + private _networkPubKey: string | null = null; + private _subnetPubKey: string | null = null; + private _networkPubKeySet: string | null = null; + private _hdRootPubkeys: string[] | null = null; + private _latestBlockhash: string | null = null; + private _currentEpochNumber: number | null = null; + + constructor(config: LitClientConfig) { + this.config = config; + this.litNetwork = config.litNetwork; + + // Instantiate dependencies if not provided (simplified example) + // TODO: Replace placeholders with actual instantiation logic + this.authManager = config.authManager || {}; // Placeholder + this.nodeClient = config.nodeClient || {}; // Placeholder + this.chainClient = config.chainClient || {}; // Placeholder + + // TODO: Initialize logger + } + + get ready(): boolean { + return this._ready; + } + + /** + * Connect to the Lit Network. + * This involves fetching network state, connecting to nodes via LitNodeClient, + * and setting up epoch/blockhash listeners. + */ + async connect(): Promise { + console.log('Connecting LitClient...'); + // 1. Fetch validator data (bootstrapUrls, minNodeCount) - similar to LitCore._getValidatorData + // - Potentially uses LitChainClient + + // 2. Handshake with nodes via simplified LitNodeClient + // - const { connectedNodes, serverKeys, coreNodeConfig } = await this.nodeClient.handshakeWithNodes(bootstrapUrls); + // - Store connectedNodes, serverKeys + // - Store network keys (networkPubKey, subnetPubKey, etc.) from coreNodeConfig + + // 3. Fetch/Sync latest blockhash + // - this._latestBlockhash = await this.nodeClient.getLatestBlockhash(); // Or dedicated method + + // 4. Fetch current epoch + // - this._currentEpochNumber = await ... + + // 5. Set up listeners (epoch changes, etc.) - Similar to LitCore + + this._ready = true; + console.log('LitClient Connected.'); + // Emit ready event? + } + + /** + * Disconnect from the Lit Network. + */ + async disconnect(): Promise { + console.log('Disconnecting LitClient...'); + // Stop listeners + // Clear state + this._ready = false; + console.log('LitClient Disconnected.'); + } + + /** + * Sign a message using a PKP. + * + * @param params - Parameters for signing. + * @returns The signature response. + */ + async pkpSign(params: PKPSignParams): Promise { + // Corrected return type + if (!this.ready) { + throw new Error('LitClient is not connected.'); // Or specific error type + } + + // 1. Validate params (e.g., ensure authContext is provided) + if (!params.authContext) { + throw new Error('AuthenticationContext is required for pkpSign'); + } + + // 2. Create the network-specific request body using LitNetwork + // const requestBody = await this.litNetwork.createSignRequest(params, this._networkContext); // Pass necessary state + + // 3. Send the request to nodes using LitNodeClient + // const rawNodeResponses = await this.nodeClient.sendRequestToNodes(requestBody, params.authContext); + + // 4. Process the raw responses using LitNetwork + // const signResponse = await this.litNetwork.handleSignResponse(rawNodeResponses); + + // 5. Return the processed result + // return signResponse; + + console.log('Simulating pkpSign with params:', params); + // TEMP: Return dummy response + const dataSignedHash = ethers.utils.keccak256( + params.toSign + ) as `0x${string}`; + return { + r: '0x' as `0x${string}`, // Placeholder hex string + s: '0x' as `0x${string}`, // Placeholder hex string + recid: 0, + signature: '0x' as `0x${string}`, // Placeholder hex string + publicKey: params.pubKey, + dataSigned: dataSignedHash, + }; + } + + // TODO: Implement other methods (encrypt, decrypt, executeJs) following the same orchestration pattern. +} diff --git a/packages/lit-node-client/src/lib/lit-node-client.ts b/packages/lit-node-client/src/lib/lit-node-client.ts index 1f8f13bf3..9fda11ac9 100644 --- a/packages/lit-node-client/src/lib/lit-node-client.ts +++ b/packages/lit-node-client/src/lib/lit-node-client.ts @@ -1800,26 +1800,6 @@ export class LitNodeClient extends LitCore implements ILitNodeClient { ); } - // Check if IPFS options are provided and if the code should be fetched from IPFS and overwrite the current code. - // This will fetch the code from the specified IPFS gateway using the provided ipfsId, - // and update the params with the fetched code, removing the ipfsId afterward. - const overwriteCode = - params.ipfsOptions?.overwriteCode || - GLOBAL_OVERWRITE_IPFS_CODE_BY_NETWORK[this.config.litNetwork]; - - if (overwriteCode && props.litActionIpfsId) { - const code = await this._getFallbackIpfsCode( - params.ipfsOptions?.gatewayUrl, - props.litActionIpfsId - ); - - props = { - ...props, - litActionCode: code, - litActionIpfsId: undefined, - }; - } - /** * We must provide an empty array for authMethods even if we are not using any auth methods. * So that the nodes can serialize the request correctly. diff --git a/packages/networks/src/lib/LitNetwork.ts b/packages/networks/src/lib/LitNetwork.ts index 8197844b3..59e677a2c 100644 --- a/packages/networks/src/lib/LitNetwork.ts +++ b/packages/networks/src/lib/LitNetwork.ts @@ -45,4 +45,23 @@ export abstract class LitNetwork { abstract createExecuteJsRequests(params: unknown): Promise; abstract handleExecuteJsResponses(params: unknown): Promise; + + // Methods for PKP Session Key Signing + abstract createSignSessionKeyRequest(params: unknown): Promise; + abstract handleSignSessionKeyResponse(params: unknown): Promise; + + // Methods for PKP Claiming + abstract createClaimKeyRequest(params: unknown): Promise; + abstract handleClaimKeyResponse(params: unknown): Promise; + + // Methods for Encryption Signing (part of Decrypt flow) + abstract createEncryptionSignRequest(params: unknown): Promise; + abstract handleEncryptionSignResponse(params: unknown): Promise; + + // Methods for Node Pricing / Selection Logic + abstract getNodePrices(): Promise; + abstract getMaxNodesForProduct(params: unknown): Promise; + + // Optional: Method for IPFS Fallback Logic + // abstract getIpfsCode(ipfsId: string): Promise; } diff --git a/packages/networks/src/lib/networks/vNaga/NagaNetwork.ts b/packages/networks/src/lib/networks/vNaga/NagaNetwork.ts new file mode 100644 index 000000000..5a61bd434 --- /dev/null +++ b/packages/networks/src/lib/networks/vNaga/NagaNetwork.ts @@ -0,0 +1,168 @@ +import { LitNetwork } from '../../LitNetwork'; +import { LitChainConfig, LitNetworkConfig } from '../../types'; +import { + HTTP, + HTTPS, + LIT_ENDPOINT, + LIT_NETWORK, + LIT_CHAINS, +} from '@lit-protocol/constants'; +import { LitContracts } from '@lit-protocol/contracts-sdk'; // Needed for original _getNodePrices +import { getNodePrices as getNodePricesSdk } from '@lit-protocol/contracts-sdk'; // Renamed import +// import { getMaxPricesForNodeProduct as getMaxPricesForNodeProductSdk, PRODUCT_IDS } from './common/helpers/pricing'; // Assuming helper path - Commented out +import { LitContract } from '@lit-protocol/types'; // Added LitContract import +import { nagaDev } from '@lit-protocol/contracts'; // Import Naga contract data + +// TEMP Placeholder for PRODUCT_IDS until helper is located/created +const PRODUCT_IDS = { + DECRYPTION: 0, + SIGN: 1, + LIT_ACTION: 2, +}; + +// Placeholder types for params until properly defined +type GetNodePricesSdkResponse = { url: string; prices: bigint[] }[]; // Match SDK return type +type GetNodePricesResponse = { url: string; price: bigint }[]; // Still aiming for this format? Need conversion +type GetMaxNodesParams = { + userMaxPrice?: bigint; + product: keyof typeof PRODUCT_IDS; + numRequiredNodes: number; // Need threshold, likely from LitClient config +}; +type GetMaxNodesResponse = GetNodePricesResponse; // Example + +export class NagaNetwork extends LitNetwork { + // Store LitContracts instance if needed across methods + private litContracts: LitContracts | null = null; + + constructor(config?: Omit) { + // Prepare a default LitChainConfig for NagaDev using yellowstone base and naga contract data + const defaultNagaChainConfig: LitChainConfig = { + chain: LIT_CHAINS.yellowstone, // Use yellowstone from LIT_CHAINS + contractData: nagaDev.data.map((c) => ({ + address: c.contracts[0].address_hash, + abi: c.contracts[0].ABI, + name: c.name, + })), + }; + + const nagaConfig: LitNetworkConfig = { + name: LIT_NETWORK.NagaDev, // Defaulting to NagaDev + endpoints: LIT_ENDPOINT, // Naga might use standard endpoints + chainConfig: config?.chainConfig || defaultNagaChainConfig, + httpProtocol: config?.httpProtocol || HTTPS, // Use constant + options: config?.options || {}, + }; + super(nagaConfig); + + // TODO: Init logger if needed for these methods + // TODO: Store necessary config like rpcUrl/contractContext if not in chainConfig + } + + // --- Implementation of Abstract Methods --- + + async getNodePrices(): Promise { + console.log('[NagaNetwork] Getting Node Prices...'); + + // Logic from LitNodeClient._getNodePrices + // TODO: Determine source for networkContext & rpcUrl (e.g., constructor config) + const networkContext = (this.options as any)?.contractContext; // Example: Getting from options + const rpcUrl = (this.options as any)?.rpcUrl; // Example: Getting from options + + if (!networkContext || !rpcUrl) { + console.warn( + '[NagaNetwork] Missing contractContext or rpcUrl in network config for getNodePrices' + ); + return []; + } + + // This returns { url: string; prices: bigint[] }[] + return getNodePricesSdk({ + realmId: 1, + litNetwork: this.name as any, + networkContext: networkContext, // Use sourced value + rpcUrl: rpcUrl, // Use sourced value + nodeProtocol: this.httpProtocol, + }); + } + + async getMaxNodesForProduct( + params: GetMaxNodesParams + ): Promise { + console.log('[NagaNetwork] Getting Max Nodes for Product:', params.product); + + // getNodePrices now returns { url: string; prices: bigint[] }[] + const nodePricesWithMultiple = await this.getNodePrices(); + + // TEMP: We need to decide how to handle multiple prices per node if getMaxPricesForNodeProductSdk expects one. + // For now, let's just log a warning and use a placeholder. + console.warn( + '[NagaNetwork] getNodePrices returned multiple prices per node, but getMaxNodesForProduct might expect one. Placeholder logic used.' + ); + const nodePrices = nodePricesWithMultiple.map((p) => ({ + url: p.url, + price: p.prices[0] || 0n, + })); // TEMP: Take first price + + // Internal helper logic from LitNodeClient.getMaxPricesForNodeProduct + const getUserMaxPrice = () => { + if (params.userMaxPrice !== undefined) { + return params.userMaxPrice; + } + console.log( + `[NagaNetwork] No user-provided maxPrice for ${params.product}; assuming unlimited.` + ); + return 340_282_366_920_938_463_463_374_607_431_768_211_455n; + }; + + // TODO: Locate and use the actual getMaxPricesForNodeProductSdk helper + // return getMaxPricesForNodeProductSdk({ ... }); + + // TEMP Placeholder implementation until helper is found + console.warn( + 'getMaxPricesForNodeProductSdk helper not found/implemented, returning filtered prices' + ); + // Dummy logic: Filter nodes based on the (first) price being <= user max price + const userMax = getUserMaxPrice(); + return nodePrices.filter((p) => p.price <= userMax); + } + + // --- Stubs for other abstract methods --- + + async createSignRequests(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async handleSignResponses(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async createDecryptRequests(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async handleDecryptResponses(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async createExecuteJsRequests(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async handleExecuteJsResponses(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async createSignSessionKeyRequest(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async handleSignSessionKeyResponse(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async createClaimKeyRequest(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async handleClaimKeyResponse(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async createEncryptionSignRequest(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + async handleEncryptionSignResponse(params: unknown): Promise { + throw new Error('Method not implemented.'); + } + // Optional: async getIpfsCode(ipfsId: string): Promise { throw new Error('Method not implemented.'); } +} diff --git a/packages/schemas/src/index.ts b/packages/schemas/src/index.ts index 034c4467f..6a576711e 100644 --- a/packages/schemas/src/index.ts +++ b/packages/schemas/src/index.ts @@ -2,3 +2,4 @@ export * from './lib/encryption'; export * from './lib/models'; export * from './lib/schemas'; export * from './lib/validation'; +export * from './lib/transformers'; diff --git a/packages/schemas/src/lib/transformers/ObjectMapFromArray.ts b/packages/schemas/src/lib/transformers/ObjectMapFromArray.ts new file mode 100644 index 000000000..450da3106 --- /dev/null +++ b/packages/schemas/src/lib/transformers/ObjectMapFromArray.ts @@ -0,0 +1,11 @@ +/** + * @example + * const obj = ['a', 'b', 'c'] + * ObjectMapFromArray(obj) // { a: 'a', b: 'b', c: 'c' } + */ +export const ObjectMapFromArray = (arr: T) => { + return arr.reduce( + (acc, scope) => ({ ...acc, [scope]: scope }), + {} as { [K in T[number]]: K } + ); +}; diff --git a/packages/schemas/src/lib/transformers/index.ts b/packages/schemas/src/lib/transformers/index.ts new file mode 100644 index 000000000..08c150df6 --- /dev/null +++ b/packages/schemas/src/lib/transformers/index.ts @@ -0,0 +1 @@ +export * from './ObjectMapFromArray'; \ No newline at end of file diff --git a/packages/types/src/lib/interfaces.ts b/packages/types/src/lib/interfaces.ts index 0888ce1ec..5762284bc 100644 --- a/packages/types/src/lib/interfaces.ts +++ b/packages/types/src/lib/interfaces.ts @@ -1100,7 +1100,6 @@ export interface BaseSiweMessage { statement?: string; version?: string; chainId?: number; - litNodeClient?: ILitNodeClient; } export interface WithRecap extends BaseSiweMessage { @@ -1118,7 +1117,6 @@ export interface WithCapacityDelegation extends BaseSiweMessage { } export interface CapacityDelegationFields extends BaseSiweMessage { - litNodeClient: ILitNodeClient; delegateeAddresses?: string[]; uses?: string; }