diff --git a/package.json b/package.json index 1d80b18..e24a3b1 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@truffle/hdwallet-provider": "^1.6.0", "@types/bn.js": "^5.1.0", "bn.js": "^5.2.0", + "ethers": "^5.5.3", "mongodb": "^4.1.4", "substrate-ss58": "^1.0.3", "web3": "1.5.3", diff --git a/src/client.ts b/src/client.ts index ce97635..65f95a1 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4,8 +4,30 @@ import { provider } from "web3-core"; import detectEthereumProvider from "@metamask/detect-provider"; import { MetaMaskInpageProvider } from "@metamask/providers"; import BN from "bn.js"; +import { ethers } from "ethers"; import { DelphinusProvider } from "./provider"; +import { encodeL1address } from "./addresses"; + +export class DelphinusContractEther { + private contract: ethers.Contract; + + constructor(address: string, jsonABI: any, provider: BlockChainClient) { + this.contract = new ethers.Contract( + address, + jsonABI.abi, + provider.getSignerOrProvider() + ); + } + + async call(method: string, ...args: any[]) { + return await this.contract[method](...args); + } + + address() { + return this.contract.address; + } +} export class DelphinusContract { private readonly contract: Contract; @@ -223,6 +245,145 @@ async function withDelphinusWeb3( } } +export abstract class BlockChainClient { + private readonly provider: ethers.providers.JsonRpcProvider; + + constructor(provider: ethers.providers.JsonRpcProvider) { + this.provider = provider; + } + + abstract switchNet( + switchToChainId: number, + chainName: string, + rpcSource: string + ): Promise; + + async getAccountInfo() { + return await this.provider.getSigner().getAddress(); + } + + async getChainID() { + return (await this.provider.getNetwork()).chainId; + } + + async send(method: string, params: any[]) { + return this.provider.send(method, params); + } + + async getContract(address: string, jsonABI: any) { + return new DelphinusContractEther(address, jsonABI, this); + } + + getSignerOrProvider() { + return this.provider.getSigner() || this.provider; + } + + /** + * + * @param address address must start with 0x + * @returns + */ + async encodeL1Address(address: string) { + if (address.substring(0, 2) != "0x") { + throw "address must start with 0x"; + } + + const addressHex = address.substring(2); + const chex = (await this.getChainID()).toString(); + return encodeL1address(addressHex, chex); + } +} + +class BlockChainClientBrowser extends BlockChainClient { + private externalProvider: ethers.providers.ExternalProvider; + + constructor() { + const provider = new ethers.providers.Web3Provider(window.ethereum as any); + super(provider); + this.externalProvider = window.ethereum as any; + } + + async switchNet( + switchToChainId: number, + chainName: string, + rpcSource: string + ) { + let currentChainId = await this.getChainID(); + console.log("switch chain", currentChainId, switchToChainId); + if (currentChainId != switchToChainId) { + try { + await this.externalProvider.request!({ + method: "wallet_switchEthereumChain", + params: [{ chainId: switchToChainId }], + }); + } catch (e: any) { + if (e.code == 4902) { + try { + await this.externalProvider.request!({ + method: "wallet_addEthereumChain", + params: [ + { + chainId: switchToChainId, + chainName: chainName, + rpcUrls: [rpcSource], + }, + ], + }); + await this.externalProvider.request!({ + method: "wallet_switchEthereumChain", + params: [{ chainId: switchToChainId }], + }); + } catch (e) { + throw new Error("Add Network Rejected by User."); + } + } else { + throw new Error("Can not switch to chain " + switchToChainId); + } + } + } + currentChainId = await this.getChainID(); + console.log("switched", currentChainId, switchToChainId); + } +} + +class BlockChainClientProvider extends BlockChainClient { + constructor(rpcUrl: string) { + const provider = new ethers.providers.JsonRpcProvider(rpcUrl); + super(provider); + } + + async switchNet( + _switchToChainId: number, + _chainName: string, + _rpcSource: string + ) {} +} + +export interface SwitchChainInfo { + chainName: string; + chainId: number; + chainRpcUrl: string; +} + +export async function withBlockchainClient( + cb: (blockchain: BlockChainClient) => Promise, + browserMode: boolean, + specifyChain?: SwitchChainInfo +) { + let client = browserMode + ? new BlockChainClientBrowser() + : new BlockChainClientProvider(specifyChain!.chainRpcUrl); + + if (specifyChain) + await client.switchNet( + specifyChain.chainId, + specifyChain.chainName, + specifyChain.chainRpcUrl + ); + + return cb(client); +} + export async function withBrowerWeb3( cb: (web3: DelphinusWeb3) => Promise ) {