Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: walletconnect #529

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b89366d
feat: init walletconnect
GMSteuart May 7, 2022
d15021b
perf: reduce wallet and adapter code
GMSteuart May 9, 2022
ddd992c
chore: cleanup
GMSteuart May 13, 2022
5d2243e
chore: remove accidentally added tsconfig
GMSteuart May 14, 2022
dc5c6ff
chore: remove unused methods
GMSteuart May 17, 2022
b5c6795
fix: disconnect alias
GMSteuart May 17, 2022
6e40a55
test: WalletConnectWalletInfo
GMSteuart May 18, 2022
1d12a32
test: init integration tests
GMSteuart May 18, 2022
4f8197f
Merge branch 'master' of https://github.com/shapeshift/hdwallet into …
GMSteuart May 18, 2022
32bbd4d
test: all pass
GMSteuart May 18, 2022
998f347
chore: remove added package
GMSteuart May 18, 2022
698781c
chore: cleanup
GMSteuart May 19, 2022
5a44577
docs: add readme
GMSteuart May 25, 2022
72b6038
fix: state syncing
GMSteuart May 25, 2022
dedb026
test: add sandbox
GMSteuart May 25, 2022
26bf49a
apply remaining changes requested in code review.
pastaghost May 26, 2022
dc58d91
Merge branch 'master' of https://github.com/shapeshift/hdwallet into …
pastaghost Jun 2, 2022
d3e6c72
remove incomplete unit test suite
pastaghost Jun 2, 2022
5824896
chore: run linter
pastaghost Jun 2, 2022
da47fd7
fix: add ignore warning directives
pastaghost Jun 2, 2022
d995692
chore(release): publish 1.23.1-alpha.0
pastaghost Jun 2, 2022
18cb5b8
Merge branch 'master' of https://github.com/shapeshift/hdwallet into …
GMSteuart Jun 8, 2022
509762c
chore: bump version and deps
GMSteuart Jun 9, 2022
fececa8
Merge branch 'feature-walletconnect' of github.com:GMSteuart/hdwallet…
GMSteuart Jun 9, 2022
0199553
chore: fix package versions
GMSteuart Jun 9, 2022
4386610
chore: revert package revert
GMSteuart Jun 9, 2022
1a5c742
build: add dev command
GMSteuart Jun 13, 2022
77b2e9a
refactor: config param to provider object
GMSteuart Jun 13, 2022
502290e
chore: run linter
pastaghost Jun 14, 2022
71c4c48
chore(release): publish 1.23.1-alpha.1
pastaghost Jun 14, 2022
6c13441
fix: move this.provider() call to end of initialize()
pastaghost Jun 16, 2022
4d3b5be
chore: run linter
pastaghost Jun 16, 2022
433dcc2
chore(release): publish 1.23.1-alpha.2
pastaghost Jun 16, 2022
c00e3ec
fix: return correct type from ethSendTx
pastaghost Jun 17, 2022
b04feec
chore(release): publish 1.23.1-alpha.3
pastaghost Jun 17, 2022
d3c190a
refactor: set walletconnect provider config in adapter constructor
pastaghost Jun 21, 2022
7a9e82d
fix: fetch ethereum address if previously unset
pastaghost Jun 21, 2022
6a37027
chore(release): publish 1.23.1-alpha.4
pastaghost Jun 21, 2022
4b80468
fix: bump dependency version in hdwallet-sandbox
pastaghost Jun 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions integration/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as native from "@shapeshiftoss/hdwallet-native";
import * as portis from "@shapeshiftoss/hdwallet-portis";
import * as tallyHo from "@shapeshiftoss/hdwallet-tallyho";
import * as trezor from "@shapeshiftoss/hdwallet-trezor";
import * as walletconnect from "@shapeshiftoss/hdwallet-walletconnect";
import * as xdefi from "@shapeshiftoss/hdwallet-xdefi";

import { binanceTests } from "./binance";
Expand Down Expand Up @@ -56,6 +57,7 @@ export function integration(suite: WalletSuite): void {
(native.isNative(wallet) ? 1 : 0) +
(metamask.isMetaMask(wallet) ? 1 : 0) +
(tallyHo.isTallyHo(wallet) ? 1 : 0) +
(walletconnect.isWalletConnect(wallet) ? 1 : 0) +
(xdefi.isXDEFI(wallet) ? 1 : 0)
).toEqual(1);
});
Expand Down
3 changes: 3 additions & 0 deletions integration/src/walletconnect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { integration } from "./integration";
import * as WalletConnect from "./wallets/walletconnect";
integration(WalletConnect);
215 changes: 215 additions & 0 deletions integration/src/wallets/walletconnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import * as core from "@shapeshiftoss/hdwallet-core";
import * as walletconnect from "@shapeshiftoss/hdwallet-walletconnect";
import WalletConnectProvider from "@walletconnect/web3-provider";

export function name(): string {
return "WalletConnect";
}

export function createInfo(): core.HDWalletInfo {
return new walletconnect.WalletConnectWalletInfo();
}

export async function createWallet(): Promise<core.HDWallet> {
const accounts = ["0x3f2329C9ADFbcCd9A84f52c906E936A42dA18CB8", "0x8CB8864f185f14e8d7da0000e4a55a09e4156ff6", "0x4e8d2E3d5FDe8CB80A917e258548268734973f23"];
const provider = {
request: jest.fn(({ method, params }: any) => {
switch (method) {
case "eth_accounts":
return accounts;
case "personal_sign": {
const [message] = params;

if (message === "48656c6c6f20576f726c64")
return "0x29f7212ecc1c76cea81174af267b67506f754ea8c73f144afa900a0d85b24b21319621aeb062903e856352f38305710190869c3ce5a1425d65ef4fa558d0fc251b";

throw new Error("unknown message");
}
case "eth_sendTransaction": {
const [{ to }] = params;

return `txHash-${to}`;
}
default:
throw new Error(`ethereum: Unkown method ${method}`);
}
}),
bridge: "https://bridge.walletconnect.org",
qrcode: true,
qrcodeModal: {},
qrcodeModalOptions: undefined,
rpc: null,
infuraId: "",
http: null,
wc: {
sendTransaction: jest.fn((msg) => {
const { to } = msg;
return {hash: `txHash-${to}`};
}),
signMessage: jest.fn().mockReturnValue({
address: "0x3f2329C9ADFbcCd9A84f52c906E936A42dA18CB8",
signature:
"0x29f7212ecc1c76cea81174af267b67506f754ea8c73f144afa900a0d85b24b21319621aeb062903e856352f38305710190869c3ce5a1425d65ef4fa558d0fc251b",
})
}, // connector
isConnecting: false,
connected: false,
connectCallbacks: [],
rpcUrl: "",
isWalletConnect: true,
connector: {
chainId: 1,
accounts,
connected: true
},
walletMeta: { // wc.peerMeta

},
enable: async () => Promise.resolve(accounts),
send: jest.fn(),
onConnect: jest.fn(),
triggerConnect: jest.fn(),
disconnect: jest.fn(), // alias for close
close: jest.fn(),
handleRequest: jest.fn(),
handleOtherRequests: jest.fn(),
handleReadRequests: jest.fn(),
formatResponse: jest.fn(),
getWalletConnector: jest.fn(),
subscribeWalletConnector: jest.fn(),
onDisconnect: jest.fn(),
updateState: jest.fn(),
updateRpcUrl: jest.fn(),
updateHttpConnection: jest.fn(),
sendAsyncPromise: jest.fn()
};
const wallet = new walletconnect.WalletConnectHDWallet(provider as any);
await wallet.initialize();
return wallet;
}

export function selfTest(get: () => core.HDWallet): void {
let wallet: walletconnect.WalletConnectHDWallet;

beforeAll(async () => {
const w = get() as walletconnect.WalletConnectHDWallet;

if (walletconnect.isWalletConnect(w) && !core.supportsBTC(w) && core.supportsETH(w)) {
wallet = w;
} else {
throw new Error("Wallet is not a WalletConnect");
}
});

it("supports Ethereum mainnet", async () => {
if (!wallet) return;
expect(await wallet.ethSupportsNetwork()).toEqual(true);
});

it("does not support BTC", async () => {
if (!wallet) return;
expect(core.supportsBTC(wallet)).toBe(false);
});

it("does not support Native ShapeShift", async () => {
if (!wallet) return;
expect(wallet.ethSupportsNativeShapeShift()).toEqual(false);
});

it("does not support EIP1559", async () => {
if (!wallet) return;
expect(await wallet.ethSupportsEIP1559()).toEqual(false);
});

it("does not support Secure Transfer", async () => {
if (!wallet) return;
expect(await wallet.ethSupportsSecureTransfer()).toEqual(false);
});

it("uses correct eth bip44 paths", () => {
if (!wallet) return;
[0, 1, 3, 27].forEach((account) => {
const paths = wallet.ethGetAccountPaths({
coin: "Ethereum",
accountIdx: account,
});
expect(paths).toEqual([
{
addressNList: core.bip32ToAddressNList(`m/44'/60'/${account}'/0/0`),
hardenedPath: core.bip32ToAddressNList(`m/44'/60'/${account}'`),
relPath: [0, 0],
description: "WalletConnect",
},
]);
paths.forEach((path) => {
expect(
wallet.describePath({
coin: "Ethereum",
path: path.addressNList,
}).isKnown
).toBeTruthy();
});
});
});

it("can describe ETH paths", () => {
if (!wallet) return;
expect(
wallet.describePath({
path: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
coin: "Ethereum",
})
).toEqual({
verbose: "Ethereum Account #0",
coin: "Ethereum",
isKnown: true,
isPrefork: false,
accountIdx: 0,
wholeAccount: true,
});

expect(
wallet.describePath({
path: core.bip32ToAddressNList("m/44'/60'/3'/0/0"),
coin: "Ethereum",
})
).toEqual({
verbose: "Ethereum Account #3",
coin: "Ethereum",
isKnown: true,
isPrefork: false,
accountIdx: 3,
wholeAccount: true,
});

expect(
wallet.describePath({
path: core.bip32ToAddressNList("m/44'/60'/0'/0/3"),
coin: "Ethereum",
})
).toEqual({
verbose: "m/44'/60'/0'/0/3",
coin: "Ethereum",
isKnown: false,
});
});

it("should return a valid ETH address", async () => {
if (!wallet) return;
expect(
await wallet.ethGetAddress()
).toEqual("0x3f2329C9ADFbcCd9A84f52c906E936A42dA18CB8");
});

it("should sign a message", async () => {
if (!wallet) return;
const res = await wallet.ethSignMessage({
addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"),
message: "Hello World",
});
expect(res?.address).toEqual("0x3f2329C9ADFbcCd9A84f52c906E936A42dA18CB8");
expect(res?.signature).toEqual(
"0x29f7212ecc1c76cea81174af267b67506f754ea8c73f144afa900a0d85b24b21319621aeb062903e856352f38305710190869c3ce5a1425d65ef4fa558d0fc251b"
);
});
}
3 changes: 2 additions & 1 deletion integration/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"resolveJsonModule": true
"resolveJsonModule": true,
"types": ["jest"]
},
"include": ["./src/**/*.[ts|json]"],
"exclude": ["node_modules", "dist"]
Expand Down
25 changes: 25 additions & 0 deletions packages/hdwallet-walletconnect/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@shapeshiftoss/hdwallet-walletconnect",
"version": "1.21.1",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "dist/index.js",
"source": "src/index.ts",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc --build",
"clean": "rm -rf dist tsconfig.tsbuildinfo",
"prepublishOnly": "yarn clean && yarn build"
},
"dependencies": {
"@shapeshiftoss/hdwallet-core": "1.21.1",
"@walletconnect/qrcode-modal": "^1.7.8",
"@walletconnect/web3-provider": "^1.7.8",
"ethers": "^5.6.5"
},
"devDependencies": {
"typescript": "^4.3.2"
}
}
33 changes: 33 additions & 0 deletions packages/hdwallet-walletconnect/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import WalletConnectProvider from "@walletconnect/web3-provider";
import * as core from "@shapeshiftoss/hdwallet-core";

import { WalletConnectHDWallet } from "./walletconnect";

export class WalletConnectAdapter {
keyring: core.Keyring;

private constructor(keyring: core.Keyring) {
this.keyring = keyring;
}

public static useKeyring(keyring: core.Keyring) {
return new WalletConnectAdapter(keyring);
}

public async initialize(): Promise<number> {
return Object.keys(this.keyring.wallets).length;
}

public async pairDevice(provider: WalletConnectProvider): Promise<WalletConnectHDWallet> {
try {
const wallet = new WalletConnectHDWallet(provider);
const deviceID = await wallet.getDeviceID();
this.keyring.add(wallet, deviceID);
this.keyring.emit(["WalletConnect", deviceID, core.Events.CONNECT], deviceID);
return wallet;
} catch (error) {
console.error("Could not pair WalletConnect");
throw error;
}
}
}
31 changes: 31 additions & 0 deletions packages/hdwallet-walletconnect/src/ethereum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as core from "@shapeshiftoss/hdwallet-core";

export function describeETHPath(path: core.BIP32Path): core.PathDescription {
const pathStr = core.addressNListToBIP32(path);
const unknown: core.PathDescription = {
verbose: pathStr,
coin: "Ethereum",
isKnown: false,
};

if (path.length !== 5) return unknown;

if (path[0] !== 0x80000000 + 44) return unknown;

if (path[1] !== 0x80000000 + core.slip44ByCoin("Ethereum")) return unknown;

if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;

if (path[3] !== 0) return unknown;

if (path[4] !== 0) return unknown;

const index = path[2] & 0x7fffffff;
return {
verbose: `Ethereum Account #${index}`,
accountIdx: index,
wholeAccount: true,
coin: "Ethereum",
isKnown: true,
};
}
2 changes: 2 additions & 0 deletions packages/hdwallet-walletconnect/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./adapter";
export * from "./walletconnect";
Loading