Skip to content

Instantly share code, notes, and snippets.

@elmariachi111
Last active June 5, 2025 08:29
Show Gist options
  • Save elmariachi111/0bdc2c52e40f9d2714e3d60f4fbd297b to your computer and use it in GitHub Desktop.
Save elmariachi111/0bdc2c52e40f9d2714e3d60f4fbd297b to your computer and use it in GitHub Desktop.
Lit capacity credit delegation & session sig creation
import {
createSiweMessage,
generateAuthSig,
LitAbility,
LitAccessControlConditionResource,
newSessionCapabilityObject,
type AuthSig,
} from '@lit-protocol/auth-helpers'
import {
decryptToFile,
encryptFile,
LitNodeClientNodeJs,
type LitNodeClient,
} from '@lit-protocol/lit-node-client'
import {
type AuthCallbackParams,
type SessionKeySignedMessage,
type SessionSigsMap,
type UnifiedAccessControlConditions,
} from '@lit-protocol/types'
import { type CustomControlCondition } from '@moleculexyz/core/schemas/custom-control-condition'
import { type Signer } from 'ethers'
import { type z } from 'zod'
import { currentChain, currentLitNetwork } from '@/utils/chain'
/**
* this is persisted on the user's browser and can be reused
* to identify the user.
*/
export interface AuthState {
authSig?: AuthSig
nonce?: string
sessionSigs?: SessionSigsMap
}
const MIN_SECONDS_SESSIONSIGS_CONSIDERED_VALID = 15
//see https://developer.litprotocol.com/coreConcepts/accessControl/EVM/customContractCalls#must-posess-at-least-one-erc1155-token-with-a-given-token-id
export const hasReadAllowanceOnIPNFT = (
chain: string,
ipnftAddress: string,
tokenId: string,
): z.infer<typeof CustomControlCondition> => ({
chain,
conditionType: 'evmContract',
contractAddress: ipnftAddress,
functionAbi: {
inputs: [
{
internalType: 'address',
name: 'reader',
type: 'address',
},
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'canRead',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
functionName: 'canRead',
functionParams: [':userAddress', tokenId],
returnValueTest: {
comparator: '=',
key: '',
value: 'true',
},
})
export const encryptFileWithLit = async (
litClient: LitNodeClient,
chain: string,
file: Blob | File,
unifiedAccessControlConditions: UnifiedAccessControlConditions,
sessionSigs: SessionSigsMap,
) =>
encryptFile(
{
chain,
file,
sessionSigs,
unifiedAccessControlConditions,
},
litClient,
)
export const decryptFileWithLit = async (
litClient: LitNodeClient,
chain: string,
ciphertext: string,
dataToEncryptHash: string,
sessionSigs: SessionSigsMap,
unifiedAccessControlConditions: UnifiedAccessControlConditions,
): Promise<ArrayBuffer> =>
decryptToFile(
{
chain,
ciphertext,
dataToEncryptHash,
sessionSigs,
unifiedAccessControlConditions,
},
litClient,
)
export const makeServerSideLitClient = async () => {
const litNodeClient = new LitNodeClientNodeJs({
alertWhenUnauthorized: false,
debug: true,
litNetwork: currentLitNetwork,
})
await litNodeClient.connect()
return litNodeClient
}
const isSessionSigExpired = (now: Date, sessionSig: SessionKeySignedMessage) =>
now.getTime() >
new Date(sessionSig.expiration).getTime() -
MIN_SECONDS_SESSIONSIGS_CONSIDERED_VALID * 1000
export const areSessionSigsValid = (now: Date, sessionSigs: SessionSigsMap) =>
Object.values(sessionSigs).every((sessionSig) => {
const signedMessage: SessionKeySignedMessage = JSON.parse(
sessionSig.signedMessage,
)
return !isSessionSigExpired(now, signedMessage)
})
export const makeSessionSigsAndDelegateCapacity = async (
litClient: LitNodeClient,
nonce: string,
signer: Signer, //todo: use SignerLike, avoid ethers
domain: string,
): Promise<SessionSigsMap> => {
const address = await signer.getAddress()
const authNeededCallback = async (authCallbackParams: AuthCallbackParams) => {
if (!authCallbackParams.uri) {
throw new Error('uri is required')
}
if (!authCallbackParams.expiration) {
throw new Error('expiration is required')
}
if (!authCallbackParams.resourceAbilityRequests) {
throw new Error('resourceAbilityRequests is required')
}
const toSign = await createSiweMessage({
domain,
expiration: authCallbackParams.expiration,
litClient,
litNodeClient: litClient,
nonce,
resources: authCallbackParams.resourceAbilityRequests,
statement: 'Authenticate with Lit to encrypt and decrypt files',
uri: authCallbackParams.uri,
walletAddress: address,
})
//the authsig will be stored on loal storage as "lit-wallet-sig"
return generateAuthSig({
signer,
toSign,
})
}
const capacityDelegationAuthSig = await (
await fetch(`/api/litCapacityCredit?address=${address}`, {
method: 'POST',
})
).json()
const sessionCapabilityObject = newSessionCapabilityObject()
const litResource = new LitAccessControlConditionResource('*')
// Add the capability to decrypt from the access control condition referred to by the lit resource.
sessionCapabilityObject.addCapabilityForResource(
litResource,
LitAbility.AccessControlConditionDecryption,
)
return litClient.getSessionSigs({
authNeededCallback,
capacityDelegationAuthSig,
chain: currentChain.name,
resourceAbilityRequests: [
{
ability: LitAbility.AccessControlConditionDecryption,
resource: litResource,
},
],
sessionCapabilityObject,
})
}
import { LIT_RPC } from '@lit-protocol/constants'
import { LitContracts } from '@lit-protocol/contracts-sdk'
import { providers, Wallet } from 'ethers'
import { type NextApiRequest, type NextApiResponse } from 'next'
import { makeServerSideLitClient } from '@/lib/lit'
import { currentLitNetwork } from '@/utils/chain'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== 'POST') {
res
.status(405)
.setHeader('Allow', ['POST'])
.end(`Method ${req.method} Not Allowed`)
return
}
// TODO: verify that this address actually belongs to the current logged in user
const { address } = req.query
console.log(`[LIT_CAPACITY_CREDIT] DELEGATING CREDITS FOR ${address}`)
try {
const walletWithCapacityCredit = new Wallet(
process.env.DEPLOYER_PRIVATE_KEY as string,
new providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE),
)
const contractClient = new LitContracts({
network: currentLitNetwork,
signer: walletWithCapacityCredit,
})
await contractClient.connect()
//todo: REUSE the capacity token, just delegate usage to the requested address
//contractClient.rateLimitNftContractUtils.read.getTokensByOwnerAddress
//contractClient.litTokenContract.read.
// this identifier will be used in delegation requests.
const { capacityTokenIdStr } = await contractClient.mintCapacityCreditsNFT({
daysUntilUTCMidnightExpiration: 7,
requestsPerKilosecond: 100,
})
const litClient = await makeServerSideLitClient()
const { capacityDelegationAuthSig } =
await litClient.createCapacityDelegationAuthSig({
capacityTokenId: capacityTokenIdStr,
dAppOwnerWallet: walletWithCapacityCredit,
delegateeAddresses: [address as string],
uses: '10',
})
console.log(
'[LIT_CAPACITY_CREDIT] capacity token [%s]',
capacityTokenIdStr,
capacityDelegationAuthSig,
)
return res.status(200).json(capacityDelegationAuthSig)
} catch (error: any) {
console.error('[LIT_CAPACITY_CREDIT] FAILED', error)
res.status(500).json({
error: error.shortMessage || error.toString(),
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment