-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #506 from 0xzoz/add-tally-wallet
Add tally wallet
- Loading branch information
Showing
16 changed files
with
1,182 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import * as core from "@shapeshiftoss/hdwallet-core"; | ||
import * as tallyHo from "@shapeshiftoss/hdwallet-tallyho"; | ||
|
||
export function name(): string { | ||
return "Tally Ho"; | ||
} | ||
|
||
export function createInfo(): core.HDWalletInfo { | ||
return new tallyHo.TallyHoHDWalletInfo(); | ||
} | ||
|
||
export async function createWallet(): Promise<core.HDWallet> { | ||
const provider = { | ||
request: jest.fn(({ method, params }: any) => { | ||
switch (method) { | ||
case "eth_accounts": | ||
return ["0x3f2329C9ADFbcCd9A84f52c906E936A42dA18CB8"]; | ||
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: Unknown method ${method}`); | ||
} | ||
}), | ||
}; | ||
const wallet = new tallyHo.TallyHoHDWallet(provider); | ||
await wallet.initialize(); | ||
return wallet; | ||
} | ||
|
||
export function selfTest(get: () => core.HDWallet): void { | ||
let wallet: tallyHo.TallyHoHDWallet; | ||
|
||
beforeAll(async () => { | ||
const w = get() as tallyHo.TallyHoHDWallet; | ||
|
||
if (tallyHo.isTallyHo(w) && !core.supportsBTC(w) && core.supportsETH(w)) { | ||
wallet = w; | ||
} else { | ||
throw "Wallet is not a Tally"; | ||
} | ||
}); | ||
|
||
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 support EIP1559", async () => { | ||
if (!wallet) return; | ||
expect(await wallet.ethSupportsEIP1559()).toEqual(true); | ||
}); | ||
|
||
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: "TallyHo", | ||
}, | ||
]); | ||
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, | ||
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, | ||
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({ | ||
addressNList: core.bip32ToAddressNList("m/44'/60'/0'/0/0"), | ||
showDisplay: false, | ||
}) | ||
).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" | ||
); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "@shapeshiftoss/hdwallet-tallyho", | ||
"version": "1.19.0", | ||
"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.19.0", | ||
"lodash": "^4.17.21", | ||
"tallyho-onboarding": "^1.0.2" | ||
}, | ||
"devDependencies": { | ||
"@types/lodash": "^4.14.168", | ||
"typescript": "^4.3.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import * as core from "@shapeshiftoss/hdwallet-core"; | ||
|
||
import { TallyHoAdapter } from "./adapter"; | ||
|
||
describe("TallyHoAdapter", () => { | ||
it("throws error if provider is not preset", async () => { | ||
const keyring = new core.Keyring(); | ||
const adapter = TallyHoAdapter.useKeyring(keyring); | ||
await expect(async () => await adapter.pairDevice()).rejects.toThrowError("Could not get Tally Ho accounts."); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import * as core from "@shapeshiftoss/hdwallet-core"; | ||
import TallyHoOnboarding from "tallyho-onboarding"; | ||
|
||
import { TallyHoHDWallet } from "./tallyho"; | ||
|
||
interface TallyHoEthereumProvider { | ||
isTally?: boolean; | ||
} | ||
|
||
interface Window { | ||
ethereum?: TallyHoEthereumProvider; | ||
} | ||
|
||
export class TallyHoAdapter { | ||
keyring: core.Keyring; | ||
|
||
private constructor(keyring: core.Keyring) { | ||
this.keyring = keyring; | ||
} | ||
|
||
public static useKeyring(keyring: core.Keyring) { | ||
return new TallyHoAdapter(keyring); | ||
} | ||
|
||
public async pairDevice(): Promise<TallyHoHDWallet> { | ||
let provider: any; | ||
// eslint-disable-next-line no-useless-catch | ||
try { | ||
provider = await this.detectTallyProvider(); | ||
} catch (error) { | ||
throw error; | ||
} | ||
if (!provider) { | ||
const onboarding = new TallyHoOnboarding(); | ||
onboarding.startOnboarding(); | ||
console.error("Please install Tally Ho!"); | ||
} | ||
if (provider === null) { | ||
throw new Error("Could not get Tally Ho accounts."); | ||
} | ||
|
||
// eslint-disable-next-line no-useless-catch | ||
try { | ||
await provider.request({ method: "eth_requestAccounts" }); | ||
} catch (error) { | ||
throw error; | ||
} | ||
const wallet = new TallyHoHDWallet(provider); | ||
const deviceID = await wallet.getDeviceID(); | ||
this.keyring.add(wallet, deviceID); | ||
this.keyring.emit(["Tally Ho", deviceID, core.Events.CONNECT], deviceID); | ||
|
||
return wallet; | ||
} | ||
|
||
/* | ||
* Tally works the same way as metamask. | ||
* This code is copied from the @metamask/detect-provider package | ||
* @see https://www.npmjs.com/package/@metamask/detect-provider | ||
*/ | ||
private async detectTallyProvider(): Promise<TallyHoEthereumProvider | null> { | ||
let handled = false; | ||
|
||
return new Promise((resolve) => { | ||
if ((window as Window).ethereum) { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
handleEthereum(); | ||
} else { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
window.addEventListener("ethereum#initialized", handleEthereum, { once: true }); | ||
|
||
setTimeout(() => { | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
handleEthereum(); | ||
}, 3000); | ||
} | ||
|
||
function handleEthereum() { | ||
if (handled) { | ||
return; | ||
} | ||
handled = true; | ||
|
||
window.removeEventListener("ethereum#initialized", handleEthereum); | ||
|
||
const { ethereum } = window as Window; | ||
|
||
if (ethereum && ethereum.isTally) { | ||
resolve(ethereum as unknown as TallyHoEthereumProvider); | ||
} else { | ||
const message = ethereum ? "Non-TallyHo window.ethereum detected." : "Unable to detect window.ethereum."; | ||
|
||
console.error("hdwallet-tallyho: ", message); | ||
resolve(null); | ||
} | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.
84e453a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
hdwallet – ./
hdwallet-git-master-shapeshift.vercel.app
hdwallet-shapeshift.vercel.app