import type { Contract} from "ethers" import { ethers } from "ethers" import type { TransactionResponse } from "@ethersproject/abstract-provider" import type * as EthereumProvider from "@rarible/ethereum-provider" import { signTypedData } from "@rarible/ethereum-provider" import type { Address, Binary, BigNumber, Word } from "@rarible/types" import { toAddress, toBigNumber, toBinary, toWord } from "@rarible/types" import type { MessageTypes, TypedMessage } from "@rarible/ethereum-provider/src/domain" import type { TypedDataSigner } from "@ethersproject/abstract-signer" import { encodeParameters } from "./abi-coder" export class EthersWeb3ProviderEthereum implements EthereumProvider.Ethereum { constructor(readonly web3Provider: ethers.providers.Web3Provider, readonly from?: string) { this.send = this.send.bind(this) } createContract(abi: any, address?: string): EthereumProvider.EthereumContract { if (!address) { throw new Error("No Contract address provided, it's required for EthersEthereum") } return new EthersContract(new ethers.Contract(address, abi, this.web3Provider.getSigner())) } send(method: string, params: any): Promise<any> { return this.web3Provider.send(method, params) } personalSign(message: string): Promise<string> { return this.web3Provider.getSigner().signMessage(message) } async signTypedData<T extends MessageTypes>(data: TypedMessage<T>): Promise<string> { const signer = await this.getFrom() return signTypedData(this.send, signer, data) } async getFrom(): Promise<string> { if (!this.from) { const [first] = await this.web3Provider.listAccounts() return first } return this.from } encodeParameter(type: any, parameter: any): string { return encodeParameters([type], [parameter]) } async getBalance(address: Address): Promise<BigNumber> { const balance = await this.web3Provider.getBalance(address) return toBigNumber(balance.toString()) } async getChainId(): Promise<number> { const { chainId } = await this.web3Provider.getNetwork() return chainId } } export class EthersEthereum implements EthereumProvider.Ethereum { constructor(readonly signer: TypedDataSigner & ethers.Signer) {} createContract(abi: any, address?: string): EthereumProvider.EthereumContract { if (!address) { throw new Error("No Contract address provided, it's required for EthersEthereum") } return new EthersContract(new ethers.Contract(address, abi, this.signer)) } personalSign(message: string): Promise<string> { return this.signer.signMessage(message) } async signTypedData<T extends MessageTypes>(data: TypedMessage<T>): Promise<string> { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { EIP712Domain, ...types } = data.types return this.signer._signTypedData(data.domain, types, data.message) } getFrom(): Promise<string> { return this.signer.getAddress() } encodeParameter(type: any, parameter: any): string { return encodeParameters([type], [parameter]) } async getBalance(address: Address): Promise<BigNumber> { if (!this.signer.provider) { throw new Error("EthersEthereum: signer provider does not exist") } const balance = await this.signer.provider.getBalance(address) return toBigNumber(balance.toString()) } async getChainId(): Promise<number> { return this.signer.getChainId() } } export class EthersContract implements EthereumProvider.EthereumContract { constructor(private readonly contract: Contract) { } functionCall(name: string, ...args: any): EthereumProvider.EthereumFunctionCall { return new EthersFunctionCall(this.contract, name, args) } } export class EthersFunctionCall implements EthereumProvider.EthereumFunctionCall { constructor( private readonly contract: Contract, private readonly name: string, private readonly args: any[], ) {} async getCallInfo(): Promise<EthereumProvider.EthereumFunctionCallInfo> { return { method: this.name, args: this.args, contract: this.contract.address, from: undefined, } } get data(): string { return (this.contract.populateTransaction[this.name](...this.args) as any).data } async estimateGas(options?: EthereumProvider.EthereumSendOptions) { const func = this.contract.estimateGas[this.name].bind(null, ...this.args) const value = await func(options) return value.toNumber() } call(options?: EthereumProvider.EthereumSendOptions): Promise<any> { const func = this.contract[this.name].bind(null, ...this.args) if (options) { return func(options) } else { return func() } } async send(options?: EthereumProvider.EthereumSendOptions): Promise<EthereumProvider.EthereumTransaction> { const func = this.contract[this.name].bind(null, ...this.args) if (options) { return new EthersTransaction(await func(options)) } else { return new EthersTransaction(await func()) } } } export class EthersTransaction implements EthereumProvider.EthereumTransaction { constructor(private readonly tx: TransactionResponse) {} get hash(): Word { return toWord(this.tx.hash) } async wait(): Promise<EthereumProvider.EthereumTransactionReceipt> { const receipt = await this.tx.wait() return { ...receipt, status: receipt.status === 1, events: (receipt as any).events, } } get to(): Address | undefined { return this.tx.to ? toAddress(this.tx.to) : undefined } get from(): Address { return toAddress(this.tx.from) } get data(): Binary { return toBinary(this.tx.data) } get nonce(): number { return this.tx.nonce } }