@lit-protocol/vincent-scaffold-sdk
Version:
Shared build configuration and utilities for Vincent abilities and policies
196 lines (168 loc) • 5.24 kB
text/typescript
/**
* LitActionsSmartSigner for EIP-7702 delegated transactions
*
* This signer implements the Alchemy SmartAccountSigner interface to support
* EIP-7702 delegation with the Lit Protocol PKP. It enables gasless transactions
* through integration with Alchemy's gas sponsorship.
*
* @example
* ```typescript
* const signer = new LitActionsSmartSigner({
* pkpPublicKey,
* chainId,
* });
*
* // Use with Alchemy's Smart Account Client, and pass as the signer.
* ```
*/
export interface LitActionsSmartSignerConfig {
pkpPublicKey: string;
chainId: number;
}
// Type definitions compatible with Alchemy SDK and viem
type Address = `0x${string}`;
type Hex = `0x${string}`;
// SignableMessage from viem - can be string or object with raw property
type SignableMessage =
| string
| {
raw: Hex | Uint8Array;
};
// TypedDataDefinition compatible with viem
type TypedDataDefinition = {
domain?: any;
types?: any;
primaryType?: string;
message?: any;
};
// EIP-7702 types
type AuthorizationRequest = {
address?: Address;
contractAddress?: Address;
chainId: number;
nonce: number;
};
type SignedAuthorization = {
address: Address;
chainId: number;
nonce: number;
r: Hex;
s: Hex;
v?: bigint;
yParity: number;
};
// SmartAccountSigner interface compatible with @account-kit/smart-contracts
interface SmartAccountSigner<Inner = any> {
signerType: string;
inner: Inner;
getAddress: () => Promise<Address>;
signMessage: (message: SignableMessage) => Promise<Hex>;
signTypedData: (params: TypedDataDefinition) => Promise<Hex>;
signAuthorization?: (
unsignedAuthorization: AuthorizationRequest
) => Promise<SignedAuthorization>;
}
export class LitActionsSmartSigner implements SmartAccountSigner {
readonly signerType = "lit-actions";
readonly inner: any;
private pkpPublicKey: string;
private signerAddress: string;
constructor(config: LitActionsSmartSignerConfig) {
if (config.pkpPublicKey.startsWith("0x")) {
config.pkpPublicKey = config.pkpPublicKey.slice(2);
}
this.pkpPublicKey = config.pkpPublicKey;
this.signerAddress = ethers.utils.computeAddress(
"0x" + config.pkpPublicKey
);
this.inner = {
pkpPublicKey: config.pkpPublicKey,
chainId: config.chainId,
}; // Inner client reference
}
async getAddress(): Promise<Address> {
return this.signerAddress as Address;
}
async signMessage(message: SignableMessage): Promise<Hex> {
let messageToSign: string | Uint8Array;
if (typeof message === "string") {
messageToSign = message;
} else {
messageToSign =
typeof message.raw === "string"
? ethers.utils.arrayify(message.raw)
: message.raw;
}
const messageHash = ethers.utils.hashMessage(messageToSign);
const sig = await Lit.Actions.signAndCombineEcdsa({
toSign: ethers.utils.arrayify(messageHash),
publicKey: this.pkpPublicKey,
sigName: `alchemyMessage`,
});
const parsedSig = JSON.parse(sig);
return ethers.utils.joinSignature({
r: "0x" + parsedSig.r.substring(2),
s: "0x" + parsedSig.s,
v: parsedSig.v,
}) as Hex;
}
async signTypedData(params: TypedDataDefinition): Promise<Hex> {
// console.log("signTypedData called with params", params);
// Create the EIP-712 hash
const hash = ethers.utils._TypedDataEncoder.hash(
params.domain || {},
params.types || {},
params.message || {}
);
const sig = await Lit.Actions.signAndCombineEcdsa({
toSign: ethers.utils.arrayify(hash),
publicKey: this.pkpPublicKey,
sigName: `alchemyTypedData`,
});
const parsedSig = JSON.parse(sig);
return ethers.utils.joinSignature({
r: "0x" + parsedSig.r.substring(2),
s: "0x" + parsedSig.s,
v: parsedSig.v,
}) as Hex;
}
// reference implementation is from Viem SmartAccountSigner
async signAuthorization(
unsignedAuthorization: AuthorizationRequest
): Promise<SignedAuthorization> {
// console.log("signAuthorization called with params", unsignedAuthorization);
const { contractAddress, chainId, nonce } = unsignedAuthorization;
if (!contractAddress || !chainId) {
throw new Error(
"Invalid authorization: contractAddress and chainId are required"
);
}
const hash = ethers.utils.keccak256(
ethers.utils.hexConcat([
"0x05",
ethers.utils.RLP.encode([
ethers.utils.hexlify(chainId),
contractAddress,
nonce ? ethers.utils.hexlify(nonce) : "0x",
]),
])
);
// console.log("hash in signAuthorization", hash);
const sig = await Lit.Actions.signAndCombineEcdsa({
toSign: ethers.utils.arrayify(hash),
publicKey: this.pkpPublicKey,
sigName: `alchemyAuth7702`,
});
const sigObj = JSON.parse(sig);
// console.log("sigObj in signAuthorization", sigObj);
return {
address: (unsignedAuthorization.address || contractAddress!) as Address,
chainId: chainId,
nonce: nonce,
r: ("0x" + sigObj.r.substring(2)) as Hex,
s: ("0x" + sigObj.s) as Hex,
v: BigInt(sigObj.v),
yParity: sigObj.v,
};
}
}