Skip to content

Commit bf1a89a

Browse files
committed
feat(e2e, lit-client, networks): add shiva env helpers and improve resilience
1 parent 115c194 commit bf1a89a

File tree

13 files changed

+736
-287
lines changed

13 files changed

+736
-287
lines changed

packages/e2e/src/helper/ShivaClient/createShivaClient.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ type PollTestnetStateOptions = {
6161
intervalMs?: number;
6262
};
6363

64+
type WaitForTestnetInfoOptions = {
65+
timeoutMs?: number;
66+
intervalMs?: number;
67+
};
68+
69+
export type ShivaTestnetInfo = {
70+
rpc_url?: string;
71+
[key: string]: unknown;
72+
};
73+
6474
/**
6575
* High-level interface surfaced by {@link createShivaClient}.
6676
*/
@@ -90,7 +100,11 @@ export type ShivaClient = {
90100
options?: PollTestnetStateOptions
91101
) => Promise<TestNetState>;
92102
/** Retrieve the full testnet configuration (contract ABIs, RPC URL, etc.). */
93-
getTestnetInfo: () => Promise<unknown>;
103+
getTestnetInfo: () => Promise<ShivaTestnetInfo | null>;
104+
/** Poll the manager until `/test/get/info/testnet/<id>` returns a payload. */
105+
waitForTestnetInfo: (
106+
options?: WaitForTestnetInfoOptions
107+
) => Promise<ShivaTestnetInfo>;
94108
/** Shut down the underlying testnet through the Shiva manager. */
95109
deleteTestnet: () => Promise<boolean>;
96110

@@ -321,11 +335,47 @@ export const createShivaClient = async (
321335
};
322336

323337
const getTestnetInfo = async () => {
324-
const response = await fetchShiva<unknown>(
338+
const response = await fetchShiva<ShivaTestnetInfo>(
325339
baseUrl,
326340
`/test/get/info/testnet/${testnetId}`
327341
);
328-
return response.body;
342+
return response.body ?? null;
343+
};
344+
345+
const waitForTestnetInfo = async (
346+
options: WaitForTestnetInfoOptions = {}
347+
): Promise<ShivaTestnetInfo> => {
348+
const {
349+
timeoutMs = DEFAULT_STATE_POLL_TIMEOUT,
350+
intervalMs = DEFAULT_STATE_POLL_INTERVAL,
351+
} = options;
352+
const deadline = Date.now() + timeoutMs;
353+
let lastError: unknown;
354+
355+
for (;;) {
356+
try {
357+
const info = await getTestnetInfo();
358+
if (info) {
359+
return info;
360+
}
361+
} catch (error) {
362+
lastError = error;
363+
}
364+
365+
if (Date.now() >= deadline) {
366+
const lastErrorMessage =
367+
lastError instanceof Error
368+
? lastError.message
369+
: lastError
370+
? String(lastError)
371+
: 'No response body received.';
372+
throw new Error(
373+
`Timed out after ${timeoutMs}ms waiting for testnet info for ${testnetId}. Last error: ${lastErrorMessage}`
374+
);
375+
}
376+
377+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
378+
}
329379
};
330380

331381
const deleteTestnet = async () => {
@@ -344,6 +394,7 @@ export const createShivaClient = async (
344394
stopRandomNodeAndWait,
345395
pollTestnetState,
346396
getTestnetInfo,
397+
waitForTestnetInfo,
347398
deleteTestnet,
348399

349400
// utils
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { SUPPORTED_NETWORKS, type SupportedNetwork } from '../createEnvVars';
2+
3+
export type ShivaEnvVars = {
4+
network: SupportedNetwork;
5+
shivaBaseUrl: string;
6+
litNodeBin: string;
7+
litActionsBin: string;
8+
};
9+
10+
export const createShivaEnvVars = (): ShivaEnvVars => {
11+
const networkEnv = process.env['NETWORK'];
12+
if (
13+
!networkEnv ||
14+
!SUPPORTED_NETWORKS.includes(networkEnv as SupportedNetwork)
15+
) {
16+
throw new Error(
17+
`Unsupported or missing NETWORK env var. Supported values: ${SUPPORTED_NETWORKS.join(
18+
', '
19+
)}`
20+
);
21+
}
22+
23+
const network = networkEnv as SupportedNetwork;
24+
25+
const shivaBaseUrl = process.env['SHIVA_BASE_URL'];
26+
if (!shivaBaseUrl) {
27+
throw new Error('Missing SHIVA_BASE_URL env var.');
28+
}
29+
30+
const litNodeBin = process.env['LIT_NODE_BIN'];
31+
if (!litNodeBin) {
32+
throw new Error('Missing LIT_NODE_BIN env var.');
33+
}
34+
35+
const litActionsBin = process.env['LIT_ACTIONS_BIN'];
36+
if (!litActionsBin) {
37+
throw new Error('Missing LIT_ACTIONS_BIN env var.');
38+
}
39+
40+
return {
41+
network,
42+
shivaBaseUrl,
43+
litNodeBin,
44+
litActionsBin,
45+
};
46+
};

packages/e2e/src/helper/createEnvVars.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
const supportedNetworks = ['naga-local', 'naga-test', 'naga-dev'] as const;
1+
export const SUPPORTED_NETWORKS = [
2+
'naga-local',
3+
'naga-test',
4+
'naga-dev',
5+
'naga-staging',
6+
] as const;
7+
export type SupportedNetwork = (typeof SUPPORTED_NETWORKS)[number];
28
type EnvName = 'local' | 'live';
39

410
export type EnvVars = {
5-
network: string;
11+
network: SupportedNetwork;
612
privateKey: `0x${string}`;
713
rpcUrl?: string | undefined;
814
localContextPath?: string;
@@ -19,14 +25,19 @@ const testEnv: Record<
1925

2026
export function createEnvVars(): EnvVars {
2127
// 1. Get network string
22-
const network = process.env['NETWORK']!!;
28+
const networkEnv = process.env['NETWORK'];
2329

24-
if (!network || !supportedNetworks.includes(network as any)) {
30+
if (
31+
!networkEnv ||
32+
!SUPPORTED_NETWORKS.includes(networkEnv as SupportedNetwork)
33+
) {
2534
throw new Error(
26-
`❌ NETWORK env var is not set or not supported. Found. ${network}`
35+
`❌ NETWORK env var is not set or not supported. Found. ${networkEnv}`
2736
);
2837
}
2938

39+
const network = networkEnv as SupportedNetwork;
40+
3041
const selectedNetwork = network.includes('local') ? 'local' : 'live';
3142

3243
// 2. Get private key
@@ -71,7 +82,11 @@ export function createEnvVars(): EnvVars {
7182
}
7283

7384
// -- live networks
74-
if (network === 'naga-dev' || network === 'naga-test') {
85+
if (
86+
network === 'naga-dev' ||
87+
network === 'naga-test' ||
88+
network === 'naga-staging'
89+
) {
7590
const liveRpcUrl = process.env['LIT_YELLOWSTONE_PRIVATE_RPC_URL'];
7691

7792
if (liveRpcUrl) {

packages/e2e/src/helper/createTestEnv.ts

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
LitNetworkModule,
33
nagaDev,
44
nagaLocal,
5+
nagaStaging,
56
nagaTest,
67
PaymentManager,
78
} from '@lit-protocol/networks';
@@ -46,36 +47,45 @@ export const createTestEnv = async (envVars: EnvVars): Promise<TestEnv> => {
4647
ledgerDepositAmount: '',
4748
};
4849

49-
if (envVars.network === 'naga-local') {
50-
networkModule = nagaLocal
51-
.withLocalContext({
52-
networkContextPath: envVars.localContextPath,
53-
networkName: 'naga-local',
54-
})
55-
.withOverrides({
56-
rpcUrl: envVars.rpcUrl,
57-
});
58-
config = CONFIG.LOCAL;
59-
} else if (
60-
envVars.network === 'naga-dev' ||
61-
envVars.network === 'naga-test'
62-
) {
63-
if (envVars.network === 'naga-dev') {
64-
networkModule = nagaDev;
65-
} else if (envVars.network === 'naga-test') {
66-
networkModule = nagaTest;
50+
switch (envVars.network) {
51+
case 'naga-local': {
52+
networkModule = nagaLocal
53+
.withLocalContext({
54+
networkContextPath: envVars.localContextPath,
55+
networkName: 'naga-local',
56+
})
57+
.withOverrides({
58+
rpcUrl: envVars.rpcUrl,
59+
});
60+
config = CONFIG.LOCAL;
61+
break;
6762
}
63+
case 'naga-dev':
64+
case 'naga-test':
65+
case 'naga-staging': {
66+
if (envVars.network === 'naga-dev') {
67+
networkModule = nagaDev;
68+
} else if (envVars.network === 'naga-test') {
69+
networkModule = nagaTest;
70+
} else {
71+
networkModule = nagaStaging;
72+
}
6873

69-
if (envVars.rpcUrl) {
70-
console.log(
71-
`🔧 Overriding RPC URL for ${envVars.network} to ${envVars.rpcUrl}`
72-
);
73-
networkModule = networkModule.withOverrides({
74-
rpcUrl: envVars.rpcUrl,
75-
});
76-
}
74+
if (envVars.rpcUrl) {
75+
console.log(
76+
`🔧 Overriding RPC URL for ${envVars.network} to ${envVars.rpcUrl}`
77+
);
78+
networkModule = networkModule.withOverrides({
79+
rpcUrl: envVars.rpcUrl,
80+
});
81+
}
7782

78-
config = CONFIG.LIVE;
83+
config = CONFIG.LIVE;
84+
break;
85+
}
86+
default: {
87+
throw new Error(`Unsupported network: ${envVars.network}`);
88+
}
7989
}
8090

8191
// 2. Create Lit Client

packages/e2e/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ export type { AuthContext } from './types';
1010

1111
// re-export new helpers that should be used to refactor the `init.ts` proces
1212
// see packages/e2e/src/tickets/delegation.suite.ts for usage examples
13-
export { createEnvVars } from './helper/createEnvVars';
13+
export { createEnvVars, SUPPORTED_NETWORKS } from './helper/createEnvVars';
14+
export type { SupportedNetwork } from './helper/createEnvVars';
1415
export { createTestAccount } from './helper/createTestAccount';
1516
export { createTestEnv } from './helper/createTestEnv';
1617
export type { CreateTestAccountResult } from './helper/createTestAccount';
1718
export { registerPaymentDelegationTicketSuite } from './tickets/delegation.suite';
1819

1920
// -- Shiva
2021
export { createShivaClient } from './helper/ShivaClient/createShivaClient';
22+
export { createShivaEnvVars } from './helper/ShivaClient/createShivaEnv';
23+
export type { ShivaTestnetInfo } from './helper/ShivaClient/createShivaClient';
24+
export type { ShivaEnvVars } from './helper/ShivaClient/createShivaEnv';
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { createAccBuilder } from '@lit-protocol/access-control-conditions';
2+
import { ViemAccountAuthenticator } from '@lit-protocol/auth';
3+
import { createSiweMessage } from '@lit-protocol/auth-helpers';
4+
import { createEnvVars } from '../../helper/createEnvVars';
5+
import { createTestAccount } from '../../helper/createTestAccount';
6+
import { createTestEnv } from '../../helper/createTestEnv';
7+
8+
const CHECK_CONDITIONS_LIT_ACTION = `
9+
(async () => {
10+
const { conditions, authSig } = jsParams;
11+
const isAuthorized = await Lit.Actions.checkConditions({
12+
conditions,
13+
authSig,
14+
chain: 'ethereum',
15+
});
16+
17+
Lit.Actions.setResponse({ response: isAuthorized ? 'true' : 'false' });
18+
})();
19+
`;
20+
21+
describe('PKP AuthSig Access Control', () => {
22+
let testEnv: Awaited<ReturnType<typeof createTestEnv>>;
23+
24+
beforeAll(async () => {
25+
const envVars = createEnvVars();
26+
testEnv = await createTestEnv(envVars);
27+
});
28+
29+
it('allows a PKP to satisfy wallet-ownership ACCs via a PKP-generated authSig', async () => {
30+
const pkpOwner = await createTestAccount(testEnv, {
31+
label: 'PKP ACC Owner',
32+
fundAccount: true,
33+
fundLedger: true,
34+
hasEoaAuthContext: true,
35+
hasPKP: true,
36+
fundPKP: true,
37+
hasPKPAuthContext: true,
38+
fundPKPLedger: true,
39+
});
40+
41+
const { pkp, pkpAuthContext, pkpViemAccount } = pkpOwner;
42+
43+
if (!pkp || !pkp.ethAddress) {
44+
throw new Error(
45+
'PKP data with ethereum address is required for this test'
46+
);
47+
}
48+
49+
if (!pkpAuthContext) {
50+
throw new Error('PKP auth context was not created');
51+
}
52+
53+
if (!pkpViemAccount) {
54+
throw new Error('PKP viem account was not initialized');
55+
}
56+
57+
// Ensure the PKP ledger has enough balance to pay for executeJs
58+
await testEnv.masterPaymentManager.depositForUser({
59+
userAddress: pkp.ethAddress as `0x${string}`,
60+
amountInEth: '0.2',
61+
});
62+
63+
const accessControlConditions = createAccBuilder()
64+
.requireWalletOwnership(pkp.ethAddress)
65+
.on('ethereum')
66+
.build();
67+
68+
const siweMessage = await createSiweMessage({
69+
walletAddress: pkpViemAccount.address,
70+
nonce: (await testEnv.litClient.getContext()).latestBlockhash,
71+
});
72+
73+
const pkpAuthSig = await ViemAccountAuthenticator.createAuthSig(
74+
pkpViemAccount,
75+
siweMessage
76+
);
77+
78+
expect(pkpAuthSig.address?.toLowerCase()).toBe(
79+
pkp.ethAddress.toLowerCase()
80+
);
81+
82+
const executionResult = await testEnv.litClient.executeJs({
83+
code: CHECK_CONDITIONS_LIT_ACTION,
84+
authContext: pkpAuthContext,
85+
jsParams: {
86+
conditions: accessControlConditions,
87+
// authSig: pkpAuthSig,
88+
},
89+
});
90+
91+
expect(executionResult.response).toBe('true');
92+
});
93+
});

0 commit comments

Comments
 (0)