diff --git a/src/ts/index.ts b/src/ts/index.ts index e8346cd3..92642979 100644 --- a/src/ts/index.ts +++ b/src/ts/index.ts @@ -26,6 +26,7 @@ export * from "./proxy"; export * from "./reader"; export * from "./settlement"; export * from "./sign"; +export * from "./signers"; export * from "./swap"; export * from "./vault"; export * from "./types/ethers"; diff --git a/src/ts/signers.ts b/src/ts/signers.ts new file mode 100644 index 00000000..cc8aafb1 --- /dev/null +++ b/src/ts/signers.ts @@ -0,0 +1,129 @@ +import { _TypedDataEncoder } from "@ethersproject/hash"; +import type { JsonRpcProvider } from "@ethersproject/providers"; +import { ethers, Signer } from "ethers"; + +import { + isJsonRpcProvider, + TypedDataDomain, + TypedDataSigner, + TypedDataTypes, +} from "./types/ethers"; + +/** + * Wrapper around a TypedDataSigner Signer object that implements `_signTypedData` using + * `eth_signTypedData_v3` instead of `eth_signTypedData_v4`. + * + * Takes a Signer instance on creation. + * All other Signer methods are proxied to initial instance. + */ +export class TypedDataV3Signer implements TypedDataSigner { + signer: Signer; + provider: JsonRpcProvider; + _isSigner = true; + + constructor(signer: Signer) { + this.signer = signer; + + if (!signer.provider) { + throw new Error("Signer does not have a provider set"); + } + if (!isJsonRpcProvider(signer.provider)) { + throw new Error("Provider must be of type JsonRpcProvider"); + } + + this.provider = signer.provider; + } + + async _signTypedData( + domain: TypedDataDomain, + types: TypedDataTypes, + data: Record, + ): Promise { + const populated = await _TypedDataEncoder.resolveNames( + domain, + types, + data, + (name: string) => { + return this.provider.resolveName(name); + }, + ); + + const payload = _TypedDataEncoder.getPayload( + populated.domain, + types, + populated.value, + ); + const msg = JSON.stringify(payload); + + const address = await this.getAddress(); + + // Actual signing + return (await this.provider.send("eth_signTypedData_v3", [ + address.toLowerCase(), + msg, + ])) as string; + } + + // --- start boilerplate proxy methods --- + + getAddress(): Promise { + return this.signer.getAddress(); + } + signMessage(message: string | ethers.utils.Bytes): Promise { + return this.signer.signMessage(message); + } + signTransaction( + transaction: ethers.utils.Deferrable, + ): Promise { + return this.signer.signTransaction(transaction); + } + connect(provider: ethers.providers.Provider): ethers.Signer { + return this.signer.connect(provider); + } + getBalance(blockTag?: ethers.providers.BlockTag): Promise { + return this.signer.getBalance(blockTag); + } + getTransactionCount(blockTag?: ethers.providers.BlockTag): Promise { + return this.signer.getTransactionCount(blockTag); + } + estimateGas( + transaction: ethers.utils.Deferrable, + ): Promise { + return this.signer.estimateGas(transaction); + } + call( + transaction: ethers.utils.Deferrable, + blockTag?: ethers.providers.BlockTag, + ): Promise { + return this.signer.call(transaction, blockTag); + } + sendTransaction( + transaction: ethers.utils.Deferrable, + ): Promise { + return this.signer.sendTransaction(transaction); + } + getChainId(): Promise { + return this.signer.getChainId(); + } + getGasPrice(): Promise { + return this.signer.getGasPrice(); + } + resolveName(name: string): Promise { + return this.signer.resolveName(name); + } + checkTransaction( + transaction: ethers.utils.Deferrable, + ): ethers.utils.Deferrable { + return this.signer.checkTransaction(transaction); + } + populateTransaction( + transaction: ethers.utils.Deferrable, + ): Promise { + return this.signer.populateTransaction(transaction); + } + _checkProvider(operation?: string): void { + return this.signer._checkProvider(operation); + } + + // --- end boilerplate proxy methods --- +} diff --git a/src/ts/types/ethers.ts b/src/ts/types/ethers.ts index 308567f1..5399869f 100644 --- a/src/ts/types/ethers.ts +++ b/src/ts/types/ethers.ts @@ -1,3 +1,4 @@ +import type { JsonRpcProvider, Provider } from "@ethersproject/providers"; import type { ethers, Signer } from "ethers"; /** @@ -36,3 +37,12 @@ export interface TypedDataSigner extends Signer { export function isTypedDataSigner(signer: Signer): signer is TypedDataSigner { return "_signTypedData" in signer; } + +/** + * Checks whether the specified provider is a JSON RPC provider. + */ +export function isJsonRpcProvider( + provider: Provider, +): provider is JsonRpcProvider { + return "send" in provider; +}