Skip to content

Commit 24b07dd

Browse files
committed
feat(docs): add examples
1 parent a95e774 commit 24b07dd

File tree

4 files changed

+371
-0
lines changed

4 files changed

+371
-0
lines changed

examples/example.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import inquirer from 'inquirer';
2+
import { customAuthFlow } from './src/custom-auth-flow';
3+
import { init } from './src/init';
4+
import { eoaNativeAuthFlow } from './src/eoa-native-auth-flow';
5+
6+
// Configuration constants
7+
const CLI_TITLE = 'Function Runner CLI';
8+
const EXIT_OPTION = 'Exit';
9+
/**
10+
* Function map containing all available functions
11+
* Add new functions here to include them in the CLI
12+
*/
13+
const functionMap: Record<string, () => void> = {
14+
init: init,
15+
customAuthFlow: customAuthFlow,
16+
eoaNativeAuthFlow: eoaNativeAuthFlow,
17+
};
18+
19+
/**
20+
* Gets the list of available function names plus the exit option
21+
*/
22+
function getAvailableOptions(): string[] {
23+
return [...Object.keys(functionMap), EXIT_OPTION];
24+
}
25+
26+
/**
27+
* Displays the CLI title and instructions
28+
*/
29+
function displayWelcome(): void {
30+
console.clear();
31+
console.log(`\n=== ${CLI_TITLE} ===`);
32+
console.log('Use ↑/↓ arrow keys to navigate, Enter to select\n');
33+
}
34+
35+
/**
36+
* Prompts user to select a function using keyboard navigation
37+
*/
38+
async function promptFunctionSelection(): Promise<string> {
39+
const { selectedFunction } = await inquirer.prompt([
40+
{
41+
type: 'list',
42+
name: 'selectedFunction',
43+
message: 'Select a function to execute:',
44+
choices: getAvailableOptions(),
45+
pageSize: 10,
46+
},
47+
]);
48+
49+
return selectedFunction;
50+
}
51+
52+
/**
53+
* Executes the selected function if it exists in the function map
54+
*/
55+
function executeFunction(functionName: string): void {
56+
const func = functionMap[functionName];
57+
if (func) {
58+
console.log(`\n--- Executing ${functionName} ---`);
59+
func();
60+
console.log(`--- ${functionName} completed ---\n`);
61+
} else {
62+
console.log(`❌ Function '${functionName}' not found`);
63+
}
64+
}
65+
66+
/**
67+
* Prompts user to continue or exit the application
68+
*/
69+
async function promptContinue(): Promise<boolean> {
70+
const { shouldContinue } = await inquirer.prompt([
71+
{
72+
type: 'confirm',
73+
name: 'shouldContinue',
74+
message: 'Would you like to run another function?',
75+
default: true,
76+
},
77+
]);
78+
79+
return shouldContinue;
80+
}
81+
82+
/**
83+
* Main CLI application loop
84+
*/
85+
async function runCLI(): Promise<void> {
86+
try {
87+
displayWelcome();
88+
89+
let keepRunning = true;
90+
91+
while (keepRunning) {
92+
const selectedFunction = await promptFunctionSelection();
93+
94+
if (selectedFunction === EXIT_OPTION) {
95+
console.log('👋 Goodbye!');
96+
break;
97+
}
98+
99+
executeFunction(selectedFunction);
100+
101+
keepRunning = await promptContinue();
102+
103+
if (keepRunning) {
104+
console.clear();
105+
console.log(`\n=== ${CLI_TITLE} ===`);
106+
console.log('Use ↑/↓ arrow keys to navigate, Enter to select\n');
107+
}
108+
}
109+
} catch (error) {
110+
console.error('❌ An error occurred:', error);
111+
process.exit(1);
112+
}
113+
}
114+
115+
// Start the CLI if this file is run directly
116+
if (require.main === module) {
117+
runCLI().catch((error) => {
118+
console.error('❌ Fatal error:', error);
119+
process.exit(1);
120+
});
121+
}
122+
123+
// Export for potential use as a module
124+
export { functionMap, runCLI };

examples/src/custom-auth-flow.ts

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { createAuthManager, storagePlugins } from '@lit-protocol/auth';
2+
import { hexToBigInt, keccak256, toBytes } from 'viem';
3+
import { init } from './init';
4+
export const customAuthFlow = async () => {
5+
const { myAccount, litClient } = await init();
6+
7+
// ========== Providing PKP for your user ==========
8+
9+
// Imagine you are an existing site owner, and you want to provide PKPs for your users.
10+
class myDappBackend {
11+
// Create a unique secret name of your dApp (you can share it if you want to share the authMethodType)
12+
uniqueDappName: string = 'my-supa-dupa-app-name';
13+
14+
// [❗️REQUIRED] a very big unique number for your unique dApp name
15+
// This will be used to generate a unique authData for each user
16+
// and be used to validate inside the Lit Action / immutable javascript.
17+
uniqueAuthMethodType: bigint = hexToBigInt(
18+
keccak256(toBytes(this.uniqueDappName))
19+
);
20+
21+
// [❗️REQUIRED] You will need to know the hexed version of the unique authMethodType
22+
// to be compared against with on Lit Action
23+
// ❗️❗️You will probably only need to know this value once and hardcode it
24+
// on the Lit Action validation code.
25+
// eg. permittedAuthMethod["auth_method_type"] === "0x15f85"
26+
hexedUniqueAuthMethodType = keccak256(toBytes(this.uniqueDappName));
27+
28+
// [❗️REQUIRED] Validation IPFS CID
29+
// https://explorer.litprotocol.com/ipfs/QmYLeVmwJPVs7Uebk85YdVPivMyrvoeKR6X37kyVRZUXW4
30+
public static validationIpfsCid =
31+
'QmYLeVmwJPVs7Uebk85YdVPivMyrvoeKR6X37kyVRZUXW4';
32+
33+
// [DEMO] Not a very safe database of registered users
34+
public registeredUsers: Array<{
35+
userId: string;
36+
password: string;
37+
pkpPublicKey: string | null;
38+
}> = [
39+
{ userId: 'alice', password: 'password-1', pkpPublicKey: null },
40+
{ userId: 'bob', password: 'password-2', pkpPublicKey: null },
41+
];
42+
43+
printSiteInfo() {
44+
console.log(
45+
`✍️ Unique Auth Method Type: ${this.hexedUniqueAuthMethodType}`
46+
);
47+
console.log('🔐 validationIpfsCid:', myDappBackend.validationIpfsCid);
48+
}
49+
50+
// [❗️REQUIRED] Generate a unique auth data for each user
51+
// Customise this to your needs.
52+
private _generateAuthData(userId: string) {
53+
const uniqueUserId = `${this.uniqueDappName}-${userId}`;
54+
55+
return {
56+
authMethodType: this.uniqueAuthMethodType,
57+
authMethodId: keccak256(toBytes(uniqueUserId)),
58+
};
59+
}
60+
61+
async mintPKPForUser(userId: string) {
62+
// 1. Check if the user is registered
63+
if (!this.registeredUsers.find((user) => user.userId === userId)) {
64+
throw new Error('User not registered');
65+
}
66+
67+
// 2. Generate the auth data from the unique user id
68+
const uniqueUserAuthData = this._generateAuthData(userId);
69+
console.log('✅ uniqueUserAuthData:', uniqueUserAuthData);
70+
71+
// 3. Mint a PKP for the user. Then, we will send the PKP to itself, since itself is also
72+
// a valid ETH Wallet. The owner of the PKP will have NO permissions. To access the PKP,
73+
// the user will need to pass the immutable validation code which lives inside the Lit Action.
74+
const { pkpData: mintedPKP, validationIpfsCid } =
75+
await litClient.mintWithCustomAuth({
76+
account: myAccount,
77+
authData: uniqueUserAuthData,
78+
scope: 'sign-anything',
79+
validationIpfsCid: myDappBackend.validationIpfsCid,
80+
});
81+
82+
console.log('✅ validationIpfsCid:', validationIpfsCid);
83+
console.log('✅ mintedPKP:', mintedPKP);
84+
console.log(
85+
'✅ hexedUniqueAuthMethodType:',
86+
this.hexedUniqueAuthMethodType
87+
);
88+
89+
// find the user first
90+
const user = this.registeredUsers.find((user) => user.userId === userId);
91+
if (!user) {
92+
throw new Error('User not found');
93+
}
94+
95+
// update the user with the PKP public key
96+
user.pkpPublicKey = mintedPKP.data.pubkey;
97+
}
98+
}
99+
100+
class myDappFrontend {
101+
constructor(private readonly myDappBackend: myDappBackend) {
102+
this.myDappBackend = myDappBackend;
103+
}
104+
105+
userDashboard(userId: string) {
106+
const user = this.myDappBackend.registeredUsers.find(
107+
(user) => user.userId === userId
108+
);
109+
const uniqueAuthMethodId = `${this.myDappBackend.uniqueDappName}-${userId}`;
110+
111+
if (!user) {
112+
throw new Error('User not found');
113+
}
114+
115+
return {
116+
getMyPkpPublicKey() {
117+
return user.pkpPublicKey;
118+
},
119+
120+
getAuthMethodId() {
121+
return keccak256(toBytes(uniqueAuthMethodId));
122+
},
123+
124+
// Ideally, as the site owner you will publish this to the public
125+
// so they can see the validation logic.
126+
getValidationIpfsCid() {
127+
return myDappBackend.validationIpfsCid;
128+
},
129+
};
130+
}
131+
}
132+
133+
// ========== As the site owner ==========
134+
const ownerDapp = new myDappBackend();
135+
ownerDapp.printSiteInfo();
136+
await ownerDapp.mintPKPForUser('alice');
137+
138+
// ========== As a user ==========
139+
const frontend = new myDappFrontend(ownerDapp);
140+
// Then as a user, first i will want to get the PKP public key if i don't have it already.
141+
// then i will also need to know the validation IPFS CID.
142+
const userDashboard = frontend.userDashboard('alice');
143+
const userPkpPublicKey = userDashboard.getMyPkpPublicKey();
144+
const dAppValidationIpfsCid = userDashboard.getValidationIpfsCid();
145+
const authMethodId = userDashboard.getAuthMethodId();
146+
147+
console.log('✅ userPkpPublicKey:', userPkpPublicKey);
148+
149+
// Then, in order to sign with the PKP the site owner minted for you, you will need to get the authContext from the authManager
150+
const userAuthManager = createAuthManager({
151+
// on browser, use browser storage plugin by default
152+
storage: storagePlugins.localStorageNode({
153+
appName: 'my-app', // namespace for isolating auth data
154+
networkName: 'naga-dev', // useful for distinguishing environments
155+
storagePath: './lit-auth-storage', // file path for storing session data
156+
}),
157+
});
158+
const userAuthContext = await userAuthManager.createCustomAuthContext({
159+
pkpPublicKey: userPkpPublicKey!,
160+
authConfig: {
161+
resources: [
162+
['pkp-signing', '*'],
163+
['lit-action-execution', '*'],
164+
],
165+
expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(),
166+
},
167+
litClient: litClient,
168+
customAuthParams: {
169+
litActionIpfsId: dAppValidationIpfsCid,
170+
jsParams: {
171+
pkpPublicKey: userPkpPublicKey,
172+
username: 'alice',
173+
password: 'lit',
174+
authMethodId: authMethodId,
175+
},
176+
},
177+
});
178+
179+
console.log('✅ userAuthContext:', userAuthContext);
180+
181+
// Finally, the user can sign with the PKP
182+
const userSignRes = await litClient.chain.ethereum.pkpSign({
183+
pubKey: userPkpPublicKey!,
184+
toSign: 'hello',
185+
authContext: userAuthContext,
186+
});
187+
188+
console.log('✅ userSignRes:', userSignRes);
189+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export const eoaNativeAuthFlow = async () => {
2+
const { init } = await import('./init');
3+
4+
const { myAccount, litClient, authManager } = await init();
5+
6+
// 1. Get the authenticator
7+
const { ViemAccountAuthenticator } = await import('@lit-protocol/auth');
8+
9+
const authDataViemAcconut = await ViemAccountAuthenticator.authenticate(
10+
myAccount
11+
);
12+
13+
const authSig = JSON.parse(authDataViemAcconut.accessToken);
14+
15+
console.log('✅ authSig:', authSig);
16+
17+
// 2. Authenticate the account
18+
const authData = await ViemAccountAuthenticator.authenticate(myAccount);
19+
console.log('✅ authData:', authData);
20+
21+
// 3a. Mint a PKP using your account. This is then owned by the account
22+
// ❗️ You will need to manually add permissions to the PKP before it can be used.
23+
const mintedPkpWithEoa = await litClient.mintWithEoa({
24+
account: myAccount,
25+
});
26+
27+
console.log('✅ mintedPkpWithEoa:', mintedPkpWithEoa);
28+
};

examples/src/init.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { createAuthManager, storagePlugins } from '@lit-protocol/auth';
2+
import { createLitClient } from '@lit-protocol/lit-client';
3+
import { privateKeyToAccount } from 'viem/accounts';
4+
5+
export const init = async () => {
6+
// Step 1: Convert your EOA private key to a viem account object
7+
const myAccount = privateKeyToAccount(
8+
process.env.PRIVATE_KEY as `0x${string}`
9+
);
10+
11+
const accountAddress = myAccount.address;
12+
console.log('🔥 accountAddress:', accountAddress);
13+
14+
// Step 2: Import and choose the Lit network to connect to
15+
// const { nagaDev } = await import('@lit-protocol/networks');
16+
const { nagaDev } = await import('@lit-protocol/networks');
17+
18+
// Step 3: Instantiate the LitClient using the selected network
19+
const litClient = await createLitClient({ network: nagaDev });
20+
21+
const authManager = createAuthManager({
22+
storage: storagePlugins.localStorageNode({
23+
appName: 'my-app',
24+
networkName: 'naga-dev',
25+
storagePath: './lit-auth-storage',
26+
}),
27+
});
28+
29+
return { myAccount, litClient, authManager };
30+
};

0 commit comments

Comments
 (0)