Skip to content

Commit

Permalink
ft: adding support of sign typed data along with eip 6942 (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikhilkumar1612 authored Dec 23, 2024
1 parent 7fbedb6 commit d8c417a
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 176 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Changelog
## [1.9.2] - 2024-12-21
### New
- Added support for signTypedData along with Eip-6942

## [1.9.1] - 2024-12-13
### Bug Fix
- Added `0x` before sending userOp to paymaster
Expand Down
70 changes: 70 additions & 0 deletions examples/26-sign-typed-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ethers } from "ethers";
import { EtherspotBundler, MessagePayload, PrimeSdk } from "../src";
import * as dotenv from 'dotenv';
dotenv.config();

async function main() {

const bundlerApiKey = "eyJvcmciOiI2NTIzZjY5MzUwOTBmNzAwMDFiYjJkZWIiLCJpZCI6IjMxMDZiOGY2NTRhZTRhZTM4MGVjYjJiN2Q2NDMzMjM4IiwiaCI6Im11cm11cjEyOCJ9";

const primeSdk = new PrimeSdk({ privateKey: process.env.WALLET_PRIVATE_KEY }, {
chainId: Number(process.env.CHAIN_ID),
bundlerProvider: new EtherspotBundler(Number(process.env.CHAIN_ID), bundlerApiKey)
});

const address: string = await primeSdk.getCounterFactualAddress();
console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);

const types = {
Mail: [
{name: 'from', type: 'Person'},
{name: 'to', type: 'Person'},
{name: 'contents', type: 'string'},
],
Person: [
{name: 'name', type: 'string'},
{name: 'wallet', type: 'address'}
],
}

const domainSeparator = {
name: "Ether Mail",
version: "1",
chainId: 1,
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
}

const typedData: MessagePayload = {
domain: domainSeparator,
primaryType: 'Person',
types
};

const message = {
from: {
name: "Cow",
wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
to: {
name: "Bob",
wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
contents: "Hello, Bob!"
}

const signature = await primeSdk.signTypedData(typedData, message);
console.log('signature:: ', signature);

// will work only if wallet is deployed already.
const signer = ethers.utils.verifyTypedData(
domainSeparator,
types,
message,
signature
);
console.log('signer:: ', signer);
}

main()
.catch(console.error)
.finally(() => process.exit());
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@etherspot/prime-sdk",
"version": "1.9.1",
"version": "1.9.2",
"description": "Etherspot Prime (Account Abstraction) SDK",
"keywords": [
"ether",
Expand Down Expand Up @@ -43,6 +43,7 @@
"21-get-multiple-accounts": "./node_modules/.bin/ts-node ./examples/21-get-multiple-accounts",
"22-concurrent-userops": "./node_modules/.bin/ts-node ./examples/22-concurrent-userops",
"25-get-quotes": "./node_modules/.bin/ts-node ./examples/25-get-quotes",
"26-sign-typed-data": "./node_modules/.bin/ts-node ./examples/26-sign-typed-data",
"format": "prettier --write \"{src,test,examples}/**/*.ts\"",
"lint": "eslint \"{src,test,examples}/**/*.ts\"",
"lint-fix": "npm run lint -- --fix",
Expand Down
12 changes: 8 additions & 4 deletions src/sdk/base/BaseAccountAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers, BigNumber, BigNumberish, TypedDataField } from 'ethers';
import { ethers, BigNumber, BigNumberish } from 'ethers';
import { BehaviorSubject } from 'rxjs';
import { Provider } from '@ethersproject/providers';
import { EntryPoint, EntryPoint__factory, INonceManager, INonceManager__factory } from '../contracts';
Expand All @@ -8,7 +8,7 @@ import { resolveProperties } from 'ethers/lib/utils';
import { PaymasterAPI } from './PaymasterAPI';
import { ErrorSubject, Exception, getUserOpHash, NotPromise, packUserOp } from '../common';
import { calcPreVerificationGas, GasOverheads } from './calcPreVerificationGas';
import { Factory, isWalletProvider, Network, NetworkNames, NetworkService, SdkOptions, SignMessageDto, State, StateService, validateDto, WalletProviderLike, WalletService } from '..';
import { Factory, isWalletProvider, MessagePayload, Network, NetworkNames, NetworkService, SdkOptions, SignMessageDto, State, StateService, validateDto, WalletProviderLike, WalletService } from '..';
import { Context } from '../context';
import { PaymasterResponse } from './VerifyingPaymasterAPI';

Expand All @@ -17,6 +17,7 @@ export interface BaseApiParams {
entryPointAddress: string;
accountAddress?: string;
overheads?: Partial<GasOverheads>;
factoryAddress?: string;
walletProvider: WalletProviderLike,
optionsLike?: SdkOptions
}
Expand Down Expand Up @@ -57,6 +58,7 @@ export abstract class BaseAccountAPI {
accountAddress?: string;
paymasterAPI?: PaymasterAPI;
factoryUsed: Factory;
factoryAddress?: string;

/**
* base constructor.
Expand Down Expand Up @@ -97,6 +99,7 @@ export abstract class BaseAccountAPI {
this.overheads = params.overheads;
this.entryPointAddress = params.entryPointAddress;
this.accountAddress = params.accountAddress;
this.factoryAddress = params.factoryAddress;

// factory "connect" define the contract address. the contract "connect" defines the "from" address.
this.entryPointView = EntryPoint__factory.connect(params.entryPointAddress, params.provider).connect(
Expand Down Expand Up @@ -509,7 +512,8 @@ export abstract class BaseAccountAPI {
return null;
}

async signTypedData(types: TypedDataField[], message: any) {
return this.services.walletService.signTypedData(types, message, this.accountAddress);
async signTypedData(types: MessagePayload, message: any) {
const initCode = await this.getInitCode();
return this.services.walletService.signTypedData(types, message, this.factoryAddress, initCode);
}
}
5 changes: 3 additions & 2 deletions src/sdk/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import {
EthereumProvider,
isWalletConnectProvider,
isWalletProvider,
MessagePayload,
WalletConnect2WalletProvider,
WalletProviderLike
} from './wallet';
import { Factory, PaymasterApi, SdkOptions } from './interfaces';
import { Network } from "./network";
import { BatchUserOpsRequest, Exception, getGasFee, onRampApiKey, openUrl, UserOpsRequest } from "./common";
import { BigNumber, BigNumberish, ethers, providers, TypedDataField } from 'ethers';
import { BigNumber, BigNumberish, ethers, providers } from 'ethers';
import { Networks, onRamperAllNetworks } from './network/constants';
import { UserOperationStruct } from './contracts/account-abstraction/contracts/core/BaseAccount';
import { EtherspotWalletAPI, HttpRpcClient, VerifyingPaymasterAPI } from './base';
Expand Down Expand Up @@ -245,7 +246,7 @@ export class PrimeSdk {
}

async signTypedData(
DataFields: TypedDataField[],
DataFields: MessagePayload,
message: any
) {
return this.etherspotWallet.signTypedData(DataFields, message);
Expand Down
5 changes: 2 additions & 3 deletions src/sdk/wallet/providers/dynamic.wallet-provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { NetworkNames, prepareNetworkName } from '../../network';
import { prepareAddress, UniqueSubject } from '../../common';
import { WalletProvider } from './interfaces';
import { TypedDataField } from 'ethers';
import { MessagePayload, WalletProvider } from './interfaces';

export abstract class DynamicWalletProvider implements WalletProvider {
readonly address$ = new UniqueSubject<string>();
Expand All @@ -21,7 +20,7 @@ export abstract class DynamicWalletProvider implements WalletProvider {

abstract signMessage(message: any): Promise<string>;

abstract signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise<string>;
abstract signTypedData(typedData: MessagePayload, message: any, factoryAddress?: string, initCode?: string): Promise<string>;

protected setAddress(address: string): void {
this.address$.next(prepareAddress(address));
Expand Down
11 changes: 9 additions & 2 deletions src/sdk/wallet/providers/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { BytesLike, TypedDataField, Wallet } from 'ethers';
import { BytesLike, TypedDataField, Wallet, TypedDataDomain } from 'ethers';
import type UniversalProvider from '@walletconnect/universal-provider';
import { UniqueSubject } from '../../common';
import { NetworkNames } from '../../network';

export type MessagePayload = {
domain: TypedDataDomain;
types: Record<string, TypedDataField[]>;
primaryType: string;
};

export interface WalletProvider {
readonly type?: string;
readonly wallet?: Wallet;
Expand All @@ -12,7 +18,7 @@ export interface WalletProvider {
readonly networkName$?: UniqueSubject<NetworkNames>;

signMessage(message: BytesLike): Promise<string>;
signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise<string>;
signTypedData(typedData: MessagePayload, message: any, factoryAddress?: string, initCode?: string): Promise<string>;
}

export interface Web3Provider {
Expand All @@ -31,6 +37,7 @@ export interface WalletConnectConnector {
accounts: string[];
chainId: number;
signPersonalMessage(params: any[]): Promise<any>;
request<T = unknown>(args: RequestArguments): Promise<T>;
on(event: string, callback: (error: Error | null, payload: any | null) => void): void;
}

Expand Down
28 changes: 24 additions & 4 deletions src/sdk/wallet/providers/key.wallet-provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Wallet, BytesLike, TypedDataField } from 'ethers';
import { WalletProvider } from './interfaces';
import { Wallet, BytesLike, utils } from 'ethers';
import { MessagePayload, WalletProvider } from './interfaces';

export class KeyWalletProvider implements WalletProvider {
readonly type = 'Key';
Expand All @@ -19,7 +19,27 @@ export class KeyWalletProvider implements WalletProvider {
return this.wallet.signMessage(message);
}

async signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise<string> {
throw new Error('Not supported in this connectedProvider');
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async signTypedData(typedData: MessagePayload, message: any, factoryAddress?: string, initCode?: string): Promise<string> {
const {domain, types} = typedData;

// EIP Domain has to be removed because ethers will add it using `domain`
if(types["EIP712Domain"]) {
delete typedData.types["EIP712Domain"];
}
const signature = await this.wallet._signTypedData(
domain,
types,
message
);

if (initCode !== '0x') {
const abiCoderResult = utils.defaultAbiCoder.encode(
['address', 'bytes', 'bytes'],
[factoryAddress, initCode, signature]
);
return abiCoderResult + '6492649264926492649264926492649264926492649264926492649264926492'; //magicBytes
}
return signature;
}
}
59 changes: 22 additions & 37 deletions src/sdk/wallet/providers/meta-mask.wallet-provider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BytesLike, TypedDataField } from 'ethers';
import { BytesLike, utils } from 'ethers';
import { toHex } from '../../common';
import { DynamicWalletProvider } from './dynamic.wallet-provider';
import { MessagePayload } from './interfaces';

declare const window: Window & {
ethereum: {
Expand Down Expand Up @@ -57,43 +58,27 @@ export class MetaMaskWalletProvider extends DynamicWalletProvider {
]);
}

async signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise<string> {
const chainId = await this.sendRequest<string>('eth_chainId');
const domainSeparator = {
name: "EtherspotWallet",
version: "2.0.0",
chainId: chainId,
verifyingContract: accountAddress
};
let signature = await this.sendRequest('eth_signTypedData_v4', [
async signTypedData(typedData: MessagePayload, message: any, factoryAddress?: string, initCode?: string): Promise<string> {
const {domain, types, primaryType} = typedData;

const msgParams = JSON.stringify({
domain,
message,
primaryType,
types
});
const signature = await this.sendRequest('eth_signTypedData_v4', [
this.address,
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"message": typedData
},
"primaryType": "message",
"domain": domainSeparator,
"message": message
}
])
msgParams
]);

if (initCode !== '0x') {
const abiCoderResult = utils.defaultAbiCoder.encode(
['address', 'bytes', 'bytes'],
[factoryAddress, initCode, signature]
);
return abiCoderResult + '6492649264926492649264926492649264926492649264926492649264926492'; //magicBytes
}
return signature;
}

Expand Down
Loading

0 comments on commit d8c417a

Please sign in to comment.