Skip to content

Overhaul #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: v2-old
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions exampleLitActions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@lit-dev/example-lit-actions",
"version": "1.0.0",
"description": "Example Lit Actions test",
"scripts": {
"test": "dotenvx run --env-file=../.env -- mocha -r ts-node/register test/**/*.spec.ts"
},
"dependencies": {
"@lit-protocol/lit-node-client": "^7.0.0",
"@lit-protocol/lit-node-client-nodejs": "^7.0.0",
"@lit-protocol/constants": "^7.0.0",
"@lit-protocol/auth-helpers": "^7.0.0",
"ethers": "^5.7.2"
},
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/chai": "^5.2.1",
"chai": "^5.2.0",
"mocha": "^11.1.0",
"ts-node": "^10.9.2"
}
}
16 changes: 16 additions & 0 deletions exampleLitActions/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "example-lit-actions",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"projectType": "library",
"sourceRoot": "exampleLitActions/src",
"targets": {
"test": {
"executor": "nx:run-commands",
"options": {
"command": "dotenvx run -- mocha -r ts-node/register test/**/*.spec.ts",
"cwd": "exampleLitActions"
}
}
},
"tags": []
}
238 changes: 238 additions & 0 deletions exampleLitActions/src/conditionalSigning.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All code example files should be named src/index.ts for consistency across all the code examples. Basically every code example is going to at least have these three files: src/index.ts, src/utils.ts, and src/litAction.ts with the latter two being dependent on whether the code example actually needs them

Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import { LitNodeClient } from '@lit-protocol/lit-node-client';
import { LIT_RPC, LIT_NETWORK, LIT_ABILITY } from "@lit-protocol/constants";
import {
createSiweMessage,
generateAuthSig,
LitActionResource,
} from "@lit-protocol/auth-helpers";
import { LitContracts } from "@lit-protocol/contracts-sdk";
import * as ethers from "ethers";

// Utility function to get environment variables
const getEnv = (name: string): string => {
const value = process.env[name];
if (!value) {
console.warn(`Environment variable ${name} is not set`);
}
return value || "";
};
Comment on lines +11 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should always define util methods in a src/utils.ts file. Our goal with the main code example file (this file) is to be as lean as possible, focusing only on the code that makes this code example unique. We don't want to use to have to filter out boilerplate code to find the code they'll want to copy/paste into their project


// Environment variables
const ETHEREUM_PRIVATE_KEY = getEnv("ETHEREUM_PRIVATE_KEY");
const CHAIN_TO_CHECK_CONDITION_ON = getEnv("CHAIN_TO_CHECK_CONDITION_ON") || "ethereum";
const LIT_PKP_PUBLIC_KEY = process.env["LIT_PKP_PUBLIC_KEY"];
const LIT_CAPACITY_CREDIT_TOKEN_ID = process.env["LIT_CAPACITY_CREDIT_TOKEN_ID"];

// Define the Lit Action code for conditional signing
const litActionCode = `
async () => {
try {
// test an access control condition
const testResult = await Lit.Actions.checkConditions({
conditions,
authSig,
chain,
});

if (!testResult) {
LitActions.setResponse({ response: "address does not have 1 or more Wei on Ethereum Mainnet" });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on Ethereum Mainnet

This should use the chain variable since it's configurable

return;
}

const sigShare = await LitActions.signEcdsa({
toSign: dataToSign,
publicKey,
sigName: "sig",
});
} catch (error) {
LitActions.setResponse({ response: error.message });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of error.message, for all the code examples can you use error.message ? error.message : JSON.stringify(error) - it's what we're doing for the Vincent tools and should just be a pattern we establish for our LA code

}
};
`;
Comment on lines +26 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Can we always define the Lit Action code in a file called src/litAction.ts
  2. Can we always define Lit Actions using the pattern:
const _litActionCode = async () => {
    // Lit Action code here...
};

export const litActionCode = `(${_litActionCode.toString()})();`;

This gives us syntax highlighting for the Lit Action code


// Result interface for our function
interface ConditionalSigningResult {
success: boolean;
signatureResult?: Record<string, any>;
response?: string;
error?: string;
}
Comment on lines +53 to +59
Copy link
Contributor

@spacesailor24 spacesailor24 Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't return a custom type from the example functions, we should just return the raw Lit SDK response and then handle the parsing inside the test. We don't want to clutter the code example with code that's not technically required - it can become confusing for the user copy/pasting the example


/**
* Execute conditional signing with Lit Protocol
* @returns Result of the conditional signing operation
*/
export const conditionalSigning = async (): Promise<ConditionalSigningResult> => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each code example should just be named runExample, this help us standardize the tests a little

let litNodeClient: LitNodeClient;

try {
// Create ethers wallet for signing
const ethersWallet = new ethers.Wallet(
ETHEREUM_PRIVATE_KEY,
new ethers.providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE)
);

console.log("🔄 Connecting to the Lit network...");
litNodeClient = new LitNodeClient({
litNetwork: LIT_NETWORK.DatilTest,
debug: false,
Comment on lines +77 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all code examples, can we make these ENVs? It makes it easier to test across the different Lit network when we want to debug something happening on a specific Lit network

});
await litNodeClient.connect();
console.log("✅ Connected to the Lit network");

// Initialize Lit Contracts client
console.log("🔄 Connecting LitContracts client to network...");
const litContracts = new LitContracts({
signer: ethersWallet,
network: LIT_NETWORK.DatilTest,
debug: false,
Comment on lines +87 to +88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ENVs for these as well

});
await litContracts.connect();
console.log("✅ Connected LitContracts client to network");

// Handle PKP (Programmable Key Pair)
let pkpPublicKey = LIT_PKP_PUBLIC_KEY;
if (!pkpPublicKey) {
console.log("🔄 PKP wasn't provided, minting a new one...");
const pkpInfo = (await litContracts.pkpNftContractUtils.write.mint()).pkp;
pkpPublicKey = pkpInfo.publicKey;
console.log("✅ PKP successfully minted");
console.log(`ℹ️ PKP token ID: ${pkpInfo.tokenId}`);
console.log(`ℹ️ PKP public key: ${pkpPublicKey}`);
console.log(`ℹ️ PKP ETH address: ${pkpInfo.ethAddress}`);
} else {
console.log(`ℹ️ Using provided PKP: ${pkpPublicKey}`);
}
Comment on lines +94 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is the type of functionality we want to abstract into a util function. It's going to be used by many code examples and isn't directly relevant to the code example which is covering how to conditional signing with a PKP. Really the code to highlight for this code example is in the Lit Action, we just need a PKP public key to send along with the Lit Action request


// Handle Capacity Credit
let capacityTokenId = LIT_CAPACITY_CREDIT_TOKEN_ID;
if (!capacityTokenId) {
console.log("🔄 No Capacity Credit provided, minting a new one...");
capacityTokenId = (
await litContracts.mintCapacityCreditsNFT({
requestsPerKilosecond: 10,
daysUntilUTCMidnightExpiration: 1,
})
).capacityTokenIdStr;
console.log(`✅ Minted new Capacity Credit with ID: ${capacityTokenId}`);
} else {
console.log(
`ℹ️ Using provided Capacity Credit with ID: ${capacityTokenId}`
);
}

// Create capacity delegation auth signature
console.log("🔄 Creating capacityDelegationAuthSig...");
const { capacityDelegationAuthSig } =
await litNodeClient.createCapacityDelegationAuthSig({
dAppOwnerWallet: ethersWallet,
capacityTokenId,
delegateeAddresses: [ethersWallet.address],
uses: "1",
});
console.log("✅ Capacity Delegation Auth Sig created");
Comment on lines +107 to +133
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can also be abstracted into util functions


// Get session signatures
const sessionSigs = await litNodeClient.getSessionSigs({
chain: CHAIN_TO_CHECK_CONDITION_ON,
capabilityAuthSigs: [capacityDelegationAuthSig],
expiration: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 24 hours
resourceAbilityRequests: [
{
resource: new LitActionResource("*"),
ability: LIT_ABILITY.LitActionExecution,
},
],
authNeededCallback: async ({ resourceAbilityRequests, expiration, uri }) => {
const toSign = await createSiweMessage({
uri: uri || "",
expiration: expiration || "",
resources: resourceAbilityRequests || [],
walletAddress: await ethersWallet.getAddress(),
nonce: await litNodeClient.getLatestBlockhash(),
litNodeClient: litNodeClient,
});

return await generateAuthSig({
signer: ethersWallet,
toSign,
});
},
});
Comment on lines +135 to +161
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you something similar to console.log("✅ Capacity Delegation Auth Sig created"); after every major thing that happens in the code example? This help us find where to look at what went wrong while debugging - also just a nice status update when the user runs the code example


// Create auth signature for the action
const authSig = await (async () => {
const toSign = await createSiweMessage({
uri: "http://localhost",
expiration: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 24 hours
walletAddress: await ethersWallet.getAddress(),
nonce: await litNodeClient.getLatestBlockhash(),
resources: [
{
resource: new LitActionResource("*"),
ability: LIT_ABILITY.LitActionExecution,
},
],
litNodeClient: litNodeClient,
});
return await generateAuthSig({
signer: ethersWallet,
toSign,
});
})();
Comment on lines +163 to +182
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another util methods that takes the resources as an array of Lit Resources (type is importable from our types package or auth package)


// Data to sign - using a known hash for consistent testing
const dataToSign = ethers.utils.arrayify(
ethers.utils.keccak256([1, 2, 3, 4, 5])
);

// Execute the Lit Action
console.log("🔄 Executing Lit Action with conditional signing...");
const litActionResult = await litNodeClient.executeJs({
sessionSigs: sessionSigs,
code: litActionCode,
jsParams: {
conditions: [
{
conditionType: "evmBasic",
contractAddress: "",
standardContractType: "",
chain: CHAIN_TO_CHECK_CONDITION_ON,
method: "eth_getBalance",
parameters: [":userAddress", "latest"],
returnValueTest: {
comparator: ">=",
value: "0",
},
},
],
authSig: authSig,
chain: CHAIN_TO_CHECK_CONDITION_ON,
dataToSign: dataToSign,
publicKey: pkpPublicKey,
},
});

console.log("✅ Lit Action executed successfully");
console.log(JSON.stringify(litActionResult, null, 2));

return {
success: litActionResult.success,
signatureResult: litActionResult.signatures || {},
response: typeof litActionResult.response === 'string'
? litActionResult.response
: JSON.stringify(litActionResult.response)
};
Comment on lines +219 to +225
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we should just return litActionResult here, or just return litNodeClient.executeJs(...

} catch (error: any) {
console.error("Error in conditional signing:", error);
return {
success: false,
error: error?.message || String(error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also do error.message ? error.message : JSON.stringify(error) here

};
} finally {
if (litNodeClient!) {
await litNodeClient.disconnect();
console.log("✅ Disconnected from Lit network");
}
}
};
Loading