Last active
June 5, 2025 08:29
-
-
Save elmariachi111/0bdc2c52e40f9d2714e3d60f4fbd297b to your computer and use it in GitHub Desktop.
Lit capacity credit delegation & session sig creation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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