Remote Signer Module

This module provides functionality to sign user operations using a remote signer. It includes the following key components:

Types

RemoteSignerParams: Defines the parameters required to create a remote signer.

export type RemoteSignerParams = {
    account: LocalAccount,
    chainId: number,
    apiKey: string,
    sessionKey: string,
    permissionsBackendUrl?: string
}

Functions

signUserOp: Signs a user operation using the session key.

async function signUserOp(
    account: Account, 
    chainId: number, 
    apiKey: string, 
    sessionKey: string, 
    userOp: UserOperation, 
    permissionsBackendUrl: string = PERMISSIONS_URL
): Promise<UserOperation> {
    return signUserOpWithSessionKey(account.address, chainId, apiKey, sessionKey, userOp, permissionsBackendUrl);
}

toRemoteSigner: Converts a local account to an extended local account with remote signing capabilities.

export async function toRemoteSigner({
    account,
    chainId,
    apiKey,
    sessionKey
}: RemoteSignerParams): Promise<ExtendedLocalAccount> {
    // Get sessionKey from apiKey and account
    const sessionKeyResponse = await getSessionKey(account.address, chainId, apiKey, sessionKey);

    // Create the extendedLocalAccount object and add the method
    const extendedLocalAccount: ExtendedLocalAccount = {
        ...account, // Spread the properties of the original LocalAccount
        async signUserOpWithRemoteSigner(userOp: UserOperation) {
            const signedUserOp = await signUserOp(account, chainId, apiKey, sessionKeyResponse.sessionKey, userOp);
            return signedUserOp;
        },

        async signMessage({ message }) {
            throw new Error('signMessage with sessionKey not implemented');
        },
        async signTransaction(_, __) {
            throw new Error('signTransaction with sessionKey not implemented');
        },
        async signTypedData<
            const TTypedData extends TypedData | Record<string, unknown>,
            TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData
        >(typedData: TypedDataDefinition<TTypedData, TPrimaryType>) {
            throw new Error('signTypedData not implemented');
        },
    };

    return extendedLocalAccount;
}

signUserOperation: Signs a user operation using the remote signer.

export const signUserOperation = async (
    etherspotWalletAccount: LocalAccount, 
    chainId: number, 
    apiKey: string, 
    sessionKey: string, 
    userOp: UserOperation
) => {
    const remoteSigner: ExtendedLocalAccount = await toRemoteSigner({
        account: etherspotWalletAccount,
        chainId: chainId,
        apiKey: apiKey,
        sessionKey: sessionKey,
        permissionsBackendUrl: PERMISSIONS_URL
    });

    const signedUserOp = await remoteSigner.signUserOpWithRemoteSigner(userOp);

    if (!signedUserOp || !signedUserOp.signature || signedUserOp.signature === '0x') {
        throw new Error('Failed to sign user operation');
    }

    return signedUserOp;
}

Pre-requisites for using a sessionKey in remote-signing

  1. EtherspotWallet account should have SessionKeyValidator module installed.
  2. SessionKeyValidator varies with the kind of operation performed, i.e there can be multiple kinds of SessionKeyValidator and the nonce to be used as part of userOp is generated from the address of SessionKeyValidator
Example:

ERC20SessionKeyValidator is used to perform the erc20 based operations from etherspotWalletAddress All validations on the UserOp (ERC20 operations) are done by ERC20SessionKeyValidator module Nonce used during the UserOp Estimation is to be from:

const erc20SessionKeyValidator = '';
BigNumber.from(erc20SessionKeyValidator)

This nonce is later used to identify the validatorModule used during the validationPhase in EntryPoint contract.

function validateUserOp(
    PackedUserOperation calldata userOp,
    bytes32 userOpHash,
    uint256 missingAccountFunds
)
    external
    payable
    virtual
    override
    onlyEntryPoint
    payPrefund(missingAccountFunds)
    returns (uint256 validSignature)
{
    address validator;
    // @notice validator encoding in nonce is just an example!
    // @notice this is not part of the standard!
    // Account Vendors may choose any other way to implement validator selection
    uint256 nonce = userOp.nonce;
    assembly {
        validator := shr(96, nonce)
    }