增加walletconnectprovider

This commit is contained in:
cebgcontract 2022-08-23 11:59:24 +08:00
parent 1e0da8f2d8
commit 46ba09f45b
6 changed files with 1197 additions and 46 deletions

View File

@ -13,11 +13,13 @@
"license": "ISC",
"dependencies": {
"@metamask/eth-sig-util": "^4.0.1",
"@walletconnect/client": "^1.8.0",
"@walletconnect/core": "^1.8.0",
"@walletconnect/http-connection": "^1.8.0",
"bip39": "^3.0.4",
"crypto-js": "^4.1.1",
"ethereumjs-wallet": "^1.0.2",
"web3": "^1.7.4",
"web3-provider-engine": "^16.0.4",
"whatwg-fetch": "^3.6.2"
},
"devDependencies": {

View File

@ -0,0 +1,97 @@
import { IChainData } from '..'
import WalletConnectProvider from '../lib/WalletConnectProvider'
import { toHexChainId } from '../util/chain.util'
export class ZWalletConnect {
provider: WalletConnectProvider
constructor(rpc: String) {
this.provider = new WalletConnectProvider({
rpc
})
this._subscribeToEvents(this.provider)
}
private _subscribeToEvents(provider: WalletConnectProvider) {
this.provider.on('accountsChanged', async(accounts: string[]) => {
console.log('accountsChanged: ', accounts)
})
// Subscribe to chainId change
this.provider.on('chainChanged', async(chainId: string) => {
const chainIdNum = parseInt(chainId)
console.log('chainChanged', chainId, chainIdNum)
})
// Subscribe to session disconnection
this.provider.on('disconnect', (err: any) => {
console.log('disconnect', err)
})
}
/**
* @param data
*/
public async addOrChangeChain(data: IChainData, cb?: () => void) {
const onChainChange = (chainId: string) => {
console.log('switchEthereumChain: ', chainId)
this.provider.removeListener('chainChanged', onChainChange)
cb && cb()
}
this.provider.on('chainChanged', onChainChange)
try {
await this.provider.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: toHexChainId(data.id),
chainName: data.name,
nativeCurrency: {
name: data.symbol,
symbol: data.symbol,
decimals: data.decimals || 18
},
blockExplorerUrls: [data.explorerurl],
rpcUrls: [data.rpc]
}
]
})
console.log('add chain success')
} catch (addError) {
console.error('add chain error: ', addError)
this.provider.removeListener('chainChanged', onChainChange)
}
}
public async signData(signObj: any, signer: string) {
const msgParams = JSON.stringify(signObj)
const from = signer
console.log('clicked, sending personal sign req', 'from', from, msgParams)
const params = [from, msgParams]
const result: any = await this.sendCmd({
method: 'eth_signTypedData_v4',
params,
from
})
return result.result
}
public async sendCmd({ method, params, from }: any) {
return new Promise((resolve, reject) => {
this.provider.sendAsync({
method,
params,
from
}, async function(err: any, result: any) {
if (err) {
reject && reject(err)
return
}
resolve && resolve(result)
})
})
}
}

View File

@ -32,6 +32,7 @@ import {
import { signLogin } from "./util/sign.util";
import { JazzIcon } from "./comp/JazzIcon";
import { ERC1155Standard } from "./standards/ERC1155Standard";
import { ZWalletConnect } from "./comp/ZWalletConnect";
@ -60,6 +61,7 @@ export interface IChainData {
id: number;
symbol: string;
explorerurl: string;
decimals?: number
}
@singleton
export default class JCWallet {
@ -72,12 +74,14 @@ export default class JCWallet {
public erc20Standard: ERC20Standard;
public erc721Standard: ERC721Standard;
public erc1155Standard: ERC1155Standard;
public wConnect: ZWalletConnect;
public mainHandlers = createWalletEvents();
public data: IAccount[] = [];
public iconType = "jazz";
private accountIndex = 0;
private walletType: number = 0;
constructor(password: string) {
constructor(type: number, password: string) {
// this.web3 = new Web3('https://rpc-mainnet.kcc.network')
if (!password) {
throw new Error("need password");
@ -85,14 +89,17 @@ export default class JCWallet {
if (!checkPassword(password)) {
throw new Error("password error");
}
this.walletType = type;
this.password = password;
savePassword(password);
var start = Date.now();
this.web3 = new Web3("https://rpc-testnet.kcc.network");
const rpc = "https://rpc-testnet.kcc.network"
this.web3 = new Web3(rpc);
console.log(`init web3 cost: ${(Date.now() - start) / 1000}`);
this.erc20Standard = new ERC20Standard(this.web3);
this.erc721Standard = new ERC721Standard(this.web3);
this.erc1155Standard = new ERC1155Standard(this.web3);
this.wConnect = new ZWalletConnect(rpc);
start = Date.now();
this.wallet = this.web3.eth.accounts.wallet.load(
this.password,

View File

@ -12,7 +12,7 @@ export class WalletConnect extends core {
});
}
static getInstance(connectorOpts, pushServerOpts) {
static getInstance(connectorOpts, pushServerOpts): WalletConnect {
if (!this._instance) {
this._instance = new WalletConnect(connectorOpts, pushServerOpts)
}

View File

@ -0,0 +1,436 @@
import { WalletConnect } from "./WalletConnect";
import HttpConnection from "@walletconnect/http-connection";
import { payloadId, signingMethods, parsePersonalSign, getRpcUrl } from "@walletconnect/utils";
import {
IRPCMap,
IConnector,
IJsonRpcRequest,
IJsonRpcResponseSuccess,
IWalletConnectProviderOptions,
} from "@walletconnect/types";
const ProviderEngine = require("web3-provider-engine");
const CacheSubprovider = require("web3-provider-engine/subproviders/cache");
const FixtureSubprovider = require("web3-provider-engine/subproviders/fixture");
const FilterSubprovider = require("web3-provider-engine/subproviders/filters");
const HookedWalletSubprovider = require("web3-provider-engine/subproviders/hooked-wallet");
const NonceSubprovider = require("web3-provider-engine/subproviders/nonce-tracker");
const SubscriptionsSubprovider = require("web3-provider-engine/subproviders/subscriptions");
class WalletConnectProvider extends ProviderEngine {
public bridge = "https://bridge.walletconnect.org";
public rpc: IRPCMap | null = null;
public infuraId = "";
public http: HttpConnection | null = null;
public wc: IConnector;
public isConnecting = false;
public connected = false;
public connectCallbacks: any[] = [];
public accounts: string[] = [];
public chainId = 1;
public rpcUrl = "";
constructor(opts: IWalletConnectProviderOptions) {
super({ pollingInterval: opts.pollingInterval || 8000 });
this.bridge = opts.connector
? opts.connector.bridge
: opts.bridge || "https://bridge.walletconnect.org";
this.qrcode = typeof opts.qrcode === "undefined" || opts.qrcode !== false;
this.qrcodeModal = opts.qrcodeModal || this.qrcodeModal;
this.qrcodeModalOptions = opts.qrcodeModalOptions;
this.wc =
opts.connector ||
new WalletConnect({
bridge: this.bridge,
storageId: opts?.storageId,
signingMethods: opts?.signingMethods,
clientMeta: opts?.clientMeta,
}, null);
this.rpc = opts.rpc || null;
if (
!this.rpc &&
(!opts.infuraId || typeof opts.infuraId !== "string" || !opts.infuraId.trim())
) {
throw new Error("Missing one of the required parameters: rpc or infuraId");
}
this.infuraId = opts.infuraId || "";
this.chainId = opts?.chainId || this.chainId;
this.initialize();
}
get isWalletConnect() {
return true;
}
get connector() {
return this.wc;
}
get walletMeta() {
return this.wc.peerMeta;
}
// Connect with a wallet and return the addresses of all available
// accounts.
enable = async (): Promise<string[]> => {
const wc = await this.getWalletConnector();
if (wc) {
this.start();
this.subscribeWalletConnector();
return wc.accounts;
} else {
throw new Error("Failed to connect to WalleConnect");
}
};
request = async (payload: any): Promise<any> => {
return this.send(payload);
};
send = async (payload: any, callback?: any): Promise<any> => {
// Web3 1.0 beta.38 (and above) calls `send` with method and parameters
if (typeof payload === "string") {
const method = payload;
let params = callback;
// maintaining the previous behavior where personal_sign could be non-hex string
if (method === "personal_sign") {
params = parsePersonalSign(params);
}
return this.sendAsyncPromise(method, params);
}
// ensure payload includes id and jsonrpc
payload = { id: payloadId(), jsonrpc: "2.0", ...payload };
// maintaining the previous behavior where personal_sign could be non-hex string
if (payload.method === "personal_sign") {
payload.params = parsePersonalSign(payload.params);
}
// Web3 1.0 beta.37 (and below) uses `send` with a callback for async queries
if (callback) {
this.sendAsync(payload, callback);
return;
}
if (payload.method === "eth_signTypedData_v4" && this.walletMeta?.name === "MetaMask") {
const { result } = await this.handleOtherRequests(payload);
return result;
} else {
return this.sendAsyncPromise(payload.method, payload.params);
}
};
onConnect = (callback: any) => {
this.connectCallbacks.push(callback);
};
triggerConnect = (result: any) => {
if (this.connectCallbacks && this.connectCallbacks.length) {
this.connectCallbacks.forEach(callback => callback(result));
}
};
async disconnect() {
this.close();
}
async close() {
const wc = await this.getWalletConnector({ disableSessionCreation: true });
await wc.killSession();
await this.onDisconnect();
}
async handleRequest(payload: any) {
try {
let response;
let result: any = null;
const wc = await this.getWalletConnector();
switch (payload.method) {
case "wc_killSession":
await this.close();
result = null;
break;
case "eth_accounts":
result = wc.accounts;
break;
case "eth_coinbase":
result = wc.accounts[0];
break;
case "eth_chainId":
result = wc.chainId;
break;
case "net_version":
result = wc.chainId;
break;
case "eth_uninstallFilter":
this.sendAsync(payload, (_: any) => _);
result = true;
break;
default:
response = await this.handleOtherRequests(payload);
}
if (response) {
return response;
}
return this.formatResponse(payload, result);
} catch (error) {
this.emit("error", error);
throw error;
}
}
async handleOtherRequests(payload: any): Promise<IJsonRpcResponseSuccess> {
if (!signingMethods.includes(payload.method) && payload.method.startsWith("eth_")) {
return this.handleReadRequests(payload);
}
const wc = await this.getWalletConnector();
const result = await wc.sendCustomRequest(payload);
return this.formatResponse(payload, result);
}
async handleReadRequests(payload: any): Promise<IJsonRpcResponseSuccess> {
if (!this.http) {
const error = new Error("HTTP Connection not available");
this.emit("error", error);
throw error;
}
return this.http.send(payload);
}
formatResponse(payload: any, result: any) {
return {
id: payload.id,
jsonrpc: payload.jsonrpc,
result: result,
};
}
// disableSessionCreation - if true, getWalletConnector won't try to create a new session
// in case the connector is disconnected
getWalletConnector(opts: { disableSessionCreation?: boolean } = {}): Promise<IConnector> {
const { disableSessionCreation = false } = opts;
return new Promise((resolve, reject) => {
const wc = this.wc;
if (this.isConnecting) {
this.onConnect((x: any) => resolve(x));
} else if (!wc.connected && !disableSessionCreation) {
this.isConnecting = true;
wc.on("modal_closed", () => {
reject(new Error("User closed modal"));
});
wc.createSession({ chainId: this.chainId })
.then(() => {
wc.on("connect", (error, payload) => {
if (error) {
this.isConnecting = false;
return reject(error);
}
this.isConnecting = false;
this.connected = true;
if (payload) {
// Handle session update
this.updateState(payload.params[0]);
}
// Emit connect event
this.emit("connect");
this.triggerConnect(wc);
resolve(wc);
});
})
.catch(error => {
this.isConnecting = false;
reject(error);
});
} else {
if (!this.connected) {
this.connected = true;
this.updateState(wc.session);
}
resolve(wc);
}
});
}
async subscribeWalletConnector() {
const wc = await this.getWalletConnector();
wc.on("disconnect", error => {
if (error) {
this.emit("error", error);
return;
}
this.onDisconnect();
});
wc.on("session_update", (error, payload) => {
if (error) {
this.emit("error", error);
return;
}
// Handle session update
this.updateState(payload.params[0]);
});
}
async onDisconnect() {
// tslint:disable-next-line:await-promise
await this.stop();
this.emit("close", 1000, "Connection closed");
this.emit("disconnect", 1000, "Connection disconnected");
this.connected = false;
}
async updateState(sessionParams: any) {
const { accounts, chainId, networkId, rpcUrl } = sessionParams;
// Check if accounts changed and trigger event
if (!this.accounts || (accounts && this.accounts !== accounts)) {
this.accounts = accounts;
this.emit("accountsChanged", accounts);
}
// Check if chainId changed and trigger event
if (!this.chainId || (chainId && this.chainId !== chainId)) {
this.chainId = chainId;
this.emit("chainChanged", chainId);
}
// Check if networkId changed and trigger event
if (!this.networkId || (networkId && this.networkId !== networkId)) {
this.networkId = networkId;
this.emit("networkChanged", networkId);
}
// Handle rpcUrl update
this.updateRpcUrl(this.chainId, rpcUrl || "");
}
updateRpcUrl(chainId: number, rpcUrl: string | undefined = "") {
const rpc = { infuraId: this.infuraId, custom: this.rpc || undefined };
rpcUrl = rpcUrl || getRpcUrl(chainId, rpc);
if (rpcUrl) {
this.rpcUrl = rpcUrl;
this.updateHttpConnection();
} else {
this.emit("error", new Error(`No RPC Url available for chainId: ${chainId}`));
}
}
updateHttpConnection() {
if (this.rpcUrl) {
this.http = new HttpConnection(this.rpcUrl);
this.http.on("payload", payload => this.emit("payload", payload));
this.http.on("error", error => this.emit("error", error));
}
}
sendAsyncPromise(method: string, params: any): Promise<any> {
return new Promise((resolve, reject) => {
this.sendAsync(
{
id: payloadId(),
jsonrpc: "2.0",
method,
params: params || [],
},
(error: any, response: any) => {
if (error) {
reject(error);
return;
}
resolve(response.result);
},
);
});
}
private initialize() {
this.updateRpcUrl(this.chainId);
this.addProvider(
new FixtureSubprovider({
eth_hashrate: "0x00",
eth_mining: false,
eth_syncing: true,
net_listening: true,
web3_clientVersion: `WalletConnect/v1.x.x/javascript`,
}),
);
this.addProvider(new CacheSubprovider());
this.addProvider(new SubscriptionsSubprovider());
this.addProvider(new FilterSubprovider());
this.addProvider(new NonceSubprovider());
this.addProvider(new HookedWalletSubprovider(this.configWallet()));
this.addProvider({
handleRequest: async (payload: IJsonRpcRequest, next: any, end: any) => {
try {
const { error, result } = await this.handleRequest(payload);
end(error, result);
} catch (error) {
end(error);
}
},
setEngine: (_: any) => _,
});
}
private configWallet() {
return {
getAccounts: async (cb: any) => {
try {
const wc = await this.getWalletConnector();
const accounts = wc.accounts;
if (accounts && accounts.length) {
cb(null, accounts);
} else {
cb(new Error("Failed to get accounts"));
}
} catch (error) {
cb(error);
}
},
processMessage: async (msgParams: { from: string; data: string }, cb: any) => {
try {
const wc = await this.getWalletConnector();
const result = await wc.signMessage([msgParams.from, msgParams.data]);
cb(null, result);
} catch (error) {
cb(error);
}
},
processPersonalMessage: async (msgParams: { from: string; data: string }, cb: any) => {
try {
const wc = await this.getWalletConnector();
const result = await wc.signPersonalMessage([msgParams.data, msgParams.from]);
cb(null, result);
} catch (error) {
cb(error);
}
},
processSignTransaction: async (txParams: any, cb: any) => {
try {
const wc = await this.getWalletConnector();
const result = await wc.signTransaction(txParams);
cb(null, result);
} catch (error) {
cb(error);
}
},
processTransaction: async (txParams: any, cb: any) => {
try {
const wc = await this.getWalletConnector();
const result = await wc.sendTransaction(txParams);
cb(null, result);
} catch (error) {
cb(error);
}
},
processTypedMessage: async (msgParams: { from: string; data: string }, cb: any) => {
try {
const wc = await this.getWalletConnector();
const result = await wc.signTypedData([msgParams.from, msgParams.data]);
cb(null, result);
} catch (error) {
cb(error);
}
},
};
}
}
export default WalletConnectProvider;

693
yarn.lock

File diff suppressed because it is too large Load Diff