Skip to content

Commit

Permalink
feat: trezor support
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Oct 5, 2024
1 parent 5a4b3a4 commit c4781cd
Show file tree
Hide file tree
Showing 11 changed files with 1,724 additions and 191 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
"^.+\\.scss": "<rootDir>/tests/mocks/StylesMock.tsx",
"@ledgerhq/hw-app-eth": "<rootDir>/tests/mocks/LedgerMock.ts",
"@ledgerhq/hw-transport-webhid": "<rootDir>/tests/mocks/LedgerMock.ts",
"@trezor/connect-web": "<rootDir>/tests/mocks/TrezorMock.ts",
},
globals: {
Buffer: Buffer,
Expand Down
1,668 changes: 1,513 additions & 155 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"@solid-primitives/i18n": "^2.1.1",
"@solid-primitives/storage": "^4.0.0",
"@solidjs/router": "^0.14.3",
"@trezor/connect-web": "^9.4.2",
"@vulpemventures/secp256k1-zkp": "^3.2.1",
"bignumber.js": "^9.1.2 ",
"bitcoinjs-lib": "^6.1.6",
Expand Down
2 changes: 2 additions & 0 deletions public/trezor.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/components/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
createSignal,
} from "solid-js";

import type { EIP6963ProviderInfo } from "../consts/Types";
import { useCreateContext } from "../context/Create";
import { useGlobalContext } from "../context/Global";
import { EIP6963ProviderInfo, useWeb3Signer } from "../context/Web3";
import { useWeb3Signer } from "../context/Web3";
import "../style/web3.scss";
import { formatError } from "../utils/errors";
import { cropString, isMobile } from "../utils/helper";
Expand Down
33 changes: 33 additions & 0 deletions src/consts/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,36 @@ export type ButtonLabelParams = {
key: DictKey;
params?: Record<string, string>;
};

export type EIP6963ProviderInfo = {
rdns: string;
uuid: string;
name: string;
icon?: string;
disabled?: boolean;
};

export type EIP1193Provider = {
isStatus?: boolean;
host?: string;
path?: string;
sendAsync?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void,
) => void;
send?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void,
) => void;
request: (request: {
method: string;
params?: Array<unknown>;
}) => Promise<unknown>;
on: (event: "chainChanged", cb: () => void) => void;
removeAllListeners: (event: "chainChanged") => void;
};

export type EIP6963ProviderDetail = {
info: EIP6963ProviderInfo;
provider: EIP1193Provider;
};
45 changes: 12 additions & 33 deletions src/context/Web3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import {

import { config } from "../config";
import { RBTC } from "../consts/Assets";
import { EIP1193Provider, EIP6963ProviderDetail } from "../consts/Types";
import LedgerSigner from "../utils/LedgerSigner";
import TrezorSigner from "../utils/TrezorSigner";
import { Contracts, getContracts } from "../utils/boltzClient";
import { useGlobalContext } from "./Global";
import LedgerIcon from "/ledger.svg";
import TrezorIcon from "/trezor.svg";

declare global {
interface WindowEventMap {
Expand All @@ -29,39 +32,6 @@ declare global {
}
}

export type EIP6963ProviderInfo = {
rdns: string;
uuid: string;
name: string;
icon?: string;
disabled?: boolean;
};

type EIP1193Provider = {
isStatus?: boolean;
host?: string;
path?: string;
sendAsync?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void,
) => void;
send?: (
request: { method: string; params?: Array<unknown> },
callback: (error: Error | null, response: unknown) => void,
) => void;
request: (request: {
method: string;
params?: Array<unknown>;
}) => Promise<unknown>;
on: (event: "chainChanged", cb: () => void) => void;
removeAllListeners: (event: "chainChanged") => void;
};

export type EIP6963ProviderDetail = {
info: EIP6963ProviderInfo;
provider: EIP1193Provider;
};

type EIP6963AnnounceProviderEvent = {
detail: EIP6963ProviderDetail;
};
Expand Down Expand Up @@ -104,6 +74,15 @@ const Web3SignerProvider = (props: {
disabled: navigator.hid === undefined,
},
},
trezor: {
provider: new TrezorSigner(),
info: {
name: "Trezor",
uuid: "trezor",
rdns: "trezor",
icon: TrezorIcon,
},
},
});
const [signer, setSigner] = createSignal<Signer | undefined>(undefined);
const [rawProvider, setRawProvider] = createSignal<
Expand Down
1 change: 1 addition & 0 deletions src/style/web3.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@

.provider-modal-icon {
height: 4rem;
min-width: 5rem;
}
5 changes: 3 additions & 2 deletions src/utils/LedgerSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
import log from "loglevel";

import { config } from "../config";
import { EIP1193Provider } from "../consts/Types";

class LedgerSigner {
class LedgerSigner implements EIP1193Provider {
private static readonly ethereumApp = "Ethereum";
private static readonly path = "44'/60'/0'/0/0";

Expand Down Expand Up @@ -136,7 +137,7 @@ class LedgerSigner {
const format = r[i++];

if (format !== 1) {
throw " format not supported";
throw "format not supported";
}

const nameLength = r[i++];
Expand Down
156 changes: 156 additions & 0 deletions src/utils/TrezorSigner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import type { Address, Unsuccessful } from "@trezor/connect-web";
import TrezorConnect from "@trezor/connect-web";
import type {
Response,
SuccessWithDevice,
} from "@trezor/connect/lib/types/params";
import {
JsonRpcProvider,
Signature,
Transaction,
TransactionLike,
} from "ethers";
import log from "loglevel";

import { config } from "../config";
import { EIP1193Provider } from "../consts/Types";

class TrezorSigner implements EIP1193Provider {
private static readonly path = "m/44'/60'/0'/0/0";

private readonly provider: JsonRpcProvider;
private initialized = false;

constructor() {
this.provider = new JsonRpcProvider(
config.assets["RBTC"].network.rpcUrls[0],
);
}

public request = async (request: {
method: string;
params?: Array<unknown>;
}) => {
switch (request.method) {
case "eth_requestAccounts": {
log.debug("Getting Trezor accounts");

await this.initialize();

const addresses = this.handleError<Address>(
await TrezorConnect.ethereumGetAddress({
path: TrezorSigner.path,
showOnTrezor: false,
} as any),
);

return [addresses.payload.address];
}

case "eth_sendTransaction": {
log.debug("Signing transaction with Trezor");

await this.initialize();

const txParams = request.params[0] as TransactionLike;

const [nonce, network, feeData] = await Promise.all([
this.provider.getTransactionCount(txParams.from),
this.provider.getNetwork(),
this.provider.getFeeData(),
]);

const trezorTx = {
to: txParams.to,
data: txParams.data,
nonce: nonce.toString(16),
gasLimit: (txParams as any).gas,
chainId: Number(network.chainId),
gasPrice: feeData.gasPrice.toString(16),
value: BigInt(txParams.value || 0).toString(16),
};
const signature = this.handleError(
await TrezorConnect.ethereumSignTransaction({
path: TrezorSigner.path,
transaction: trezorTx,
} as unknown as any),
);

const tx = Transaction.from({
...trezorTx,
type: 0,
value: txParams.value,
gasPrice: feeData.gasPrice,
nonce: Number(trezorTx.nonce),
signature: Signature.from(signature.payload),
});

await this.provider.send("eth_sendRawTransaction", [
tx.serialized,
]);

return tx.hash;
}

case "eth_signTypedData_v4": {
log.debug("Signing EIP-712 message with Trezor");

await this.initialize();

const message = JSON.parse(request.params[1] as string);
const signature = this.handleError(
await TrezorConnect.ethereumSignTypedData({
data: message,
path: TrezorSigner.path,
metamask_v4_compat: true,
}),
);
return signature.payload.signature;
}
}

return this.provider.send(request.method, request.params);
};

public on = () => {};

public removeAllListeners = (event: string) => {};

private initialize = async () => {
if (this.initialized) {
return;
}

try {
await TrezorConnect.init({
lazyLoad: true,
manifest: {
email: "[email protected]",
appUrl: "https://boltz.exchange",
},
});
} catch (e) {
if (
!(e instanceof Error) ||
e.message !== "TrezorConnect has been already initialized"
) {
log.debug("TrezorConnect already initialized");
return;
}
}

this.initialized = true;
};

private handleError = <T>(
res: Awaited<Response<T>>,
): SuccessWithDevice<T> => {
if (res.success) {
return res as SuccessWithDevice<T>;
}

throw (res as Unsuccessful).payload.error;
};
}

export default TrezorSigner;
Empty file added tests/mocks/TrezorMock.ts
Empty file.

0 comments on commit c4781cd

Please sign in to comment.