From 53fb39da54923cacf50cc4f9f9076c6fac2ac988 Mon Sep 17 00:00:00 2001 From: anson Date: Fri, 12 Dec 2025 03:10:19 +0000 Subject: [PATCH 1/7] refactor(e2e): tidy up --- package.json | 6 +- packages/e2e/src/e2e-revamp.spec.ts | 172 ++++--- packages/e2e/src/e2e.spec.ts | 320 +------------ packages/e2e/src/helper/constants.ts | 3 + packages/e2e/src/init.ts | 4 +- packages/e2e/src/suites/custom-auth.suite.ts | 118 +++++ packages/e2e/src/suites/endpoints.suite.ts | 423 ++++++++++++++++++ packages/e2e/src/suites/eoa-native.suite.ts | 53 +++ .../pkp-pre-generated-materials.suite.ts | 215 +++++++++ packages/e2e/src/suites/suite-utils.ts | 66 +++ packages/e2e/src/suites/viem.suite.ts | 110 +++++ packages/e2e/src/suites/wrapped-keys.suite.ts | 8 + 12 files changed, 1121 insertions(+), 377 deletions(-) create mode 100644 packages/e2e/src/helper/constants.ts create mode 100644 packages/e2e/src/suites/custom-auth.suite.ts create mode 100644 packages/e2e/src/suites/endpoints.suite.ts create mode 100644 packages/e2e/src/suites/eoa-native.suite.ts create mode 100644 packages/e2e/src/suites/pkp-pre-generated-materials.suite.ts create mode 100644 packages/e2e/src/suites/suite-utils.ts create mode 100644 packages/e2e/src/suites/viem.suite.ts create mode 100644 packages/e2e/src/suites/wrapped-keys.suite.ts diff --git a/package.json b/package.json index 17d706fd4..7c0dd1b95 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "lint": "npx nx run-many --target=lint --all", "lint:fix": "npx nx run-many --target=lint --all -- --fix", "format:check": "npx nx format:check --all", - "test:e2e": "npx jest --clearCache --config ./jest.e2e.config.ts && LOG_LEVEL=${LOG_LEVEL:-silent} dotenvx run --env-file=.env -- jest --runInBand --detectOpenHandles --forceExit --config ./jest.e2e.config.ts --testTimeout=50000000 --runTestsByPath packages/e2e/src/e2e.spec.ts", - "test:target": "npx jest --clearCache --config ./jest.e2e.config.ts && LOG_LEVEL=${LOG_LEVEL:-silent} dotenvx run --env-file=.env -- jest --runInBand --detectOpenHandles --forceExit --config ./jest.e2e.config.ts --testTimeout=50000000", - "test:e2e:ci": "npx jest --clearCache --config ./jest.e2e.config.ts && LOG_LEVEL=${LOG_LEVEL:-silent} npx jest --runInBand --detectOpenHandles --forceExit --config ./jest.e2e.config.ts --testTimeout=50000000 --runTestsByPath", + "test:e2e": "npx jest --clearCache --config ./jest.e2e.config.ts && LOG_LEVEL=${LOG_LEVEL:-silent} dotenvx run --env-file=.env -- jest --runInBand --detectOpenHandles --forceExit --config ./jest.e2e.config.ts --testTimeout=50000000 --cacheDirectory .jest-cache --runTestsByPath packages/e2e/src/e2e.spec.ts", + "test:target": "npx jest --clearCache --config ./jest.e2e.config.ts && LOG_LEVEL=${LOG_LEVEL:-silent} dotenvx run --env-file=.env -- jest --runInBand --detectOpenHandles --forceExit --config ./jest.e2e.config.ts --testTimeout=50000000 --cacheDirectory .jest-cache", + "test:e2e:ci": "npx jest --clearCache --config ./jest.e2e.config.ts && LOG_LEVEL=${LOG_LEVEL:-silent} npx jest --runInBand --detectOpenHandles --forceExit --config ./jest.e2e.config.ts --testTimeout=50000000 --cacheDirectory .jest-cache --runTestsByPath", "pretest:e2e": "pnpm run generate:lit-actions", "pretest:e2e:ci": "pnpm run generate:lit-actions", "pretest:custom": "pnpm run generate:lit-actions", diff --git a/packages/e2e/src/e2e-revamp.spec.ts b/packages/e2e/src/e2e-revamp.spec.ts index 5bd2c40e3..e233de196 100644 --- a/packages/e2e/src/e2e-revamp.spec.ts +++ b/packages/e2e/src/e2e-revamp.spec.ts @@ -1,74 +1,138 @@ -// WIP! Use e2e.spec.ts instead import { createEnvVars } from './helper/createEnvVars'; import { createTestEnv } from './helper/createTestEnv'; +import { getNetworkConfig } from './helper/network'; +import type { ResolvedNetwork } from './helper/network'; +import type { AuthContext } from './types'; + +/** + * How to run: + * - Full revamped suite (canonical): + * `NETWORK=naga-dev pnpm run test:e2e` + * + * - Just this file: + * `NETWORK=naga-dev pnpm exec dotenvx run --env-file=.env -- jest --runInBand --config ./jest.e2e.config.ts --cacheDirectory .jest-cache --runTestsByPath packages/e2e/src/e2e-revamp.spec.ts` + * + * - Single test / suite (use Jest name filtering): + * `NETWORK=naga-dev pnpm exec dotenvx run --env-file=.env -- jest --runInBand --config ./jest.e2e.config.ts --cacheDirectory .jest-cache --runTestsByPath packages/e2e/src/e2e-revamp.spec.ts --testNamePattern "EOA auth.*pkpSign"` + * + * - Alternative local workflow: + * add `.only` to a `describe` or `it` block temporarily. + */ import { createTestAccount, CreateTestAccountResult, } from './helper/createTestAccount'; +import { registerEndpointSuite } from './suites/endpoints.suite'; +import { registerViemSuite } from './suites/viem.suite'; +import { registerPkpPreGeneratedMaterialsSuite } from './suites/pkp-pre-generated-materials.suite'; +import { registerEoaNativeSuite } from './suites/eoa-native.suite'; +import { registerWrappedKeysSuite } from './suites/wrapped-keys.suite'; +import { registerCustomAuthSuite } from './suites/custom-auth.suite'; +import { registerPaymentDelegationTicketSuite } from './tickets/delegation.suite'; -const registerEoaExecuteJsSuite = () => { - describe('EOA auth (revamp)', () => { - let testEnv: Awaited>; - let alice: CreateTestAccountResult; +const SELECTED_NETWORK = process.env['NETWORK']; +const IS_PAID_NETWORK = SELECTED_NETWORK !== 'naga-dev'; +const describeIfPaid = IS_PAID_NETWORK ? describe : describe.skip; - beforeAll(async () => { - const envVars = createEnvVars(); - testEnv = await createTestEnv(envVars); +describe('revamped e2e suite', () => { + let envVars: ReturnType; + let testEnv: Awaited>; + let resolvedNetwork: ResolvedNetwork; + let alice: CreateTestAccountResult; + let bob: CreateTestAccountResult; - alice = await createTestAccount(testEnv, { - label: 'Alice', - fundAccount: true, - hasEoaAuthContext: true, - fundLedger: true, - hasPKP: true, - fundPKP: true, - hasPKPAuthContext: true, - fundPKPLedger: true, - }); - }); + beforeAll(async () => { + envVars = createEnvVars(); + testEnv = await createTestEnv(envVars); + const { name, importName, type } = getNetworkConfig(envVars.network); + resolvedNetwork = { name, importName, type, networkModule: testEnv.networkModule }; - test('executeJs signs with Alice PKP', async () => { - if (!alice.eoaAuthContext) { - throw new Error('Alice is missing an EOA auth context'); - } - if (!alice.pkp) { - throw new Error('Alice is missing a PKP'); - } + alice = await createTestAccount(testEnv, { + label: 'Alice', + fundAccount: true, + hasEoaAuthContext: true, + fundLedger: true, + hasPKP: true, + fundPKP: true, + hasPKPAuthContext: true, + fundPKPLedger: true, + }); - const litActionCode = ` -(async () => { - const { sigName, toSign, publicKey } = jsParams; - const { keccak256, arrayify } = ethers.utils; + bob = await createTestAccount(testEnv, { + label: 'Bob', + fundAccount: true, + hasEoaAuthContext: true, + fundLedger: true, + hasPKP: false, + fundPKP: false, + fundPKPLedger: false, + hasPKPAuthContext: false, + }); + }); - const toSignBytes = new TextEncoder().encode(toSign); - const toSignBytes32 = keccak256(toSignBytes); - const toSignBytes32Array = arrayify(toSignBytes32); + const authModes = [ + { + label: 'EOA', + getAuthContext: () => alice.eoaAuthContext!, + includePaymentFlows: true, + getAccsAddress: (_authContext: AuthContext) => alice.account.address, + }, + { + label: 'PKP', + getAuthContext: () => alice.pkpAuthContext!, + includePaymentFlows: false, + }, + ] satisfies Array<{ + label: string; + getAuthContext: () => AuthContext; + includePaymentFlows: boolean; + getAccsAddress?: (authContext: AuthContext) => string; + }>; - const sigShare = await Lit.Actions.signEcdsa({ - toSign: toSignBytes32Array, - publicKey, - sigName, - }); -})();`; + authModes.forEach((mode) => { + describe(`${mode.label} auth`, () => { + const getTestEnv = () => testEnv; + const getAliceAccount = () => alice; + const getBobAccount = () => bob; + const getPkpPublicKey = () => { + if (!alice.pkp) { + throw new Error('Alice is missing a PKP'); + } + return alice.pkp.pubkey; + }; + const getPkpEthAddress = () => { + if (!alice.pkp) { + throw new Error('Alice is missing a PKP'); + } + return alice.pkp.ethAddress as `0x${string}`; + }; - const toSign = 'Revamp executeJs test'; - const result = await testEnv.litClient.executeJs({ - code: litActionCode, - authContext: alice.eoaAuthContext, - jsParams: { - message: toSign, - sigName: 'revamp-e2e-sig', - toSign, - publicKey: alice.pkp.pubkey, - }, + registerEndpointSuite(getTestEnv, mode.getAuthContext, { + getPkpPublicKey, + getPkpEthAddress, + getAliceAccount, + getBobAccount, + includePaymentFlows: mode.includePaymentFlows, + getAccsAddress: mode.getAccsAddress, }); - expect(result).toBeDefined(); - expect(result.signatures).toBeDefined(); + registerViemSuite(getTestEnv, mode.getAuthContext, getPkpPublicKey); }); }); -}; -describe('revamped e2e suite', () => { - registerEoaExecuteJsSuite(); + registerPkpPreGeneratedMaterialsSuite( + () => testEnv, + () => alice, + () => resolvedNetwork + ); + + registerEoaNativeSuite(() => testEnv, () => alice); + + registerWrappedKeysSuite(); + + registerCustomAuthSuite(() => testEnv, () => bob); +}); + +describeIfPaid('Paid networks tests', () => { + registerPaymentDelegationTicketSuite(); }); diff --git a/packages/e2e/src/e2e.spec.ts b/packages/e2e/src/e2e.spec.ts index 211f0d88f..8ed7c03cc 100644 --- a/packages/e2e/src/e2e.spec.ts +++ b/packages/e2e/src/e2e.spec.ts @@ -1,317 +1,3 @@ -import type { AuthContext } from '@lit-protocol/e2e'; -import { - createCustomAuthContext, - createEncryptDecryptFlowTest, - createEoaNativeAuthFlowTest, - createExecuteJsTest, - createPaymentDelegationFlowTest, - createPaymentManagerFlowTest, - createPkpAuthContextWithPreGeneratedMaterials, - createPkpEncryptDecryptTest, - createPkpPermissionsManagerFlowTest, - createPkpSignTest, - createPregenDelegationServerReuseTest, - createViemSignMessageTest, - createViemSignTransactionTest, - createViemSignTypedDataTest, - createViewPKPsByAddressTest, - createViewPKPsByAuthDataTest, - init, - registerPaymentDelegationTicketSuite, -} from '@lit-protocol/e2e'; -import { registerWrappedKeysTests } from './test-helpers/executeJs/wrappedKeys'; - -const SELECTED_NETWORK = process.env['NETWORK']; -const IS_PAID_NETWORK = SELECTED_NETWORK !== 'naga-dev'; -const RPC_OVERRIDE_ENV_VAR = - SELECTED_NETWORK === 'naga' || SELECTED_NETWORK === 'naga-proto' - ? 'LIT_MAINNET_RPC_URL' - : 'LIT_YELLOWSTONE_PRIVATE_RPC_URL'; -const describeIfPaid = IS_PAID_NETWORK ? describe : describe.skip; -const RPC_OVERRIDE = process.env[RPC_OVERRIDE_ENV_VAR]; -if (RPC_OVERRIDE) { - console.log( - `๐Ÿงช E2E: Using RPC override (${RPC_OVERRIDE_ENV_VAR}):`, - RPC_OVERRIDE - ); -} - -describe('all', () => { - describe('full alice, bob, and eve', () => { - let ctx: Awaited>; - - // Auth contexts for testing - let eveCustomAuthContext: AuthContext; - - beforeAll(async () => { - try { - ctx = await init(); - // Replenish ledger balances for the Alice EOA + PKP before the suite runs. - await ctx.masterDepositForUser(ctx.aliceViemAccount.address); - await ctx.masterDepositForUser(ctx.aliceViemAccountPkp.ethAddress); - eveCustomAuthContext = await createCustomAuthContext(ctx); - } catch (e) { - console.error('โŒ Failed to initialise E2E test context', e); - process.exit(1); - } - }); - - describe('EOA Auth', () => { - console.log('๐Ÿ” Testing using Externally Owned Account authentication'); - - describe('endpoints', () => { - it('pkpSign', () => - createPkpSignTest(ctx, () => ctx.aliceEoaAuthContext)()); - it('executeJs', () => - createExecuteJsTest(ctx, () => ctx.aliceEoaAuthContext)()); - it('viewPKPsByAddress', () => createViewPKPsByAddressTest(ctx)()); - it('viewPKPsByAuthData', () => - createViewPKPsByAuthDataTest(ctx, () => ctx.aliceEoaAuthContext)()); - it('pkpEncryptDecrypt', () => - createPkpEncryptDecryptTest(ctx, () => ctx.aliceEoaAuthContext)()); - it('encryptDecryptFlow', () => - createEncryptDecryptFlowTest(ctx, () => ctx.aliceEoaAuthContext)()); - it('pkpPermissionsManagerFlow', () => - createPkpPermissionsManagerFlowTest( - ctx, - () => ctx.aliceEoaAuthContext - )()); - it('paymentManagerFlow', () => - createPaymentManagerFlowTest(ctx, () => ctx.aliceEoaAuthContext)()); - it('paymentDelegationFlow', () => - createPaymentDelegationFlowTest( - ctx, - () => ctx.aliceEoaAuthContext - )()); - - describe('integrations', () => { - describe('pkp viem account', () => { - it('sign message', () => - createViemSignMessageTest(ctx, () => ctx.aliceEoaAuthContext)()); - it('sign transaction', () => - createViemSignTransactionTest( - ctx, - () => ctx.aliceEoaAuthContext - )()); - it('sign typed data', () => - createViemSignTypedDataTest( - ctx, - () => ctx.aliceEoaAuthContext - )()); - }); - }); - }); - - describe('PKP Auth', () => { - console.log('๐Ÿ” Testing using Programmable Key Pair authentication'); - - describe('endpoints', () => { - it('pkpSign', () => - createPkpSignTest(ctx, () => ctx.alicePkpAuthContext)()); - it('executeJs', () => - createExecuteJsTest(ctx, () => ctx.alicePkpAuthContext)()); - it('viewPKPsByAddress', () => createViewPKPsByAddressTest(ctx)()); - it('viewPKPsByAuthData', () => - createViewPKPsByAuthDataTest(ctx, () => ctx.alicePkpAuthContext)()); - it('pkpEncryptDecrypt', () => - createPkpEncryptDecryptTest(ctx, () => ctx.alicePkpAuthContext)()); - it('encryptDecryptFlow', () => - createEncryptDecryptFlowTest(ctx, () => ctx.alicePkpAuthContext)()); - it('pkpPermissionsManagerFlow', () => - createPkpPermissionsManagerFlowTest( - ctx, - () => ctx.alicePkpAuthContext - )()); - }); - - describe('integrations', () => { - describe('pkp viem account', () => { - it('sign message', () => - createViemSignMessageTest(ctx, () => ctx.alicePkpAuthContext)()); - it('sign transaction', () => - createViemSignTransactionTest( - ctx, - () => ctx.alicePkpAuthContext - )()); - it('sign typed data', () => - createViemSignTypedDataTest( - ctx, - () => ctx.alicePkpAuthContext - )()); - }); - }); - }); - - describe('Custom Auth', () => { - console.log('๐Ÿ” Testing using Custom authentication method'); - - describe('endpoints', () => { - it('pkpSign', () => - createPkpSignTest( - ctx, - () => eveCustomAuthContext, - ctx.eveViemAccountPkp.pubkey - )()); - it('executeJs', () => - createExecuteJsTest( - ctx, - () => eveCustomAuthContext, - ctx.eveViemAccountPkp.pubkey - )()); - it('viewPKPsByAddress', () => createViewPKPsByAddressTest(ctx)()); - it('viewPKPsByAuthData', () => - createViewPKPsByAuthDataTest(ctx, () => eveCustomAuthContext)()); - it('pkpEncryptDecrypt', () => - createPkpEncryptDecryptTest(ctx, () => ctx.aliceEoaAuthContext)()); - it('encryptDecryptFlow', () => - createEncryptDecryptFlowTest(ctx, () => ctx.aliceEoaAuthContext)()); - - // Disable for now because it requires a different flow - // it('pkpPermissionsManagerFlow', () => - // createPkpPermissionsManagerFlowTest( - // ctx, - // () => eveCustomAuthContext, ctx.eveViemAccountPkp.pubkey - // )()); - }); - - // describe('integrations', () => { - // describe('pkp viem account', () => { - // it('sign message', () => - // createViemSignMessageTest(ctx, () => eveCustomAuthContext, ctx.eveViemAccountPkp.pubkey)()); - // it('sign transaction', () => - // createViemSignTransactionTest(ctx, () => eveCustomAuthContext, ctx.eveViemAccountPkp.pubkey)()); - // it('sign typed data', () => - // createViemSignTypedDataTest(ctx, () => eveCustomAuthContext, ctx.eveViemAccountPkp.pubkey)()); - // }); - // }); - }); - - describe('PKP Auth with Pre-generated Materials', () => { - console.log('๐Ÿ” Testing PKP auth with pre-generated session materials'); - - let preGeneratedAuthContext: any; - - beforeAll(async () => { - try { - preGeneratedAuthContext = - await createPkpAuthContextWithPreGeneratedMaterials(ctx); - } catch (e) { - console.error('Failed to create pre-generated auth context:', e); - throw e; - } - }); - - describe('endpoints', () => { - it('pkpSign with pre-generated materials', () => - createPkpSignTest(ctx, () => preGeneratedAuthContext)()); - - it('executeJs with pre-generated materials', () => - createExecuteJsTest(ctx, () => preGeneratedAuthContext)()); - - it('pkpEncryptDecrypt with pre-generated materials', () => - createPkpEncryptDecryptTest(ctx, () => preGeneratedAuthContext)()); - }); - - describe('error handling', () => { - it('should reject when only sessionKeyPair is provided', async () => { - const tempAuthContext = await ctx.authManager.createPkpAuthContext({ - authData: ctx.aliceViemAccountAuthData, - pkpPublicKey: ctx.aliceViemAccountPkp.pubkey, - authConfig: { - resources: [['pkp-signing', '*']], - expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), - }, - litClient: ctx.litClient, - }); - - const sessionKeyPair = tempAuthContext.sessionKeyPair; - - await expect( - ctx.authManager.createPkpAuthContext({ - authData: ctx.aliceViemAccountAuthData, - pkpPublicKey: ctx.aliceViemAccountPkp.pubkey, - authConfig: { - resources: [['pkp-signing', '*']], - expiration: new Date( - Date.now() + 1000 * 60 * 15 - ).toISOString(), - }, - litClient: ctx.litClient, - sessionKeyPair, // Only providing sessionKeyPair - // delegationAuthSig is missing - }) - ).rejects.toThrow( - 'Both sessionKeyPair and delegationAuthSig must be provided together, or neither should be provided' - ); - }); - - it('should reject when only delegationAuthSig is provided', async () => { - const tempAuthContext = await ctx.authManager.createPkpAuthContext({ - authData: ctx.aliceViemAccountAuthData, - pkpPublicKey: ctx.aliceViemAccountPkp.pubkey, - authConfig: { - resources: [['pkp-signing', '*']], - expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), - }, - litClient: ctx.litClient, - }); - - const delegationAuthSig = - await tempAuthContext.authNeededCallback(); - - await expect( - ctx.authManager.createPkpAuthContext({ - authData: ctx.aliceViemAccountAuthData, - pkpPublicKey: ctx.aliceViemAccountPkp.pubkey, - authConfig: { - resources: [['pkp-signing', '*']], - expiration: new Date( - Date.now() + 1000 * 60 * 15 - ).toISOString(), - }, - litClient: ctx.litClient, - // sessionKeyPair is missing - delegationAuthSig, // Only providing delegationAuthSig - }) - ).rejects.toThrow( - 'Both sessionKeyPair and delegationAuthSig must be provided together, or neither should be provided' - ); - }); - }); - - /** - * This scenario mirrors the client/server hand-off used in production: - * 1. A client generates session materials and a delegation auth sig. - * 2. The bundle travels over the wire (simulated via JSON serialisation). - * 3. A server restores those materials with a fresh AuthManager instance and - * proves it can sign with the delegated PKP using an independently created LitClient. - * Keeping this in the main e2e suite ensures we catch regressions in CI without - * relying on the ad-hoc ticket test. - */ - describe('server reuse flow', () => { - it('should sign using materials shipped over the wire', () => - createPregenDelegationServerReuseTest({ - authManager: ctx.authManager, - authData: ctx.aliceViemAccountAuthData, - pkpPublicKey: ctx.aliceViemAccountPkp.pubkey, - clientLitClient: ctx.litClient, - resolvedNetwork: ctx.resolvedNetwork, - })()); - }); - }); - - describe('EOA Native', () => { - console.log('๐Ÿ” Testing EOA native authentication and PKP minting'); - it('eoaNativeAuthFlow', () => createEoaNativeAuthFlowTest(ctx)()); - }); - }); - describe('wrapped keys', () => { - registerWrappedKeysTests(); - }); - }); -}); - -// ====== These tests only run on paid networks ====== -describeIfPaid('Paid networks tests', () => { - registerPaymentDelegationTicketSuite(); -}); +// Canonical E2E suite orchestrator. +// The implementation now lives in `e2e-revamp.spec.ts` with modular suites. +import './e2e-revamp.spec'; diff --git a/packages/e2e/src/helper/constants.ts b/packages/e2e/src/helper/constants.ts new file mode 100644 index 000000000..b8bc4b416 --- /dev/null +++ b/packages/e2e/src/helper/constants.ts @@ -0,0 +1,3 @@ +export const EVE_VALIDATION_IPFS_CID = + 'QmcxWmo3jefFsPUnskJXYBwsJYtiFuMAH1nDQEs99AwzDe'; + diff --git a/packages/e2e/src/init.ts b/packages/e2e/src/init.ts index cfdd3f7dc..090eeff21 100644 --- a/packages/e2e/src/init.ts +++ b/packages/e2e/src/init.ts @@ -14,6 +14,7 @@ import { } from './helper/network'; import { z } from 'zod'; import { fundAccount } from './helper/fundAccount'; +import { EVE_VALIDATION_IPFS_CID } from './helper/constants'; import { getOrCreatePkp } from './helper/pkp-utils'; import { PKPData, AuthData, CustomAuthData } from '@lit-protocol/schemas'; import { @@ -46,9 +47,6 @@ const LIVE_NETWORK_LEDGER_DEPOSIT_AMOUNT = '1'; const MAINNET_NETWORK_FUNDING_AMOUNT = '0.01'; const MAINNET_LEDGER_DEPOSIT_AMOUNT = '0.01'; -const EVE_VALIDATION_IPFS_CID = - 'QmcxWmo3jefFsPUnskJXYBwsJYtiFuMAH1nDQEs99AwzDe'; - type BaseInitResult = { litClient: LitClientInstance; authManager: AuthManagerInstance; diff --git a/packages/e2e/src/suites/custom-auth.suite.ts b/packages/e2e/src/suites/custom-auth.suite.ts new file mode 100644 index 000000000..326239224 --- /dev/null +++ b/packages/e2e/src/suites/custom-auth.suite.ts @@ -0,0 +1,118 @@ +import { utils as litUtils } from '@lit-protocol/lit-client'; +import type { CustomAuthData } from '@lit-protocol/schemas'; +import type { AuthContext } from '../types'; +import { createTestAccount, CreateTestAccountResult } from '../helper/createTestAccount'; +import type { TestEnv } from '../helper/createTestEnv'; +import { fundAccount } from '../helper/fundAccount'; +import { EVE_VALIDATION_IPFS_CID } from '../helper/constants'; +import { registerEndpointSuite } from './endpoints.suite'; + +export function registerCustomAuthSuite( + getTestEnv: () => TestEnv, + getBobAccount: () => CreateTestAccountResult +) { + describe('Custom auth', () => { + let eve: CreateTestAccountResult; + let evePkp: { pubkey: string; ethAddress: `0x${string}`; tokenId: bigint }; + let eveCustomAuthData: CustomAuthData; + let eveCustomAuthContext: AuthContext; + + beforeAll(async () => { + const testEnv = getTestEnv(); + + eve = await createTestAccount(testEnv, { + label: 'Eve', + fundAccount: true, + fundLedger: true, + hasPKP: false, + fundPKP: false, + fundPKPLedger: false, + hasEoaAuthContext: false, + hasPKPAuthContext: false, + }); + + // Must match the validation Lit Action's expected dapp name. + const uniqueDappName = 'e2e-test-dapp'; + const authMethodConfig = + litUtils.generateUniqueAuthMethodType({ uniqueDappName }); + eveCustomAuthData = litUtils.generateAuthData({ + uniqueDappName, + uniqueAuthMethodType: authMethodConfig.bigint, + userId: 'eve', + }); + + const { pkpData } = await testEnv.litClient.mintWithCustomAuth({ + account: eve.account, + authData: eveCustomAuthData, + scope: 'sign-anything', + validationIpfsCid: EVE_VALIDATION_IPFS_CID, + }); + + evePkp = { + ...pkpData.data, + tokenId: pkpData.data.tokenId, + ethAddress: pkpData.data.ethAddress as `0x${string}`, + }; + + await fundAccount( + evePkp.ethAddress, + testEnv.masterAccount, + testEnv.networkModule, + { + label: 'Eve PKP', + ifLessThan: testEnv.config.nativeFundingAmount, + thenFund: testEnv.config.nativeFundingAmount, + } + ); + + await testEnv.masterPaymentManager.depositForUser({ + userAddress: evePkp.ethAddress, + amountInEth: testEnv.config.ledgerDepositAmount, + }); + + eveCustomAuthContext = + await testEnv.authManager.createCustomAuthContext({ + pkpPublicKey: evePkp.pubkey, + authConfig: { + resources: [ + ['pkp-signing', '*'], + ['lit-action-execution', '*'], + ['access-control-condition-decryption', '*'], + ], + expiration: new Date( + Date.now() + 1000 * 60 * 15 + ).toISOString(), + }, + litClient: testEnv.litClient, + customAuthParams: { + litActionIpfsId: EVE_VALIDATION_IPFS_CID, + jsParams: { + pkpPublicKey: evePkp.pubkey, + username: 'eve', + password: 'lit', + authMethodId: eveCustomAuthData.authMethodId, + }, + }, + }); + }); + + const getEveAccount = () => eve; + const getPkpPublicKey = () => evePkp.pubkey; + const getPkpEthAddress = () => evePkp.ethAddress; + + registerEndpointSuite( + getTestEnv, + () => eveCustomAuthContext, + { + getPkpPublicKey, + getPkpEthAddress, + getAliceAccount: getEveAccount, + getBobAccount, + includePaymentFlows: false, + includeEncryptDecryptFlow: false, + includePermissionsFlow: false, + includeViewPkpsByAuthData: false, + } + ); + }); +} diff --git a/packages/e2e/src/suites/endpoints.suite.ts b/packages/e2e/src/suites/endpoints.suite.ts new file mode 100644 index 000000000..bc7db100b --- /dev/null +++ b/packages/e2e/src/suites/endpoints.suite.ts @@ -0,0 +1,423 @@ +import { createAccBuilder } from '@lit-protocol/access-control-conditions'; +import { ViemAccountAuthenticator } from '@lit-protocol/auth'; +import type { AuthData } from '@lit-protocol/schemas'; +import type { AuthContext } from '../types'; +import type { TestEnv } from '../helper/createTestEnv'; +import type { CreateTestAccountResult } from '../helper/createTestAccount'; +import { + PKP_SIGN_TRANSIENT_FRAGMENTS, + SIGN_ECDSA_LIT_ACTION_CODE, + withRetry, +} from './suite-utils'; + +export type EndpointSuiteOptions = { + getPkpPublicKey: () => string; + getPkpEthAddress: () => `0x${string}`; + getAliceAccount: () => CreateTestAccountResult; + getBobAccount: () => CreateTestAccountResult; + includePaymentFlows?: boolean; + includeEncryptDecryptFlow?: boolean; + includePermissionsFlow?: boolean; + includeViewPkpsByAuthData?: boolean; + authDataOverride?: AuthData; + getAccsAddress?: (authContext: AuthContext) => string; +}; + +export function registerEndpointSuite( + getTestEnv: () => TestEnv, + getAuthContext: () => AuthContext, + opts: EndpointSuiteOptions +) { + describe('endpoints', () => { + it('pkpSign', async () => { + const testEnv = getTestEnv(); + const res = await withRetry( + () => + testEnv.litClient.chain.ethereum.pkpSign({ + authContext: getAuthContext(), + pubKey: opts.getPkpPublicKey(), + toSign: 'Hello, world!', + }), + { transientMessageFragments: PKP_SIGN_TRANSIENT_FRAGMENTS } + ); + + expect(res.signature).toBeDefined(); + }); + + it('executeJs', async () => { + const testEnv = getTestEnv(); + const result = await testEnv.litClient.executeJs({ + code: SIGN_ECDSA_LIT_ACTION_CODE, + authContext: getAuthContext(), + jsParams: { + message: 'Test message from revamp e2e executeJs', + sigName: 'revamp-e2e-sig', + toSign: 'Test message from revamp e2e executeJs', + publicKey: opts.getPkpPublicKey(), + }, + }); + + expect(result).toBeDefined(); + expect(result.signatures).toBeDefined(); + }); + + it('viewPKPsByAddress', async () => { + const testEnv = getTestEnv(); + const pkps = await testEnv.litClient.viewPKPsByAddress({ + ownerAddress: opts.getPkpEthAddress(), + pagination: { limit: 10, offset: 0 }, + }); + + expect(pkps).toBeDefined(); + expect(Array.isArray(pkps.pkps)).toBe(true); + expect(typeof pkps.pagination.total).toBe('number'); + expect(typeof pkps.pagination.hasMore).toBe('boolean'); + }); + + if (opts.includeViewPkpsByAuthData ?? true) { + it('viewPKPsByAuthData', async () => { + const testEnv = getTestEnv(); + const aliceAccount = opts.getAliceAccount(); + const authData = + opts.authDataOverride ?? + aliceAccount.authData ?? + (await ViemAccountAuthenticator.authenticate(aliceAccount.account)); + + const pkps = await testEnv.litClient.viewPKPsByAuthData({ + authData: { + authMethodType: authData.authMethodType, + authMethodId: authData.authMethodId, + accessToken: authData.accessToken || 'mock-token', + }, + pagination: { limit: 10, offset: 0 }, + }); + + expect(pkps).toBeDefined(); + expect(Array.isArray(pkps.pkps)).toBe(true); + expect(pkps.pkps.length).toBeGreaterThan(0); + + const firstPkp = pkps.pkps[0]; + expect(firstPkp.tokenId).toBeDefined(); + expect(firstPkp.pubkey).toBeDefined(); + expect(firstPkp.ethAddress).toBeDefined(); + }); + } + + it('pkpEncryptDecrypt', async () => { + const testEnv = getTestEnv(); + const authContext = getAuthContext(); + const addressForAccs = + opts.getAccsAddress?.(authContext) ?? opts.getPkpEthAddress(); + + const builder = createAccBuilder(); + const accs = builder + .requireWalletOwnership(addressForAccs) + .on('ethereum') + .build(); + + const dataToEncrypt = 'Hello from PKP encrypt-decrypt revamp test!'; + const encryptedData = await testEnv.litClient.encrypt({ + dataToEncrypt, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + }); + + expect(encryptedData.ciphertext).toBeDefined(); + expect(encryptedData.dataToEncryptHash).toBeDefined(); + + const decryptedData = await testEnv.litClient.decrypt({ + data: encryptedData, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + authContext, + }); + + expect(decryptedData.convertedData).toBe(dataToEncrypt); + }); + + if (opts.includeEncryptDecryptFlow ?? true) { + it('encryptDecryptFlow', async () => { + const testEnv = getTestEnv(); + const aliceAccount = opts.getAliceAccount(); + const bobAccount = opts.getBobAccount(); + const authContext = getAuthContext(); + const senderAddress = + opts.getAccsAddress?.(authContext) ?? opts.getPkpEthAddress(); + const builder = createAccBuilder(); + const accs = builder + .requireWalletOwnership(bobAccount.account.address) + .on('ethereum') + .build(); + + const stringData = 'Hello from encrypt-decrypt flow revamp test!'; + const encryptedStringData = await testEnv.litClient.encrypt({ + dataToEncrypt: stringData, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + }); + + expect(encryptedStringData.metadata?.dataType).toBe('string'); + + const jsonData = { + message: 'Test JSON data', + sender: senderAddress, + recipient: bobAccount.account.address, + timestamp: Date.now(), + }; + + const encryptedJsonData = await testEnv.litClient.encrypt({ + dataToEncrypt: jsonData, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + }); + + expect(encryptedJsonData.metadata?.dataType).toBe('json'); + + const uint8Data = new Uint8Array([72, 101, 108, 108, 111]); + const encryptedUint8Data = await testEnv.litClient.encrypt({ + dataToEncrypt: uint8Data, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + }); + + expect(encryptedUint8Data.ciphertext).toBeDefined(); + + const documentData = new TextEncoder().encode( + 'This is a PDF document content...' + ); + const encryptedFileData = await testEnv.litClient.encrypt({ + dataToEncrypt: documentData, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + metadata: { + dataType: 'file', + mimeType: 'application/pdf', + filename: 'secret-document.pdf', + size: documentData.length, + custom: { + author: 'Alice', + createdDate: new Date().toISOString(), + confidential: true, + }, + }, + }); + + expect(encryptedFileData.metadata?.dataType).toBe('file'); + + const bobAuthContext = + bobAccount.eoaAuthContext ?? + (await testEnv.authManager.createEoaAuthContext({ + config: { account: bobAccount.account }, + authConfig: { + domain: 'localhost', + statement: 'Decrypt test data', + expiration: new Date( + Date.now() + 1000 * 60 * 60 * 24 + ).toISOString(), + resources: [['access-control-condition-decryption', '*']], + }, + litClient: testEnv.litClient, + })); + + const decryptedStringResponse = await testEnv.litClient.decrypt({ + data: encryptedStringData, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + authContext: bobAuthContext, + }); + + expect(decryptedStringResponse.convertedData).toBe(stringData); + + const decryptedJsonResponse = await testEnv.litClient.decrypt({ + ciphertext: encryptedJsonData.ciphertext, + dataToEncryptHash: encryptedJsonData.dataToEncryptHash, + metadata: encryptedJsonData.metadata, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + authContext: bobAuthContext, + }); + + expect(decryptedJsonResponse.convertedData).toEqual(jsonData); + + const decryptedUint8Response = await testEnv.litClient.decrypt({ + data: encryptedUint8Data, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + authContext: bobAuthContext, + }); + + if (decryptedUint8Response.convertedData) { + expect(decryptedUint8Response.convertedData).toEqual(uint8Data); + } else { + expect(decryptedUint8Response.decryptedData).toEqual(uint8Data); + } + + const decryptedFileResponse = await testEnv.litClient.decrypt({ + data: encryptedFileData, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + authContext: bobAuthContext, + }); + + expect(decryptedFileResponse.metadata?.dataType).toBe('file'); + expect(decryptedFileResponse.metadata?.filename).toBe( + 'secret-document.pdf' + ); + expect(decryptedFileResponse.metadata?.custom?.author).toBe('Alice'); + + if ( + typeof File !== 'undefined' && + decryptedFileResponse.convertedData instanceof File + ) { + const fileArrayBuffer = + await decryptedFileResponse.convertedData.arrayBuffer(); + const fileUint8Array = new Uint8Array(fileArrayBuffer); + expect(fileUint8Array).toEqual(documentData); + } else { + expect(decryptedFileResponse.convertedData).toEqual(documentData); + } + }); + } + + if (opts.includePermissionsFlow ?? true) { + it('pkpPermissionsManagerFlow', async () => { + const testEnv = getTestEnv(); + const aliceAccount = opts.getAliceAccount(); + const authContext = getAuthContext(); + const pkpPublicKey = opts.getPkpPublicKey(); + + const pkpViemAccount = await testEnv.litClient.getPkpViemAccount({ + pkpPublicKey, + authContext, + chainConfig: testEnv.litClient.getChainConfig().viemConfig, + }); + + const pkpPermissionsManager = + await testEnv.litClient.getPKPPermissionsManager({ + pkpIdentifier: { tokenId: aliceAccount.pkp?.tokenId }, + account: pkpViemAccount, + }); + + const initialContext = + await pkpPermissionsManager.getPermissionsContext(); + const initialAuthMethodsCount = initialContext.authMethods.length; + + const testAuthMethodParams = { + authMethodType: 1, + authMethodId: '0x1234567890abcdef1234567890abcdef12345678', + userPubkey: + '0x04abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + scopes: ['sign-anything'] as ( + | 'sign-anything' + | 'no-permissions' + | 'personal-sign' + )[], + }; + + const addAuthMethodTx = + await pkpPermissionsManager.addPermittedAuthMethod( + testAuthMethodParams + ); + expect(addAuthMethodTx.receipt.status).toBe('success'); + + const authMethodsAfterAdd = + await pkpPermissionsManager.getPermittedAuthMethods(); + expect(authMethodsAfterAdd.length).toBe(initialAuthMethodsCount + 1); + + const removeScopeTx = + await pkpPermissionsManager.removePermittedAuthMethodScope({ + authMethodType: testAuthMethodParams.authMethodType, + authMethodId: testAuthMethodParams.authMethodId, + scopeId: 1, + }); + expect(removeScopeTx.receipt.status).toBe('success'); + + const removeAuthMethodTx = + await pkpPermissionsManager.removePermittedAuthMethod({ + authMethodType: testAuthMethodParams.authMethodType, + authMethodId: testAuthMethodParams.authMethodId, + }); + expect(removeAuthMethodTx.receipt.status).toBe('success'); + + const finalAuthMethods = + await pkpPermissionsManager.getPermittedAuthMethods(); + expect(finalAuthMethods.length).toBe(initialAuthMethodsCount); + }); + } + + if (opts.includePaymentFlows) { + it('paymentManagerFlow', async () => { + const testEnv = getTestEnv(); + const aliceAccount = opts.getAliceAccount(); + const authContext = getAuthContext(); + const paymentManager = await testEnv.litClient.getPaymentManager({ + account: aliceAccount.account, + }); + + const userAddress = + authContext.wallet?.account?.address || + authContext.account?.address || + aliceAccount.account.address; + + const depositAmount = '0.00001'; + const depositResult = await paymentManager.deposit({ + amountInEth: depositAmount, + }); + expect(depositResult.receipt).toBeDefined(); + + const balanceInfo = await paymentManager.getBalance({ userAddress }); + expect(Number(balanceInfo.raw.totalBalance)).toBeGreaterThan(0); + + const withdrawAmount = '0.000005'; + const withdrawRequestResult = await paymentManager.requestWithdraw({ + amountInEth: withdrawAmount, + }); + expect(withdrawRequestResult.receipt).toBeDefined(); + }); + + it('paymentDelegationFlow', async () => { + const testEnv = getTestEnv(); + const aliceAccount = opts.getAliceAccount(); + const bobAccount = opts.getBobAccount(); + const alicePaymentManager = + await testEnv.litClient.getPaymentManager({ + account: aliceAccount.account, + }); + const bobPaymentManager = await testEnv.litClient.getPaymentManager({ + account: bobAccount.account, + }); + + const aliceAddress = aliceAccount.account.address; + const bobAddress = bobAccount.account.address; + + const initialPayers = await bobPaymentManager.getPayers({ + userAddress: bobAddress, + }); + const initialUsers = await alicePaymentManager.getUsers({ + payerAddress: aliceAddress, + }); + + const delegateTx = await alicePaymentManager.delegatePayments({ + userAddress: bobAddress, + }); + expect(delegateTx.receipt.status).toBe('success'); + + const payersAfterDelegate = await bobPaymentManager.getPayers({ + userAddress: bobAddress, + }); + expect(payersAfterDelegate.length).toBe(initialPayers.length + 1); + + const usersAfterDelegate = await alicePaymentManager.getUsers({ + payerAddress: aliceAddress, + }); + expect(usersAfterDelegate.length).toBe(initialUsers.length + 1); + + const undelegateTx = + await alicePaymentManager.undelegatePayments({ + userAddress: bobAddress, + }); + expect(undelegateTx.receipt.status).toBe('success'); + }); + } + }); +} diff --git a/packages/e2e/src/suites/eoa-native.suite.ts b/packages/e2e/src/suites/eoa-native.suite.ts new file mode 100644 index 000000000..835ab01c5 --- /dev/null +++ b/packages/e2e/src/suites/eoa-native.suite.ts @@ -0,0 +1,53 @@ +import { ViemAccountAuthenticator } from '@lit-protocol/auth'; +import type { CreateTestAccountResult } from '../helper/createTestAccount'; +import type { TestEnv } from '../helper/createTestEnv'; + +export function registerEoaNativeSuite( + getTestEnv: () => TestEnv, + getAliceAccount: () => CreateTestAccountResult +) { + describe('EOA native authentication and PKP minting', () => { + it('authenticates via ViemAccountAuthenticator', async () => { + const alice = getAliceAccount(); + const authDataViemAccount = + await ViemAccountAuthenticator.authenticate(alice.account); + + expect(authDataViemAccount.accessToken).toBeDefined(); + expect(authDataViemAccount.authMethodType).toBeDefined(); + expect(authDataViemAccount.authMethodId).toBeDefined(); + + const authSig = JSON.parse(authDataViemAccount.accessToken); + expect(authSig.sig).toBeDefined(); + expect(authSig.derivedVia).toBeDefined(); + expect(authSig.signedMessage).toBeDefined(); + expect(authSig.address).toBeDefined(); + + const authData = await ViemAccountAuthenticator.authenticate( + alice.account + ); + expect(authData.authMethodType).toBe(authDataViemAccount.authMethodType); + expect(authData.authMethodId).toBe(authDataViemAccount.authMethodId); + }); + + it('mints a PKP using EOA', async () => { + const testEnv = getTestEnv(); + const alice = getAliceAccount(); + + const mintedPkpWithEoa = await testEnv.litClient.mintWithEoa({ + account: alice.account, + }); + + expect(mintedPkpWithEoa.data).toBeDefined(); + expect(mintedPkpWithEoa.txHash).toBeDefined(); + + const pkpData = mintedPkpWithEoa.data; + expect(pkpData.tokenId).toBeDefined(); + expect(pkpData.pubkey).toBeDefined(); + expect(pkpData.ethAddress).toMatch(/^0x[a-fA-F0-9]{40}$/); + expect(pkpData.pubkey).toMatch(/^0x04[a-fA-F0-9]{128}$/); + expect(typeof pkpData.tokenId).toBe('bigint'); + expect(mintedPkpWithEoa.txHash).toMatch(/^0x[a-fA-F0-9]{64}$/); + }); + }); +} + diff --git a/packages/e2e/src/suites/pkp-pre-generated-materials.suite.ts b/packages/e2e/src/suites/pkp-pre-generated-materials.suite.ts new file mode 100644 index 000000000..980fe4884 --- /dev/null +++ b/packages/e2e/src/suites/pkp-pre-generated-materials.suite.ts @@ -0,0 +1,215 @@ +import { createAccBuilder } from '@lit-protocol/access-control-conditions'; +import { generateSessionKeyPair } from '@lit-protocol/auth'; +import type { AuthData } from '@lit-protocol/schemas'; +import type { ResolvedNetwork } from '../helper/network'; +import type { CreateTestAccountResult } from '../helper/createTestAccount'; +import type { TestEnv } from '../helper/createTestEnv'; +import type { AuthContext } from '../types'; +import { createPregenDelegationServerReuseTest } from '../test-helpers/signSessionKey/pregen-delegation'; +import { + PKP_SIGN_TRANSIENT_FRAGMENTS, + SIGN_ECDSA_LIT_ACTION_CODE, + withRetry, +} from './suite-utils'; + +export function registerPkpPreGeneratedMaterialsSuite( + getTestEnv: () => TestEnv, + getAliceAccount: () => CreateTestAccountResult, + getResolvedNetwork: () => ResolvedNetwork +) { + describe('PKP auth with pre-generated materials', () => { + let preGeneratedAuthContext: AuthContext; + + beforeAll(async () => { + const testEnv = getTestEnv(); + const alice = getAliceAccount(); + + if (!alice.pkp) { + throw new Error('Alice is missing a PKP'); + } + if (!alice.authData) { + throw new Error('Alice is missing authData'); + } + + const sessionKeyPair = generateSessionKeyPair(); + const delegationAuthSig = + await testEnv.authManager.generatePkpDelegationAuthSig({ + pkpPublicKey: alice.pkp.pubkey, + authData: alice.authData, + sessionKeyPair, + authConfig: { + resources: [ + ['pkp-signing', '*'], + ['lit-action-execution', '*'], + ['access-control-condition-decryption', '*'], + ], + expiration: new Date( + Date.now() + 1000 * 60 * 15 + ).toISOString(), + }, + litClient: testEnv.litClient, + }); + + preGeneratedAuthContext = + await testEnv.authManager.createPkpAuthContextFromPreGenerated({ + pkpPublicKey: alice.pkp.pubkey, + sessionKeyPair, + delegationAuthSig, + authData: alice.authData, + }); + }); + + describe('endpoints', () => { + it('pkpSign with pre-generated materials', async () => { + const testEnv = getTestEnv(); + const alice = getAliceAccount(); + + const res = await withRetry( + () => + testEnv.litClient.chain.ethereum.pkpSign({ + authContext: preGeneratedAuthContext, + pubKey: alice.pkp!.pubkey, + toSign: 'Hello from pre-generated PKP auth', + }), + { transientMessageFragments: PKP_SIGN_TRANSIENT_FRAGMENTS } + ); + + expect(res.signature).toBeDefined(); + }); + + it('executeJs with pre-generated materials', async () => { + const testEnv = getTestEnv(); + const alice = getAliceAccount(); + + const result = await testEnv.litClient.executeJs({ + code: SIGN_ECDSA_LIT_ACTION_CODE, + authContext: preGeneratedAuthContext, + jsParams: { + message: 'Pre-generated materials executeJs test', + sigName: 'pregen-e2e-sig', + toSign: 'Pre-generated materials executeJs test', + publicKey: alice.pkp!.pubkey, + }, + }); + + expect(result).toBeDefined(); + expect(result.signatures).toBeDefined(); + }); + + it('pkpEncryptDecrypt with pre-generated materials', async () => { + const testEnv = getTestEnv(); + const alice = getAliceAccount(); + + const builder = createAccBuilder(); + const accs = builder + .requireWalletOwnership(alice.pkp!.ethAddress) + .on('ethereum') + .build(); + + const dataToEncrypt = 'Hello from pre-generated encrypt/decrypt test!'; + const encryptedData = await testEnv.litClient.encrypt({ + dataToEncrypt, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + }); + + const decryptedData = await testEnv.litClient.decrypt({ + data: encryptedData, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + authContext: preGeneratedAuthContext, + }); + + expect(decryptedData.convertedData).toBe(dataToEncrypt); + }); + }); + + describe('error handling', () => { + it('should reject when only sessionKeyPair is provided', async () => { + const testEnv = getTestEnv(); + const alice = getAliceAccount(); + + const tempAuthContext: any = + await testEnv.authManager.createPkpAuthContext({ + authData: alice.authData as AuthData, + pkpPublicKey: alice.pkp!.pubkey, + authConfig: { + resources: [['pkp-signing', '*']], + expiration: new Date( + Date.now() + 1000 * 60 * 15 + ).toISOString(), + }, + litClient: testEnv.litClient, + }); + + const sessionKeyPair = tempAuthContext.sessionKeyPair; + + await expect( + testEnv.authManager.createPkpAuthContext({ + authData: alice.authData as AuthData, + pkpPublicKey: alice.pkp!.pubkey, + authConfig: { + resources: [['pkp-signing', '*']], + expiration: new Date( + Date.now() + 1000 * 60 * 15 + ).toISOString(), + }, + litClient: testEnv.litClient, + sessionKeyPair, + }) + ).rejects.toThrow( + 'Both sessionKeyPair and delegationAuthSig must be provided together, or neither should be provided' + ); + }); + + it('should reject when only delegationAuthSig is provided', async () => { + const testEnv = getTestEnv(); + const alice = getAliceAccount(); + + const tempAuthContext: any = + await testEnv.authManager.createPkpAuthContext({ + authData: alice.authData as AuthData, + pkpPublicKey: alice.pkp!.pubkey, + authConfig: { + resources: [['pkp-signing', '*']], + expiration: new Date( + Date.now() + 1000 * 60 * 15 + ).toISOString(), + }, + litClient: testEnv.litClient, + }); + + const delegationAuthSig = + await tempAuthContext.authNeededCallback(); + + await expect( + testEnv.authManager.createPkpAuthContext({ + authData: alice.authData as AuthData, + pkpPublicKey: alice.pkp!.pubkey, + authConfig: { + resources: [['pkp-signing', '*']], + expiration: new Date( + Date.now() + 1000 * 60 * 15 + ).toISOString(), + }, + litClient: testEnv.litClient, + delegationAuthSig, + }) + ).rejects.toThrow( + 'Both sessionKeyPair and delegationAuthSig must be provided together, or neither should be provided' + ); + }); + }); + + describe('server reuse flow', () => { + it('should sign using materials shipped over the wire', () => + createPregenDelegationServerReuseTest({ + authManager: getTestEnv().authManager, + authData: getAliceAccount().authData as AuthData, + pkpPublicKey: getAliceAccount().pkp!.pubkey, + clientLitClient: getTestEnv().litClient, + resolvedNetwork: getResolvedNetwork(), + })()); + }); + }); +} diff --git a/packages/e2e/src/suites/suite-utils.ts b/packages/e2e/src/suites/suite-utils.ts new file mode 100644 index 000000000..d6a8cba01 --- /dev/null +++ b/packages/e2e/src/suites/suite-utils.ts @@ -0,0 +1,66 @@ +export const SIGN_ECDSA_LIT_ACTION_CODE = ` +(async () => { + const { sigName, toSign, publicKey } = jsParams; + const { keccak256, arrayify } = ethers.utils; + + const toSignBytes = new TextEncoder().encode(toSign); + const toSignBytes32 = keccak256(toSignBytes); + const toSignBytes32Array = arrayify(toSignBytes32); + + await Lit.Actions.signEcdsa({ + toSign: toSignBytes32Array, + publicKey, + sigName, + }); +})();`; + +const DEFAULT_TRANSIENT_FRAGMENTS = [ + 'Rate Limit Exceeded', + 'rate limit', + '429', +] as const; + +export const PKP_SIGN_TRANSIENT_FRAGMENTS = [ + ...DEFAULT_TRANSIENT_FRAGMENTS, + 'Pubkey share not found', + 'unable to get signature share', + 'NodeUnknownError', +] as const; + +export async function withRetry( + fn: () => Promise, + options: { + retries?: number; + baseDelayMs?: number; + transientMessageFragments?: readonly string[]; + } = {} +): Promise { + const { + retries = 3, + baseDelayMs = 1500, + transientMessageFragments = DEFAULT_TRANSIENT_FRAGMENTS, + } = options; + + let lastError: unknown; + for (let attempt = 1; attempt <= retries; attempt++) { + try { + return await fn(); + } catch (err: any) { + lastError = err; + const message = String(err?.message ?? err); + const isTransient = transientMessageFragments.some((fragment) => + message.includes(fragment) + ); + + if (isTransient && attempt < retries) { + const delay = baseDelayMs * attempt; + await new Promise((resolve) => setTimeout(resolve, delay)); + continue; + } + + throw err; + } + } + + throw lastError; +} diff --git a/packages/e2e/src/suites/viem.suite.ts b/packages/e2e/src/suites/viem.suite.ts new file mode 100644 index 000000000..2697af64d --- /dev/null +++ b/packages/e2e/src/suites/viem.suite.ts @@ -0,0 +1,110 @@ +import type { AuthContext } from '../types'; +import type { TestEnv } from '../helper/createTestEnv'; +import { withRetry } from './suite-utils'; + +export function registerViemSuite( + getTestEnv: () => TestEnv, + getAuthContext: () => AuthContext, + getPkpPublicKey: () => string +) { + describe('integrations', () => { + describe('pkp viem account', () => { + it('sign message', async () => { + const testEnv = getTestEnv(); + const pkpViemAccount = await testEnv.litClient.getPkpViemAccount({ + pkpPublicKey: getPkpPublicKey(), + authContext: getAuthContext(), + chainConfig: testEnv.litClient.getChainConfig().viemConfig, + }); + + const signature = await withRetry(() => + pkpViemAccount.signMessage({ + message: 'Hello Viem + Lit', + }) + ); + + expect(signature).toMatch(/^0x[a-fA-F0-9]{130}$/); + }); + + it('sign transaction', async () => { + const testEnv = getTestEnv(); + const pkpViemAccount = await testEnv.litClient.getPkpViemAccount({ + pkpPublicKey: getPkpPublicKey(), + authContext: getAuthContext(), + chainConfig: testEnv.litClient.getChainConfig().viemConfig, + }); + + const txRequest = { + chainId: testEnv.litClient.getChainConfig().viemConfig.id, + to: pkpViemAccount.address, + value: BigInt('1000000000000000'), + }; + + const signedTx = await withRetry(() => + pkpViemAccount.signTransaction(txRequest) + ); + expect(signedTx).toMatch(/^0x[a-fA-F0-9]+$/); + }); + + it('sign typed data', async () => { + const testEnv = getTestEnv(); + const pkpViemAccount = await testEnv.litClient.getPkpViemAccount({ + pkpPublicKey: getPkpPublicKey(), + authContext: getAuthContext(), + chainConfig: testEnv.litClient.getChainConfig().viemConfig, + }); + + const { getAddress } = await import('viem'); + + const typedData = { + domain: { + name: 'E2E Test Service', + version: '1', + chainId: BigInt(1), + verifyingContract: getAddress( + '0x1e0Ae8205e9726E6F296ab8869930607a853204C' + ), + }, + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail' as const, + message: { + from: { + name: 'Alice', + wallet: getAddress( + '0x2111111111111111111111111111111111111111' + ), + }, + to: { + name: 'Bob', + wallet: getAddress( + '0x3111111111111111111111111111111111111111' + ), + }, + contents: 'Hello from revamp e2e typed data test!', + }, + } as const; + + const signature = await withRetry(() => + pkpViemAccount.signTypedData(typedData) + ); + expect(signature).toMatch(/^0x[a-fA-F0-9]{130}$/); + }); + }); + }); +} diff --git a/packages/e2e/src/suites/wrapped-keys.suite.ts b/packages/e2e/src/suites/wrapped-keys.suite.ts new file mode 100644 index 000000000..9ff4cfffe --- /dev/null +++ b/packages/e2e/src/suites/wrapped-keys.suite.ts @@ -0,0 +1,8 @@ +import { registerWrappedKeysTests } from '../test-helpers/executeJs/wrappedKeys'; + +export function registerWrappedKeysSuite() { + describe('wrapped keys', () => { + registerWrappedKeysTests(); + }); +} + From f9d8588a2d7a6e65d010d4ea67946e4529b357bc Mon Sep 17 00:00:00 2001 From: anson Date: Fri, 12 Dec 2025 03:15:08 +0000 Subject: [PATCH 2/7] refactor(e2e): restructure test suite and update .gitignore --- .gitignore | 3 +- packages/e2e/src/e2e-revamp.spec.ts | 138 -------------------------- packages/e2e/src/e2e.spec.ts | 146 +++++++++++++++++++++++++++- 3 files changed, 145 insertions(+), 142 deletions(-) delete mode 100644 packages/e2e/src/e2e-revamp.spec.ts diff --git a/.gitignore b/.gitignore index da87bd13b..69e472aac 100644 --- a/.gitignore +++ b/.gitignore @@ -98,4 +98,5 @@ alice-auth-manager-data alice-auth-manager-data !/packages/contracts/dist/ !/packages/contracts/dist/dev -.secret \ No newline at end of file +.secret +.jest-cache \ No newline at end of file diff --git a/packages/e2e/src/e2e-revamp.spec.ts b/packages/e2e/src/e2e-revamp.spec.ts deleted file mode 100644 index e233de196..000000000 --- a/packages/e2e/src/e2e-revamp.spec.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { createEnvVars } from './helper/createEnvVars'; -import { createTestEnv } from './helper/createTestEnv'; -import { getNetworkConfig } from './helper/network'; -import type { ResolvedNetwork } from './helper/network'; -import type { AuthContext } from './types'; - -/** - * How to run: - * - Full revamped suite (canonical): - * `NETWORK=naga-dev pnpm run test:e2e` - * - * - Just this file: - * `NETWORK=naga-dev pnpm exec dotenvx run --env-file=.env -- jest --runInBand --config ./jest.e2e.config.ts --cacheDirectory .jest-cache --runTestsByPath packages/e2e/src/e2e-revamp.spec.ts` - * - * - Single test / suite (use Jest name filtering): - * `NETWORK=naga-dev pnpm exec dotenvx run --env-file=.env -- jest --runInBand --config ./jest.e2e.config.ts --cacheDirectory .jest-cache --runTestsByPath packages/e2e/src/e2e-revamp.spec.ts --testNamePattern "EOA auth.*pkpSign"` - * - * - Alternative local workflow: - * add `.only` to a `describe` or `it` block temporarily. - */ -import { - createTestAccount, - CreateTestAccountResult, -} from './helper/createTestAccount'; -import { registerEndpointSuite } from './suites/endpoints.suite'; -import { registerViemSuite } from './suites/viem.suite'; -import { registerPkpPreGeneratedMaterialsSuite } from './suites/pkp-pre-generated-materials.suite'; -import { registerEoaNativeSuite } from './suites/eoa-native.suite'; -import { registerWrappedKeysSuite } from './suites/wrapped-keys.suite'; -import { registerCustomAuthSuite } from './suites/custom-auth.suite'; -import { registerPaymentDelegationTicketSuite } from './tickets/delegation.suite'; - -const SELECTED_NETWORK = process.env['NETWORK']; -const IS_PAID_NETWORK = SELECTED_NETWORK !== 'naga-dev'; -const describeIfPaid = IS_PAID_NETWORK ? describe : describe.skip; - -describe('revamped e2e suite', () => { - let envVars: ReturnType; - let testEnv: Awaited>; - let resolvedNetwork: ResolvedNetwork; - let alice: CreateTestAccountResult; - let bob: CreateTestAccountResult; - - beforeAll(async () => { - envVars = createEnvVars(); - testEnv = await createTestEnv(envVars); - const { name, importName, type } = getNetworkConfig(envVars.network); - resolvedNetwork = { name, importName, type, networkModule: testEnv.networkModule }; - - alice = await createTestAccount(testEnv, { - label: 'Alice', - fundAccount: true, - hasEoaAuthContext: true, - fundLedger: true, - hasPKP: true, - fundPKP: true, - hasPKPAuthContext: true, - fundPKPLedger: true, - }); - - bob = await createTestAccount(testEnv, { - label: 'Bob', - fundAccount: true, - hasEoaAuthContext: true, - fundLedger: true, - hasPKP: false, - fundPKP: false, - fundPKPLedger: false, - hasPKPAuthContext: false, - }); - }); - - const authModes = [ - { - label: 'EOA', - getAuthContext: () => alice.eoaAuthContext!, - includePaymentFlows: true, - getAccsAddress: (_authContext: AuthContext) => alice.account.address, - }, - { - label: 'PKP', - getAuthContext: () => alice.pkpAuthContext!, - includePaymentFlows: false, - }, - ] satisfies Array<{ - label: string; - getAuthContext: () => AuthContext; - includePaymentFlows: boolean; - getAccsAddress?: (authContext: AuthContext) => string; - }>; - - authModes.forEach((mode) => { - describe(`${mode.label} auth`, () => { - const getTestEnv = () => testEnv; - const getAliceAccount = () => alice; - const getBobAccount = () => bob; - const getPkpPublicKey = () => { - if (!alice.pkp) { - throw new Error('Alice is missing a PKP'); - } - return alice.pkp.pubkey; - }; - const getPkpEthAddress = () => { - if (!alice.pkp) { - throw new Error('Alice is missing a PKP'); - } - return alice.pkp.ethAddress as `0x${string}`; - }; - - registerEndpointSuite(getTestEnv, mode.getAuthContext, { - getPkpPublicKey, - getPkpEthAddress, - getAliceAccount, - getBobAccount, - includePaymentFlows: mode.includePaymentFlows, - getAccsAddress: mode.getAccsAddress, - }); - - registerViemSuite(getTestEnv, mode.getAuthContext, getPkpPublicKey); - }); - }); - - registerPkpPreGeneratedMaterialsSuite( - () => testEnv, - () => alice, - () => resolvedNetwork - ); - - registerEoaNativeSuite(() => testEnv, () => alice); - - registerWrappedKeysSuite(); - - registerCustomAuthSuite(() => testEnv, () => bob); -}); - -describeIfPaid('Paid networks tests', () => { - registerPaymentDelegationTicketSuite(); -}); diff --git a/packages/e2e/src/e2e.spec.ts b/packages/e2e/src/e2e.spec.ts index 8ed7c03cc..b6146c790 100644 --- a/packages/e2e/src/e2e.spec.ts +++ b/packages/e2e/src/e2e.spec.ts @@ -1,3 +1,143 @@ -// Canonical E2E suite orchestrator. -// The implementation now lives in `e2e-revamp.spec.ts` with modular suites. -import './e2e-revamp.spec'; +import { createEnvVars } from './helper/createEnvVars'; +import { createTestEnv } from './helper/createTestEnv'; +import { getNetworkConfig } from './helper/network'; +import type { ResolvedNetwork } from './helper/network'; +import type { AuthContext } from './types'; + +/** + * How to run: + * - Full revamped suite (canonical): + * `NETWORK=naga-dev pnpm run test:e2e` + * + * - Just this file: + * `NETWORK=naga-dev pnpm exec dotenvx run --env-file=.env -- jest --runInBand --config ./jest.e2e.config.ts --cacheDirectory .jest-cache --runTestsByPath packages/e2e/src/e2e.spec.ts` + * + * - Single test / suite (use Jest name filtering): + * `NETWORK=naga-dev pnpm exec dotenvx run --env-file=.env -- jest --runInBand --config ./jest.e2e.config.ts --cacheDirectory .jest-cache --runTestsByPath packages/e2e/src/e2e.spec.ts --testNamePattern "EOA auth.*pkpSign"` + * + * - Alternative local workflow: + * add `.only` to a `describe` or `it` block temporarily. + */ +import { + createTestAccount, + CreateTestAccountResult, +} from './helper/createTestAccount'; +import { registerEndpointSuite } from './suites/endpoints.suite'; +import { registerViemSuite } from './suites/viem.suite'; +import { registerPkpPreGeneratedMaterialsSuite } from './suites/pkp-pre-generated-materials.suite'; +import { registerEoaNativeSuite } from './suites/eoa-native.suite'; +import { registerWrappedKeysSuite } from './suites/wrapped-keys.suite'; +import { registerCustomAuthSuite } from './suites/custom-auth.suite'; +import { registerPaymentDelegationTicketSuite } from './tickets/delegation.suite'; + +const SELECTED_NETWORK = process.env['NETWORK']; +const IS_PAID_NETWORK = SELECTED_NETWORK !== 'naga-dev'; +const describeIfPaid = IS_PAID_NETWORK ? describe : describe.skip; + +describe('revamped e2e suite', () => { + let envVars: ReturnType; + let testEnv: Awaited>; + let resolvedNetwork: ResolvedNetwork; + let alice: CreateTestAccountResult; + let bob: CreateTestAccountResult; + + beforeAll(async () => { + envVars = createEnvVars(); + testEnv = await createTestEnv(envVars); + const { name, importName, type } = getNetworkConfig(envVars.network); + resolvedNetwork = { + name, + importName, + type, + networkModule: testEnv.networkModule, + }; + + alice = await createTestAccount(testEnv, { + label: 'Alice', + fundAccount: true, + hasEoaAuthContext: true, + fundLedger: true, + hasPKP: true, + fundPKP: true, + hasPKPAuthContext: true, + fundPKPLedger: true, + }); + + bob = await createTestAccount(testEnv, { + label: 'Bob', + fundAccount: true, + hasEoaAuthContext: true, + fundLedger: true, + hasPKP: false, + fundPKP: false, + fundPKPLedger: false, + hasPKPAuthContext: false, + }); + }); + + const authModes = [ + { + label: 'EOA', + getAuthContext: () => alice.eoaAuthContext!, + includePaymentFlows: true, + getAccsAddress: (_authContext: AuthContext) => alice.account.address, + }, + { + label: 'PKP', + getAuthContext: () => alice.pkpAuthContext!, + includePaymentFlows: false, + }, + ] satisfies Array<{ + label: string; + getAuthContext: () => AuthContext; + includePaymentFlows: boolean; + getAccsAddress?: (authContext: AuthContext) => string; + }>; + + authModes.forEach((mode) => { + describe(`${mode.label} auth`, () => { + const getTestEnv = () => testEnv; + const getAliceAccount = () => alice; + const getBobAccount = () => bob; + const getPkpPublicKey = () => { + if (!alice.pkp) { + throw new Error('Alice is missing a PKP'); + } + return alice.pkp.pubkey; + }; + const getPkpEthAddress = () => { + if (!alice.pkp) { + throw new Error('Alice is missing a PKP'); + } + return alice.pkp.ethAddress as `0x${string}`; + }; + + registerEndpointSuite(getTestEnv, mode.getAuthContext, { + getPkpPublicKey, + getPkpEthAddress, + getAliceAccount, + getBobAccount, + includePaymentFlows: mode.includePaymentFlows, + getAccsAddress: mode.getAccsAddress, + }); + + registerViemSuite(getTestEnv, mode.getAuthContext, getPkpPublicKey); + }); + }); + + registerPkpPreGeneratedMaterialsSuite( + () => testEnv, + () => alice, + () => resolvedNetwork + ); + + registerEoaNativeSuite(() => testEnv, () => alice); + + registerWrappedKeysSuite(); + + registerCustomAuthSuite(() => testEnv, () => bob); +}); + +describeIfPaid('Paid networks tests', () => { + registerPaymentDelegationTicketSuite(); +}); From f8368660787a965ae49c98858cd8c455359e92aa Mon Sep 17 00:00:00 2001 From: anson Date: Fri, 12 Dec 2025 11:54:03 +0000 Subject: [PATCH 3/7] refactor(e2e): enhance viewPKPsByAuthData with retry logic and error handling --- packages/e2e/src/suites/endpoints.suite.ts | 45 +++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/e2e/src/suites/endpoints.suite.ts b/packages/e2e/src/suites/endpoints.suite.ts index bc7db100b..a2d4565f3 100644 --- a/packages/e2e/src/suites/endpoints.suite.ts +++ b/packages/e2e/src/suites/endpoints.suite.ts @@ -78,23 +78,42 @@ export function registerEndpointSuite( it('viewPKPsByAuthData', async () => { const testEnv = getTestEnv(); const aliceAccount = opts.getAliceAccount(); - const authData = - opts.authDataOverride ?? - aliceAccount.authData ?? - (await ViemAccountAuthenticator.authenticate(aliceAccount.account)); - - const pkps = await testEnv.litClient.viewPKPsByAuthData({ - authData: { - authMethodType: authData.authMethodType, - authMethodId: authData.authMethodId, - accessToken: authData.accessToken || 'mock-token', + const pkps = await withRetry( + async () => { + const authData = + opts.authDataOverride ?? + (await ViemAccountAuthenticator.authenticate( + aliceAccount.account + )); + + const res = await testEnv.litClient.viewPKPsByAuthData({ + authData: { + authMethodType: authData.authMethodType, + authMethodId: authData.authMethodId, + accessToken: authData.accessToken || 'mock-token', + }, + pagination: { limit: 10, offset: 0 }, + }); + + if (!res.pkps?.length) { + throw new Error('No PKPs found yet'); + } + + return res; }, - pagination: { limit: 10, offset: 0 }, - }); + { + transientMessageFragments: [ + 'Verification failed', + 'Failed to verify signature', + 'authentication failed', + 'No PKPs found yet', + ...PKP_SIGN_TRANSIENT_FRAGMENTS, + ], + } + ); expect(pkps).toBeDefined(); expect(Array.isArray(pkps.pkps)).toBe(true); - expect(pkps.pkps.length).toBeGreaterThan(0); const firstPkp = pkps.pkps[0]; expect(firstPkp.tokenId).toBeDefined(); From ba4ae43986f4ab4e6641a8414ec1b7d15f4fa2ed Mon Sep 17 00:00:00 2001 From: anson Date: Mon, 15 Dec 2025 22:48:06 +0000 Subject: [PATCH 4/7] fmt --- packages/e2e/src/e2e.spec.ts | 10 ++- packages/e2e/src/helper/constants.ts | 1 - packages/e2e/src/suites/custom-auth.suite.ts | 77 +++++++++---------- packages/e2e/src/suites/endpoints.suite.ts | 14 ++-- packages/e2e/src/suites/eoa-native.suite.ts | 6 +- .../pkp-pre-generated-materials.suite.ts | 23 ++---- packages/e2e/src/suites/viem.suite.ts | 8 +- packages/e2e/src/suites/wrapped-keys.suite.ts | 1 - 8 files changed, 62 insertions(+), 78 deletions(-) diff --git a/packages/e2e/src/e2e.spec.ts b/packages/e2e/src/e2e.spec.ts index b6146c790..7f639dc47 100644 --- a/packages/e2e/src/e2e.spec.ts +++ b/packages/e2e/src/e2e.spec.ts @@ -131,11 +131,17 @@ describe('revamped e2e suite', () => { () => resolvedNetwork ); - registerEoaNativeSuite(() => testEnv, () => alice); + registerEoaNativeSuite( + () => testEnv, + () => alice + ); registerWrappedKeysSuite(); - registerCustomAuthSuite(() => testEnv, () => bob); + registerCustomAuthSuite( + () => testEnv, + () => bob + ); }); describeIfPaid('Paid networks tests', () => { diff --git a/packages/e2e/src/helper/constants.ts b/packages/e2e/src/helper/constants.ts index b8bc4b416..6490f6727 100644 --- a/packages/e2e/src/helper/constants.ts +++ b/packages/e2e/src/helper/constants.ts @@ -1,3 +1,2 @@ export const EVE_VALIDATION_IPFS_CID = 'QmcxWmo3jefFsPUnskJXYBwsJYtiFuMAH1nDQEs99AwzDe'; - diff --git a/packages/e2e/src/suites/custom-auth.suite.ts b/packages/e2e/src/suites/custom-auth.suite.ts index 326239224..918f1ddb5 100644 --- a/packages/e2e/src/suites/custom-auth.suite.ts +++ b/packages/e2e/src/suites/custom-auth.suite.ts @@ -1,7 +1,10 @@ import { utils as litUtils } from '@lit-protocol/lit-client'; import type { CustomAuthData } from '@lit-protocol/schemas'; import type { AuthContext } from '../types'; -import { createTestAccount, CreateTestAccountResult } from '../helper/createTestAccount'; +import { + createTestAccount, + CreateTestAccountResult, +} from '../helper/createTestAccount'; import type { TestEnv } from '../helper/createTestEnv'; import { fundAccount } from '../helper/fundAccount'; import { EVE_VALIDATION_IPFS_CID } from '../helper/constants'; @@ -33,8 +36,9 @@ export function registerCustomAuthSuite( // Must match the validation Lit Action's expected dapp name. const uniqueDappName = 'e2e-test-dapp'; - const authMethodConfig = - litUtils.generateUniqueAuthMethodType({ uniqueDappName }); + const authMethodConfig = litUtils.generateUniqueAuthMethodType({ + uniqueDappName, + }); eveCustomAuthData = litUtils.generateAuthData({ uniqueDappName, uniqueAuthMethodType: authMethodConfig.bigint, @@ -70,49 +74,42 @@ export function registerCustomAuthSuite( amountInEth: testEnv.config.ledgerDepositAmount, }); - eveCustomAuthContext = - await testEnv.authManager.createCustomAuthContext({ - pkpPublicKey: evePkp.pubkey, - authConfig: { - resources: [ - ['pkp-signing', '*'], - ['lit-action-execution', '*'], - ['access-control-condition-decryption', '*'], - ], - expiration: new Date( - Date.now() + 1000 * 60 * 15 - ).toISOString(), - }, - litClient: testEnv.litClient, - customAuthParams: { - litActionIpfsId: EVE_VALIDATION_IPFS_CID, - jsParams: { - pkpPublicKey: evePkp.pubkey, - username: 'eve', - password: 'lit', - authMethodId: eveCustomAuthData.authMethodId, - }, + eveCustomAuthContext = await testEnv.authManager.createCustomAuthContext({ + pkpPublicKey: evePkp.pubkey, + authConfig: { + resources: [ + ['pkp-signing', '*'], + ['lit-action-execution', '*'], + ['access-control-condition-decryption', '*'], + ], + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + }, + litClient: testEnv.litClient, + customAuthParams: { + litActionIpfsId: EVE_VALIDATION_IPFS_CID, + jsParams: { + pkpPublicKey: evePkp.pubkey, + username: 'eve', + password: 'lit', + authMethodId: eveCustomAuthData.authMethodId, }, - }); + }, + }); }); const getEveAccount = () => eve; const getPkpPublicKey = () => evePkp.pubkey; const getPkpEthAddress = () => evePkp.ethAddress; - registerEndpointSuite( - getTestEnv, - () => eveCustomAuthContext, - { - getPkpPublicKey, - getPkpEthAddress, - getAliceAccount: getEveAccount, - getBobAccount, - includePaymentFlows: false, - includeEncryptDecryptFlow: false, - includePermissionsFlow: false, - includeViewPkpsByAuthData: false, - } - ); + registerEndpointSuite(getTestEnv, () => eveCustomAuthContext, { + getPkpPublicKey, + getPkpEthAddress, + getAliceAccount: getEveAccount, + getBobAccount, + includePaymentFlows: false, + includeEncryptDecryptFlow: false, + includePermissionsFlow: false, + includeViewPkpsByAuthData: false, + }); }); } diff --git a/packages/e2e/src/suites/endpoints.suite.ts b/packages/e2e/src/suites/endpoints.suite.ts index a2d4565f3..df7dff8b9 100644 --- a/packages/e2e/src/suites/endpoints.suite.ts +++ b/packages/e2e/src/suites/endpoints.suite.ts @@ -398,10 +398,9 @@ export function registerEndpointSuite( const testEnv = getTestEnv(); const aliceAccount = opts.getAliceAccount(); const bobAccount = opts.getBobAccount(); - const alicePaymentManager = - await testEnv.litClient.getPaymentManager({ - account: aliceAccount.account, - }); + const alicePaymentManager = await testEnv.litClient.getPaymentManager({ + account: aliceAccount.account, + }); const bobPaymentManager = await testEnv.litClient.getPaymentManager({ account: bobAccount.account, }); @@ -431,10 +430,9 @@ export function registerEndpointSuite( }); expect(usersAfterDelegate.length).toBe(initialUsers.length + 1); - const undelegateTx = - await alicePaymentManager.undelegatePayments({ - userAddress: bobAddress, - }); + const undelegateTx = await alicePaymentManager.undelegatePayments({ + userAddress: bobAddress, + }); expect(undelegateTx.receipt.status).toBe('success'); }); } diff --git a/packages/e2e/src/suites/eoa-native.suite.ts b/packages/e2e/src/suites/eoa-native.suite.ts index 835ab01c5..a1e7b33c8 100644 --- a/packages/e2e/src/suites/eoa-native.suite.ts +++ b/packages/e2e/src/suites/eoa-native.suite.ts @@ -9,8 +9,9 @@ export function registerEoaNativeSuite( describe('EOA native authentication and PKP minting', () => { it('authenticates via ViemAccountAuthenticator', async () => { const alice = getAliceAccount(); - const authDataViemAccount = - await ViemAccountAuthenticator.authenticate(alice.account); + const authDataViemAccount = await ViemAccountAuthenticator.authenticate( + alice.account + ); expect(authDataViemAccount.accessToken).toBeDefined(); expect(authDataViemAccount.authMethodType).toBeDefined(); @@ -50,4 +51,3 @@ export function registerEoaNativeSuite( }); }); } - diff --git a/packages/e2e/src/suites/pkp-pre-generated-materials.suite.ts b/packages/e2e/src/suites/pkp-pre-generated-materials.suite.ts index 980fe4884..d9da70ba6 100644 --- a/packages/e2e/src/suites/pkp-pre-generated-materials.suite.ts +++ b/packages/e2e/src/suites/pkp-pre-generated-materials.suite.ts @@ -43,9 +43,7 @@ export function registerPkpPreGeneratedMaterialsSuite( ['lit-action-execution', '*'], ['access-control-condition-decryption', '*'], ], - expiration: new Date( - Date.now() + 1000 * 60 * 15 - ).toISOString(), + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), }, litClient: testEnv.litClient, }); @@ -135,9 +133,7 @@ export function registerPkpPreGeneratedMaterialsSuite( pkpPublicKey: alice.pkp!.pubkey, authConfig: { resources: [['pkp-signing', '*']], - expiration: new Date( - Date.now() + 1000 * 60 * 15 - ).toISOString(), + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), }, litClient: testEnv.litClient, }); @@ -150,9 +146,7 @@ export function registerPkpPreGeneratedMaterialsSuite( pkpPublicKey: alice.pkp!.pubkey, authConfig: { resources: [['pkp-signing', '*']], - expiration: new Date( - Date.now() + 1000 * 60 * 15 - ).toISOString(), + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), }, litClient: testEnv.litClient, sessionKeyPair, @@ -172,15 +166,12 @@ export function registerPkpPreGeneratedMaterialsSuite( pkpPublicKey: alice.pkp!.pubkey, authConfig: { resources: [['pkp-signing', '*']], - expiration: new Date( - Date.now() + 1000 * 60 * 15 - ).toISOString(), + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), }, litClient: testEnv.litClient, }); - const delegationAuthSig = - await tempAuthContext.authNeededCallback(); + const delegationAuthSig = await tempAuthContext.authNeededCallback(); await expect( testEnv.authManager.createPkpAuthContext({ @@ -188,9 +179,7 @@ export function registerPkpPreGeneratedMaterialsSuite( pkpPublicKey: alice.pkp!.pubkey, authConfig: { resources: [['pkp-signing', '*']], - expiration: new Date( - Date.now() + 1000 * 60 * 15 - ).toISOString(), + expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), }, litClient: testEnv.litClient, delegationAuthSig, diff --git a/packages/e2e/src/suites/viem.suite.ts b/packages/e2e/src/suites/viem.suite.ts index 2697af64d..2dc94bd23 100644 --- a/packages/e2e/src/suites/viem.suite.ts +++ b/packages/e2e/src/suites/viem.suite.ts @@ -86,15 +86,11 @@ export function registerViemSuite( message: { from: { name: 'Alice', - wallet: getAddress( - '0x2111111111111111111111111111111111111111' - ), + wallet: getAddress('0x2111111111111111111111111111111111111111'), }, to: { name: 'Bob', - wallet: getAddress( - '0x3111111111111111111111111111111111111111' - ), + wallet: getAddress('0x3111111111111111111111111111111111111111'), }, contents: 'Hello from revamp e2e typed data test!', }, diff --git a/packages/e2e/src/suites/wrapped-keys.suite.ts b/packages/e2e/src/suites/wrapped-keys.suite.ts index 9ff4cfffe..c7edbcd39 100644 --- a/packages/e2e/src/suites/wrapped-keys.suite.ts +++ b/packages/e2e/src/suites/wrapped-keys.suite.ts @@ -5,4 +5,3 @@ export function registerWrappedKeysSuite() { registerWrappedKeysTests(); }); } - From a69b41503f87ead2e259ca79d588e11f27ce2803 Mon Sep 17 00:00:00 2001 From: anson Date: Tue, 16 Dec 2025 20:57:26 +0000 Subject: [PATCH 5/7] fix(e2e): resolve TypeScript errors for authContext account access --- .../src/helper/tests/payment-manager-flow.ts | 21 ++++++++++++++----- packages/e2e/src/suites/endpoints.suite.ts | 20 ++++++++++++++---- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/e2e/src/helper/tests/payment-manager-flow.ts b/packages/e2e/src/helper/tests/payment-manager-flow.ts index 550fad65a..8ace68442 100644 --- a/packages/e2e/src/helper/tests/payment-manager-flow.ts +++ b/packages/e2e/src/helper/tests/payment-manager-flow.ts @@ -12,11 +12,22 @@ export const createPaymentManagerFlowTest = ( account: ctx.aliceViemAccount, }); - // Get the user's address from authContext (assuming it has a wallet or account) - const userAddress = - authContext.wallet?.account?.address || - authContext.account?.address || - ctx.aliceViemAccount.address; + // Extract address from authContext if it's an EOA auth context + // For EOA: account can be Account (has address) or WalletClient (has account.address) + // For PKP: account doesn't exist, fall back to aliceViemAccount + let userAddress: string; + if ('account' in authContext && authContext.account) { + const account = authContext.account as any; + if ('address' in account && account.address) { + userAddress = account.address; + } else if (account.account?.address) { + userAddress = account.account.address; + } else { + userAddress = ctx.aliceViemAccount.address; + } + } else { + userAddress = ctx.aliceViemAccount.address; + } console.log('๐Ÿ’ฐ Testing deposit functionality...'); // Test deposit diff --git a/packages/e2e/src/suites/endpoints.suite.ts b/packages/e2e/src/suites/endpoints.suite.ts index df7dff8b9..29dac46cc 100644 --- a/packages/e2e/src/suites/endpoints.suite.ts +++ b/packages/e2e/src/suites/endpoints.suite.ts @@ -373,10 +373,22 @@ export function registerEndpointSuite( account: aliceAccount.account, }); - const userAddress = - authContext.wallet?.account?.address || - authContext.account?.address || - aliceAccount.account.address; + // Extract address from authContext if it's an EOA auth context + // For EOA: account can be Account (has address) or WalletClient (has account.address) + // For PKP: account doesn't exist, fall back to aliceAccount + let userAddress: string; + if ('account' in authContext && authContext.account) { + const account = authContext.account as any; + if ('address' in account && account.address) { + userAddress = account.address; + } else if (account.account?.address) { + userAddress = account.account.address; + } else { + userAddress = aliceAccount.account.address; + } + } else { + userAddress = aliceAccount.account.address; + } const depositAmount = '0.00001'; const depositResult = await paymentManager.deposit({ From 9660cf5ed457c6dfdb673b23fbecc86fb51ccc62 Mon Sep 17 00:00:00 2001 From: anson Date: Wed, 17 Dec 2025 18:07:57 +0000 Subject: [PATCH 6/7] fix(e2e): update workflow configuration --- .github/workflows/e2e-naga.yml | 6 +++-- packages/e2e/src/helper/createEnvVars.ts | 28 +++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e-naga.yml b/.github/workflows/e2e-naga.yml index 231fa7e70..2902c242f 100644 --- a/.github/workflows/e2e-naga.yml +++ b/.github/workflows/e2e-naga.yml @@ -61,8 +61,10 @@ jobs: privateKey: LIVE_MASTER_ACCOUNT_NAGA_TEST env: LOG_LEVEL: debug2 + NETWORK: ${{ matrix.network }} LIVE_MASTER_ACCOUNT: ${{ secrets[matrix.privateKey] }} LOCAL_MASTER_ACCOUNT: ${{ secrets[matrix.privateKey] }} + LIT_YELLOWSTONE_PRIVATE_RPC_URL: ${{ vars.LIT_YELLOWSTONE_PRIVATE_RPC_URL }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -101,8 +103,8 @@ jobs: echo "LOCAL_MASTER_ACCOUNT is not set for network ${{ matrix.network }}" >&2 exit 1 fi - - name: Run health check (${{ matrix.network }}) - run: NETWORK=${{ matrix.network }} pnpm run test:e2e:ci -- packages/e2e/src/e2e.spec.ts --testNamePattern "^all " + - name: Run e2e tests (${{ matrix.network }}) + run: pnpm run test:e2e:ci -- packages/e2e/src/e2e.spec.ts timeout-minutes: 10 release: diff --git a/packages/e2e/src/helper/createEnvVars.ts b/packages/e2e/src/helper/createEnvVars.ts index cd64df726..f886710dd 100644 --- a/packages/e2e/src/helper/createEnvVars.ts +++ b/packages/e2e/src/helper/createEnvVars.ts @@ -25,6 +25,8 @@ export type EnvVars = { localContextPath?: string; }; +const PrivateKeySchema = /^(0x)?[0-9a-fA-F]{64}$/; + // -- configure const testEnv: Record< EnvName, @@ -36,14 +38,15 @@ const testEnv: Record< export function createEnvVars(): EnvVars { // 1. Get network string - const networkEnv = process.env['NETWORK']; + const networkEnvRaw = process.env['NETWORK']; + const networkEnv = networkEnvRaw?.trim(); if ( !networkEnv || !SUPPORTED_NETWORKS.includes(networkEnv as SupportedNetwork) ) { throw new Error( - `โŒ NETWORK env var is not set or not supported. Found. ${networkEnv}` + `โŒ NETWORK env var is not set or not supported. Found: ${networkEnvRaw ?? 'undefined'}` ); } @@ -53,20 +56,35 @@ export function createEnvVars(): EnvVars { // 2. Get private key let privateKey: `0x${string}`; + let privateKeyEnvKey: (typeof testEnv)[EnvName]['key']; if (network.includes('local')) { Object.assign(testEnv.local, { type: 'local' }); - privateKey = process.env[testEnv.local.key]!! as `0x${string}`; + privateKeyEnvKey = testEnv.local.key; + privateKey = (process.env[privateKeyEnvKey] ?? '') + .trim() as `0x${string}`; } else { Object.assign(testEnv.live, { type: 'live' }); - privateKey = process.env[testEnv.live.key]!! as `0x${string}`; + privateKeyEnvKey = testEnv.live.key; + privateKey = (process.env[privateKeyEnvKey] ?? '') + .trim() as `0x${string}`; } if (!privateKey) { throw new Error( - `โŒ You are on "${selectedNetwork}" environment, network ${network}. We are expecting ` + `โŒ Missing required env var ${privateKeyEnvKey} for "${selectedNetwork}" environment (${network}).` ); } + if (!PrivateKeySchema.test(privateKey)) { + throw new Error( + `โŒ Invalid private key format in ${privateKeyEnvKey}. Expected 32-byte hex string (64 chars) with optional 0x prefix.` + ); + } + + if (!privateKey.startsWith('0x')) { + privateKey = (`0x${privateKey}` as unknown) as `0x${string}`; + } + // 3. Get RPC URL let rpcUrl: string | undefined; let localContextPath: string | undefined; From 1f1667b2775d52654e02cde27a8df7caa54185d3 Mon Sep 17 00:00:00 2001 From: anson Date: Wed, 17 Dec 2025 19:09:55 +0000 Subject: [PATCH 7/7] fmt --- packages/e2e/src/helper/createEnvVars.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/e2e/src/helper/createEnvVars.ts b/packages/e2e/src/helper/createEnvVars.ts index f886710dd..a208a68c8 100644 --- a/packages/e2e/src/helper/createEnvVars.ts +++ b/packages/e2e/src/helper/createEnvVars.ts @@ -46,7 +46,9 @@ export function createEnvVars(): EnvVars { !SUPPORTED_NETWORKS.includes(networkEnv as SupportedNetwork) ) { throw new Error( - `โŒ NETWORK env var is not set or not supported. Found: ${networkEnvRaw ?? 'undefined'}` + `โŒ NETWORK env var is not set or not supported. Found: ${ + networkEnvRaw ?? 'undefined' + }` ); } @@ -60,13 +62,11 @@ export function createEnvVars(): EnvVars { if (network.includes('local')) { Object.assign(testEnv.local, { type: 'local' }); privateKeyEnvKey = testEnv.local.key; - privateKey = (process.env[privateKeyEnvKey] ?? '') - .trim() as `0x${string}`; + privateKey = (process.env[privateKeyEnvKey] ?? '').trim() as `0x${string}`; } else { Object.assign(testEnv.live, { type: 'live' }); privateKeyEnvKey = testEnv.live.key; - privateKey = (process.env[privateKeyEnvKey] ?? '') - .trim() as `0x${string}`; + privateKey = (process.env[privateKeyEnvKey] ?? '').trim() as `0x${string}`; } if (!privateKey) { @@ -82,7 +82,7 @@ export function createEnvVars(): EnvVars { } if (!privateKey.startsWith('0x')) { - privateKey = (`0x${privateKey}` as unknown) as `0x${string}`; + privateKey = `0x${privateKey}` as unknown as `0x${string}`; } // 3. Get RPC URL