diff --git a/CHANGELOG.md b/CHANGELOG.md index 940331a7..7d221152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # Changelog +## [1.8.1] - 2024-05-14 +### New +- Added signTypedData method to all providers supported +### Bug Fix +- Sign message rpc parameters should be in the correct order according to the rpc documentation msg params should always be first + ## [1.8.0] - 2024-04-29 ### Breaking changes - Removed (deprecated) networks Goerli, Op Goerli, Arb Goerli, Mumbai, Klaytn and Mantle Testnet (Goerli) diff --git a/package-lock.json b/package-lock.json index 7df40872..ad171f0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@etherspot/prime-sdk", - "version": "1.8.0", + "version": "1.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@etherspot/prime-sdk", - "version": "1.8.0", + "version": "1.8.1", "license": "MIT", "dependencies": { "@apollo/client": "3.8.7", diff --git a/package.json b/package.json index 82351e2c..0a66064e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@etherspot/prime-sdk", - "version": "1.8.0", + "version": "1.8.1", "description": "Etherspot Prime (Account Abstraction) SDK", "keywords": [ "ether", diff --git a/src/sdk/base/BaseAccountAPI.ts b/src/sdk/base/BaseAccountAPI.ts index b3a48a20..e43448f6 100644 --- a/src/sdk/base/BaseAccountAPI.ts +++ b/src/sdk/base/BaseAccountAPI.ts @@ -1,4 +1,4 @@ -import { ethers, BigNumber, BigNumberish } from 'ethers'; +import { ethers, BigNumber, BigNumberish, TypedDataField } from 'ethers'; import { BehaviorSubject } from 'rxjs'; import { Provider } from '@ethersproject/providers'; import { EntryPoint, EntryPoint__factory, INonceManager, INonceManager__factory } from '../contracts'; @@ -508,4 +508,8 @@ export abstract class BaseAccountAPI { } return null; } + + async signTypedData(types: TypedDataField[], message: any) { + return this.services.walletService.signTypedData(types, message, this.accountAddress); + } } diff --git a/src/sdk/sdk.ts b/src/sdk/sdk.ts index b982adfb..968cfb3d 100644 --- a/src/sdk/sdk.ts +++ b/src/sdk/sdk.ts @@ -10,7 +10,7 @@ import { import { Factory, PaymasterApi, SdkOptions } from './interfaces'; import { Network } from "./network"; import { BatchUserOpsRequest, Exception, getGasFee, onRampApiKey, openUrl, UserOpsRequest } from "./common"; -import { BigNumber, BigNumberish, ethers, providers } from 'ethers'; +import { BigNumber, BigNumberish, ethers, providers, TypedDataField } from 'ethers'; import { Networks, onRamperAllNetworks } from './network/constants'; import { UserOperationStruct } from './contracts/account-abstraction/contracts/core/BaseAccount'; import { EtherspotWalletAPI, HttpRpcClient, VerifyingPaymasterAPI } from './base'; @@ -244,6 +244,13 @@ export class PrimeSdk { return this.bundler.sendUserOpToBundler(signedUserOp); } + async signTypedData( + DataFields: TypedDataField[], + message: any + ) { + return this.etherspotWallet.signTypedData(DataFields, message); + } + async getNativeBalance() { if (!this.etherspotWallet.accountAddress) { await this.getCounterFactualAddress(); diff --git a/src/sdk/wallet/providers/dynamic.wallet-provider.ts b/src/sdk/wallet/providers/dynamic.wallet-provider.ts index 34636d19..4c73eed9 100644 --- a/src/sdk/wallet/providers/dynamic.wallet-provider.ts +++ b/src/sdk/wallet/providers/dynamic.wallet-provider.ts @@ -1,6 +1,7 @@ import { NetworkNames, prepareNetworkName } from '../../network'; import { prepareAddress, UniqueSubject } from '../../common'; import { WalletProvider } from './interfaces'; +import { TypedDataField } from 'ethers'; export abstract class DynamicWalletProvider implements WalletProvider { readonly address$ = new UniqueSubject(); @@ -20,6 +21,8 @@ export abstract class DynamicWalletProvider implements WalletProvider { abstract signMessage(message: any): Promise; + abstract signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise; + protected setAddress(address: string): void { this.address$.next(prepareAddress(address)); } diff --git a/src/sdk/wallet/providers/interfaces.ts b/src/sdk/wallet/providers/interfaces.ts index 3ecf35e1..5d01514c 100644 --- a/src/sdk/wallet/providers/interfaces.ts +++ b/src/sdk/wallet/providers/interfaces.ts @@ -1,4 +1,4 @@ -import { BytesLike, Wallet } from 'ethers'; +import { BytesLike, TypedDataField, Wallet } from 'ethers'; import type UniversalProvider from '@walletconnect/universal-provider'; import { UniqueSubject } from '../../common'; import { NetworkNames } from '../../network'; @@ -12,6 +12,7 @@ export interface WalletProvider { readonly networkName$?: UniqueSubject; signMessage(message: BytesLike): Promise; + signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise; } export interface Web3Provider { diff --git a/src/sdk/wallet/providers/key.wallet-provider.ts b/src/sdk/wallet/providers/key.wallet-provider.ts index 028d0990..5c56cd5a 100644 --- a/src/sdk/wallet/providers/key.wallet-provider.ts +++ b/src/sdk/wallet/providers/key.wallet-provider.ts @@ -1,4 +1,4 @@ -import { Wallet, BytesLike } from 'ethers'; +import { Wallet, BytesLike, TypedDataField } from 'ethers'; import { WalletProvider } from './interfaces'; export class KeyWalletProvider implements WalletProvider { @@ -18,4 +18,8 @@ export class KeyWalletProvider implements WalletProvider { async signMessage(message: BytesLike): Promise { return this.wallet.signMessage(message); } + + async signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise { + throw new Error('Not supported in this connectedProvider'); + } } diff --git a/src/sdk/wallet/providers/meta-mask.wallet-provider.ts b/src/sdk/wallet/providers/meta-mask.wallet-provider.ts index 6877514e..6c981141 100644 --- a/src/sdk/wallet/providers/meta-mask.wallet-provider.ts +++ b/src/sdk/wallet/providers/meta-mask.wallet-provider.ts @@ -1,4 +1,4 @@ -import { BytesLike } from 'ethers'; +import { BytesLike, TypedDataField } from 'ethers'; import { toHex } from '../../common'; import { DynamicWalletProvider } from './dynamic.wallet-provider'; @@ -52,11 +52,51 @@ export class MetaMaskWalletProvider extends DynamicWalletProvider { async signMessage(message: BytesLike): Promise { return this.sendRequest('personal_sign', [ - this.address, // toHex(message), + this.address, // ]); } + async signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise { + const chainId = await this.sendRequest('eth_chainId'); + const domainSeparator = { + name: "EtherspotWallet", + version: "2.0.0", + chainId: chainId, + verifyingContract: accountAddress + }; + let 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 + } + ]) + return signature; + } + protected async connect(): Promise { const { ethereum } = window; diff --git a/src/sdk/wallet/providers/wallet-connect-2.wallet-provider.ts b/src/sdk/wallet/providers/wallet-connect-2.wallet-provider.ts index 95800060..f6809b7b 100644 --- a/src/sdk/wallet/providers/wallet-connect-2.wallet-provider.ts +++ b/src/sdk/wallet/providers/wallet-connect-2.wallet-provider.ts @@ -1,4 +1,4 @@ -import { BytesLike } from 'ethers'; +import { BytesLike, TypedDataField } from 'ethers'; import { toHex } from '../../common'; import { DynamicWalletProvider } from './dynamic.wallet-provider'; import { EthereumProvider } from './interfaces'; @@ -38,6 +38,49 @@ export class WalletConnect2WalletProvider extends DynamicWalletProvider { return typeof response === 'string' ? response : null; } + async signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise { + + const domainSeparator = { + name: "EtherspotWallet", + version: "2.0.0", + chainId: this.provider.chainId, + verifyingContract: accountAddress + }; + const signature = await this.provider.signer.request({ + method: 'eth_signTypedData_v4', + params: [ + 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 + } + ] + }) + return typeof signature === 'string' ? signature : null; + } + protected updateSessionHandler(error: Error, payload: { params: { accounts: string[]; chainId: number } }): void { let address: string = null; let chainId: number = null; diff --git a/src/sdk/wallet/providers/wallet-connect.wallet-provider.ts b/src/sdk/wallet/providers/wallet-connect.wallet-provider.ts index 0eb298ce..d184e533 100644 --- a/src/sdk/wallet/providers/wallet-connect.wallet-provider.ts +++ b/src/sdk/wallet/providers/wallet-connect.wallet-provider.ts @@ -1,4 +1,4 @@ -import { BytesLike } from 'ethers'; +import { BytesLike, TypedDataField } from 'ethers'; import { toHex } from '../../common'; import { DynamicWalletProvider } from './dynamic.wallet-provider'; import { WalletConnectConnector } from './interfaces'; @@ -42,6 +42,10 @@ export class WalletConnectWalletProvider extends DynamicWalletProvider { return response || null; } + async signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise { + throw new Error('Not supported on this provider') + } + protected updateSessionHandler(error: Error, payload: { params: { accounts: string[]; chainId: number } }): void { let address: string = null; let chainId: number = null; diff --git a/src/sdk/wallet/providers/web3.wallet-provider.ts b/src/sdk/wallet/providers/web3.wallet-provider.ts index 5faf7c82..ae8a4a67 100644 --- a/src/sdk/wallet/providers/web3.wallet-provider.ts +++ b/src/sdk/wallet/providers/web3.wallet-provider.ts @@ -1,4 +1,4 @@ -import { BytesLike } from 'ethers'; +import { BytesLike, TypedDataField } from 'ethers'; import { prepareAddress, toHex } from '../../common'; import { NetworkNames, prepareNetworkName } from '../../network'; import { Web3Provider } from './interfaces'; @@ -51,13 +51,53 @@ export class Web3WalletProvider extends DynamicWalletProvider { return this.sendRequest( 'personal_sign', [ - this.address, // toHex(message), + this.address, // ], this.address, ); } + async signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise { + const chainId = await this.sendRequest('eth_chainId'); + const domainSeparator = { + name: "EtherspotWallet", + version: "2.0.0", + chainId: chainId, + verifyingContract: accountAddress + }; + let 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 + } + ]) + return signature; + } + protected async sendRequest(method: string, params: any[] = [], from?: string): Promise { return new Promise((resolve, reject) => { const id = Date.now(); diff --git a/src/sdk/wallet/providers/web3eip1193.wallet-provider.ts b/src/sdk/wallet/providers/web3eip1193.wallet-provider.ts index 029ba7a0..3a31f139 100644 --- a/src/sdk/wallet/providers/web3eip1193.wallet-provider.ts +++ b/src/sdk/wallet/providers/web3eip1193.wallet-provider.ts @@ -1,4 +1,4 @@ -import { BytesLike } from 'ethers'; +import { BytesLike, TypedDataField } from 'ethers'; import { prepareAddress, toHex } from '../../common'; import { NetworkNames, prepareNetworkName } from '../../network'; import { Web3eip1193Provider } from './interfaces'; @@ -50,6 +50,46 @@ export class Web3eip1193WalletProvider extends DynamicWalletProvider { return this.sendRequest('personal_sign', [toHex(message), this.address]); } + async signTypedData(typedData: TypedDataField[], message: any, accountAddress: string): Promise { + const chainId = await this.sendRequest('eth_chainId'); + const domainSeparator = { + name: "EtherspotWallet", + version: "2.0.0", + chainId: chainId, + verifyingContract: accountAddress + }; + let 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 + } + ]) + return signature; + } + protected async sendRequest(method: string, params: any[] = []): Promise { try { const result = await this.web3.request({ diff --git a/src/sdk/wallet/wallet.service.ts b/src/sdk/wallet/wallet.service.ts index 0db277fd..86423381 100644 --- a/src/sdk/wallet/wallet.service.ts +++ b/src/sdk/wallet/wallet.service.ts @@ -1,6 +1,6 @@ import { Observable, Subscription } from 'rxjs'; import { map } from 'rxjs/operators'; -import { BytesLike, ethers, providers, Wallet as EtherWallet } from 'ethers'; +import { BytesLike, ethers, providers, Wallet as EtherWallet, TypedDataField } from 'ethers'; import { Service, ObjectSubject } from '../common'; import { WalletProvider, WalletProviderLike, KeyWalletProvider, WalletLike } from './providers'; import { Wallet, WalletOptions } from './interfaces'; @@ -45,6 +45,10 @@ export class WalletService extends Service { return this.provider ? this.provider.signMessage(message) : null; } + async signTypedData(types: TypedDataField[], message: any, accountAddress: string): Promise { + return this.provider ? this.provider.signTypedData(types, message, accountAddress) : null; + } + protected switchWalletProvider(providerLike: WalletProviderLike): void { let provider: WalletProvider = null; if (providerLike) {