From cc980c2f6c72461c4fc4e6adbe8a8eed4f091ebb Mon Sep 17 00:00:00 2001 From: Anson Date: Wed, 21 Feb 2024 00:36:11 +0300 Subject: [PATCH] Feature/lit 2491 js sdk make delegateeAddresses field optional (#376) * chore: bump accs-schema version to 0.0.6 * fix: wrong error message * feat: make delegatee Optional * feat: add test --- ...-rli-from-lit-node-client-no-delegatee.mjs | 184 ++++++++++++++++++ .../src/lib/lit-node-client-nodejs.ts | 31 ++- .../pkp-client/src/lib/wallet-factory.spec.ts | 2 +- 3 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 e2e-nodejs/group-rli/test-rli-from-lit-node-client-no-delegatee.mjs diff --git a/e2e-nodejs/group-rli/test-rli-from-lit-node-client-no-delegatee.mjs b/e2e-nodejs/group-rli/test-rli-from-lit-node-client-no-delegatee.mjs new file mode 100644 index 0000000000..aa35694e46 --- /dev/null +++ b/e2e-nodejs/group-rli/test-rli-from-lit-node-client-no-delegatee.mjs @@ -0,0 +1,184 @@ +// Usage: +// DEBUG=true NETWORK=manzano MINT_NEW=true yarn test:e2e:nodejs --filter=test-rli-from-lit-node-client-no-delegatee.mjs +import path from 'path'; +import { success, fail, testThis } from '../../tools/scripts/utils.mjs'; +import LITCONFIG from '../../lit.config.json' assert { type: 'json' }; +import { LitNodeClient } from '@lit-protocol/lit-node-client'; +import { LitAbility, LitActionResource } from '@lit-protocol/auth-helpers'; + +import { LitContracts } from '@lit-protocol/contracts-sdk'; +import { ethers } from 'ethers'; +import * as siwe from 'siwe'; + +export async function main() { + if (process.env.LOAD_ENV === 'false') { + console.log('❗️ This test cannot be run with LOAD_ENV=false'); + process.exit(); + } + + // NOTE: In this example, there will be no delegatees in the array + // ==================== Setup ==================== + + // ==================================================== + // = dApp Owner's Perspetive = + // ==================================================== + const provider = new ethers.providers.JsonRpcProvider( + LITCONFIG.CHRONICLE_RPC + ); + globalThis.LitCI.wallet = new ethers.Wallet( + LITCONFIG.CONTROLLER_PRIVATE_KEY, + provider + ); + const dAppOwnerWallet = globalThis.LitCI.wallet; + + // As a dApp owner, I want to mint a Rate Limit Increase NFT so he who owns or delegated to + // would be able to perform 14400 requests per day + let contractClient = new LitContracts({ + signer: dAppOwnerWallet, + debug: process.env.DEBUG === 'true' ?? LITCONFIG.TEST_ENV.debug, + network: process.env.NETWORK ?? LITCONFIG.TEST_ENV.litNetwork, + }); + + await contractClient.connect(); + + // -- mint new Capacity Credits NFT + const { capacityTokenIdStr } = await contractClient.mintCapacityCreditsNFT({ + requestsPerDay: 14400, // 10 request per minute + daysUntilUTCMidnightExpiration: 2, + }); + + const litNodeClient = new LitNodeClient({ + litNetwork: process.env.NETWORK ?? LITCONFIG.TEST_ENV.litNetwork, + debug: process.env.DEBUG === 'true' ?? LITCONFIG.TEST_ENV.debug, + minNodeCount: undefined, + checkNodeAttestation: process.env.CHECK_SEV ?? false, + }); + + await litNodeClient.connect(); + + // we will create an delegation auth sig, which internally we will create + // a recap object, add the resource "lit-ratelimitincrease://{tokenId}" to it, and add it to the siwe + // message. We will then sign the siwe message with the dApp owner's wallet. + const { capacityDelegationAuthSig } = + await litNodeClient.createCapacityDelegationAuthSig({ + uses: '1', + dAppOwnerWallet: dAppOwnerWallet, + capacityTokenId: capacityTokenIdStr, + }); + + // ==================================================== + // = As an end user = + // ==================================================== + const endUserContractClient = new LitContracts({ + signer: dAppOwnerWallet, + debug: process.env.DEBUG === 'true' ?? LITCONFIG.TEST_ENV.debug, + network: process.env.NETWORK ?? LITCONFIG.TEST_ENV.litNetwork, + }); + + await endUserContractClient.connect(); + + let endUserPkpMintRes = + await endUserContractClient.pkpNftContractUtils.write.mint(); + + const endUserPkpInfo = endUserPkpMintRes.pkp; + + // We need to setup a generic siwe auth callback that will be called by the lit-node-client + const endUserControllerAuthNeededCallback = async ({ + resources, + expiration, + uri, + }) => { + const litResource = new LitActionResource('*'); + + const recapObject = + await litNodeClient.generateSessionCapabilityObjectWithWildcards([ + litResource, + ]); + + recapObject.addCapabilityForResource( + litResource, + LitAbility.LitActionExecution + ); + + const verified = recapObject.verifyCapabilitiesForResource( + litResource, + LitAbility.LitActionExecution + ); + + if (!verified) { + throw new Error('Failed to verify capabilities for resource'); + } + + console.log('authCallback verified:', verified); + + let siweMessage = new siwe.SiweMessage({ + domain: 'localhost:3000', + address: dAppOwnerWallet.address, + statement: 'Some custom statement.', + uri, + version: '1', + chainId: '1', + expirationTime: expiration, + resources, + }); + + siweMessage = recapObject.addToSiweMessage(siweMessage); + + const messageToSign = siweMessage.prepareMessage(); + const signature = await dAppOwnerWallet.signMessage(messageToSign); + + const authSig = { + sig: signature.replace('0x', ''), + derivedVia: 'web3.eth.personal.sign', + signedMessage: messageToSign, + address: dAppOwnerWallet.address, + }; + + return authSig; + }; + + // - When generating a session sigs, we need to specify the resourceAbilityRequests, which + // is a list of resources and abilities that we want to be able to perform. + // "lit-ratelimitincrease://{tokenId}" that the dApp owner has delegated to us. + // - We also included the capacityDelegationAuthSig that we created earlier, which would be + // added to the capabilities array in the signing template. + let sessionSigs = await litNodeClient.getSessionSigs({ + expiration: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 24 hours + chain: 'ethereum', + resourceAbilityRequests: [ + { + resource: new LitActionResource('*'), + ability: LitAbility.LitActionExecution, + }, + ], + authNeededCallback: endUserControllerAuthNeededCallback, + capacityDelegationAuthSig, + }); + + // /web/execute + const res = await litNodeClient.executeJs({ + sessionSigs, + code: `(async () => { + const sigShare = await LitActions.signEcdsa({ + toSign: dataToSign, + publicKey, + sigName: "sig", + }); + })();`, + authMethods: [], + jsParams: { + dataToSign: ethers.utils.arrayify( + ethers.utils.keccak256([1, 2, 3, 4, 5]) + ), + publicKey: endUserPkpInfo.publicKey, + }, + }); + + if (res) { + return success('delegatee able to sign'); + } + + return fail(`Failed to get proof from Recap Session Capability`); +} + +await testThis({ name: path.basename(import.meta.url), fn: main }); diff --git a/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts b/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts index a7d51b5af0..c1511a9d1d 100644 --- a/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts +++ b/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts @@ -118,7 +118,7 @@ import * as siwe from 'siwe'; interface CapacityCreditsReq { dAppOwnerWallet: ethers.Wallet; capacityTokenId: string; - delegateeAddresses: string[]; + delegateeAddresses?: string[]; uses?: string; domain?: string; expiration?: string; @@ -135,8 +135,7 @@ interface CapacityCreditsRes { export class LitNodeClientNodeJs extends LitCore - implements LitClientSessionManager -{ + implements LitClientSessionManager { defaultAuthCallback?: (authSigParams: AuthCallbackParams) => Promise; // ========== Constructor ========== @@ -195,6 +194,11 @@ export class LitNodeClientNodeJs statement, } = params; + // -- if delegateeAddresses is not provided, set it to an empty array + if (!delegateeAddresses) { + delegateeAddresses = []; + } + // -- This is the owner address who holds the Capacity Credits NFT token and wants to delegate its // usage to a list of delegatee addresses const dAppOwnerWalletAddress = ethers.utils.getAddress( @@ -221,8 +225,8 @@ export class LitNodeClientNodeJs } // -- validate - if (!dAppOwnerWallet || !delegateeAddresses) { - throw new Error('Both parameters must exist'); + if (!dAppOwnerWallet) { + throw new Error('dAppOwnerWallet must exist'); } // -- validate dAppOwnerWallet is an ethers wallet @@ -230,10 +234,10 @@ export class LitNodeClientNodeJs // throw new Error('dAppOwnerWallet must be an ethers wallet'); // } - // -- validate delegateeAddresses has to be an array and has to have at least one address - if (!Array.isArray(delegateeAddresses) || delegateeAddresses.length === 0) { - throw new Error( - 'delegateeAddresses must be an array and has to have at least one' + // -- Strip the 0x prefix from each element in the addresses array if it exists + if (delegateeAddresses && delegateeAddresses.length > 0) { + delegateeAddresses = delegateeAddresses.map((address) => + address.startsWith('0x') ? address.slice(2) : address ); } @@ -242,11 +246,6 @@ export class LitNodeClientNodeJs // lit-ratelimitincrease://{tokenId} const litResource = new LitRLIResource(capacityTokenId); - // Strip the 0x prefix from each element in the addresses array - delegateeAddresses = delegateeAddresses.map((address) => - address.startsWith('0x') ? address.slice(2) : address - ); - const recapObject = await this.generateSessionCapabilityObjectWithWildcards( [litResource] ); @@ -2768,8 +2767,8 @@ export class LitNodeClientNodeJs const sessionCapabilityObject = params.sessionCapabilityObject ? params.sessionCapabilityObject : await this.generateSessionCapabilityObjectWithWildcards( - params.resourceAbilityRequests.map((r) => r.resource) - ); + params.resourceAbilityRequests.map((r) => r.resource) + ); const expiration = params.expiration || LitNodeClientNodeJs.getExpiration(); if (!this.latestBlockhash) { diff --git a/packages/pkp-client/src/lib/wallet-factory.spec.ts b/packages/pkp-client/src/lib/wallet-factory.spec.ts index ec520ea204..57b4f4b927 100644 --- a/packages/pkp-client/src/lib/wallet-factory.spec.ts +++ b/packages/pkp-client/src/lib/wallet-factory.spec.ts @@ -67,7 +67,7 @@ describe('WalletFactory', () => { }; expect(() => WalletFactory.createWallet('eth', ethProp)).toThrowError( - 'controllerAuthSig and authContext are defined, can only use one or the other' + 'Multiple authentications are defined, can only use one at a time' ); });