Skip to content

Commit

Permalink
feat: add support for Trezor hardware wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
mikasackermn authored and yeager-eren committed Jul 7, 2024
1 parent 26d254e commit 6edecbb
Show file tree
Hide file tree
Showing 35 changed files with 1,303 additions and 58 deletions.
10 changes: 8 additions & 2 deletions examples/queue-manager-demo/src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ export interface Configs {
API_KEY: string;
}

// this API key is limited and
// it is only for test purpose
/*
* this API key is limited and
* it is only for test purpose
*/
const RANGO_PUBLIC_API_KEY = 'c6381a79-2817-4602-83bf-6a641a409e32';
export const WC_PROJECT_ID = 'e24844c5deb5193c1c14840a7af6a40b';
export const TREZOR_MANIFEST = {
appUrl: 'https://widget.rango.exchange/',
email: '[email protected]',
};

let configs: Configs = {
API_KEY: RANGO_PUBLIC_API_KEY,
Expand Down
17 changes: 10 additions & 7 deletions examples/queue-manager-demo/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import React, { useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { Events, Provider } from '@rango-dev/wallets-react';
import type { WalletType } from '@rango-dev/wallets-shared';

import { allProviders } from '@rango-dev/provider-all';
import { Events, Provider } from '@rango-dev/wallets-react';
import { RangoClient } from 'rango-sdk';
import React, { useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';

import { App } from './App';
import { WalletType } from '@rango-dev/wallets-shared';
import { WC_PROJECT_ID } from './configs';
import { TREZOR_MANIFEST, WC_PROJECT_ID } from './configs';

const providers = allProviders({
walletconnect2: {
WC_PROJECT_ID: WC_PROJECT_ID,
},
trezorManifest: TREZOR_MANIFEST,
});

function AppContainer() {
Expand All @@ -26,12 +29,12 @@ function AppContainer() {
try {
const res = await client.getAllMetadata();
setBlockChains(res.blockchains);
} catch (e) {
} catch (e: any) {
setError(e.message);
}
setLoading(false);
};
getAllBlockchains();
void getAllBlockchains();
}, []);

return (
Expand Down
3 changes: 2 additions & 1 deletion examples/wallets-adapter-demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { RangoClient } from 'rango-sdk';
import React, { useEffect, useState } from 'react';

import WalletsModal from './components/WalletsModal';
import { WC_PROJECT_ID } from './constants';
import { TREZOR_MANIFEST, WC_PROJECT_ID } from './constants';

const providers = allProviders({
walletconnect2: {
WC_PROJECT_ID: WC_PROJECT_ID,
},
trezorManifest: TREZOR_MANIFEST,
});

export function App() {
Expand Down
4 changes: 4 additions & 0 deletions examples/wallets-adapter-demo/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export const WC_PROJECT_ID = 'e24844c5deb5193c1c14840a7af6a40b';
export const TREZOR_MANIFEST = {
appUrl: 'https://widget.rango.exchange/',
email: '[email protected]',
};
3 changes: 2 additions & 1 deletion examples/wallets-demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { RangoClient } from 'rango-sdk';
import React, { useEffect, useState } from 'react';

import List from './components/List';
import { WC_PROJECT_ID } from './constants';
import { TREZOR_MANIFEST, WC_PROJECT_ID } from './constants';

const providers = allProviders({
walletconnect2: {
WC_PROJECT_ID: WC_PROJECT_ID,
},
trezorManifest: TREZOR_MANIFEST,
});

export function App() {
Expand Down
3 changes: 2 additions & 1 deletion examples/wallets-demo/src/components/List/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useWallets } from '@rango-dev/wallets-react';
import { sortWalletsBasedOnState } from '@rango-dev/wallets-shared';
import React from 'react';

import { WC_PROJECT_ID } from '../../constants';
import { TREZOR_MANIFEST, WC_PROJECT_ID } from '../../constants';

import Item from './Item';

Expand All @@ -16,6 +16,7 @@ function List({ tokens }: { tokens: Token[] }) {
const { state, getWalletInfo } = useWallets();
const providerTypes = allProviders({
walletconnect2: { WC_PROJECT_ID: WC_PROJECT_ID },
trezorManifest: TREZOR_MANIFEST,
}).map((p) => p.config.type);
const allWallets = sortWalletsBasedOnState(
providerTypes.map((type) => {
Expand Down
5 changes: 5 additions & 0 deletions examples/wallets-demo/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export const WC_PROJECT_ID = 'e24844c5deb5193c1c14840a7af6a40b';
export const TREZOR_MANIFEST = {
appUrl: 'https://widget.rango.exchange/',
email: '[email protected]',
};

export const swaps = {
EVM: {
from: 'BSC.USDC.0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d',
Expand Down
3 changes: 2 additions & 1 deletion wallets/provider-all/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@rango-dev/provider-taho": "^0.34.1-next.5",
"@rango-dev/provider-tokenpocket": "^0.34.1-next.5",
"@rango-dev/provider-tomo": "^0.1.1-next.2",
"@rango-dev/provider-trezor": "^0.1.0",
"@rango-dev/provider-tron-link": "^0.34.1-next.4",
"@rango-dev/provider-trustwallet": "^0.34.1-next.5",
"@rango-dev/provider-walletconnect-2": "^0.27.1-next.6",
Expand All @@ -58,4 +59,4 @@
"publishConfig": {
"access": "public"
}
}
}
24 changes: 13 additions & 11 deletions wallets/provider-all/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { ProviderInterface } from '@rango-dev/wallets-react';
import type { WalletType } from '@rango-dev/wallets-shared';
import type { WalletType, WalletTypes } from '@rango-dev/wallets-shared';

import { WalletTypes } from '@rango-dev/wallets-shared';

export const isWalletConnectExcluded = (
selectedProviders?: (WalletType | ProviderInterface)[]
) =>
selectedProviders &&
!selectedProviders.find((provider) =>
typeof provider === 'string'
? provider === WalletTypes.WALLET_CONNECT_2
: provider.getWalletInfo([]).name === 'WalletConnect'
export const isWalletExcluded = (
providers: (WalletType | ProviderInterface)[],
wallet: { name: string; type: WalletTypes }
) => {
return (
providers.length &&
!providers.find((provider) =>
typeof provider === 'string'
? provider === wallet.type
: provider.getWalletInfo([]).name === wallet.name
)
);
};
28 changes: 25 additions & 3 deletions wallets/provider-all/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Environments as TrezorEnvironments } from '@rango-dev/provider-trezor';
import type { Environments as WalletConnectEnvironments } from '@rango-dev/provider-walletconnect-2';
import type { ProviderInterface } from '@rango-dev/wallets-react';
import type { WalletType } from '@rango-dev/wallets-shared';

import * as argentx from '@rango-dev/provider-argentx';
import * as bitget from '@rango-dev/provider-bitget';
Expand Down Expand Up @@ -29,20 +29,30 @@ import * as solflareSnap from '@rango-dev/provider-solflare-snap';
import * as taho from '@rango-dev/provider-taho';
import * as tokenpocket from '@rango-dev/provider-tokenpocket';
import * as tomo from '@rango-dev/provider-tomo';
import * as trezor from '@rango-dev/provider-trezor';
import * as tronLink from '@rango-dev/provider-tron-link';
import * as trustwallet from '@rango-dev/provider-trustwallet';
import * as walletconnect2 from '@rango-dev/provider-walletconnect-2';
import * as xdefi from '@rango-dev/provider-xdefi';
import { type WalletType, WalletTypes } from '@rango-dev/wallets-shared';

import { isWalletConnectExcluded } from './helpers';
import { isWalletExcluded } from './helpers';

interface Options {
walletconnect2: WalletConnectEnvironments;
selectedProviders?: (WalletType | ProviderInterface)[];
trezor?: TrezorEnvironments;
}

export const allProviders = (options?: Options) => {
if (!isWalletConnectExcluded(options?.selectedProviders)) {
const providers = options?.selectedProviders || [];

if (
!isWalletExcluded(providers, {
type: WalletTypes.WALLET_CONNECT_2,
name: 'WalletConnect',
})
) {
if (!!options?.walletconnect2?.WC_PROJECT_ID) {
walletconnect2.init(options.walletconnect2);
} else {
Expand All @@ -52,6 +62,17 @@ export const allProviders = (options?: Options) => {
}
}

if (
!isWalletExcluded(providers, {
type: WalletTypes.TREZOR,
name: 'Trezor',
})
) {
if (!!options?.trezor?.manifest) {
trezor.init(options.trezor);
}
}

return [
safe,
defaultInjected,
Expand Down Expand Up @@ -84,5 +105,6 @@ export const allProviders = (options?: Options) => {
braavos,
ledger,
rabby,
trezor,
];
};
4 changes: 1 addition & 3 deletions wallets/provider-ledger/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import type Transport from '@ledgerhq/hw-transport';

import { getAltStatusMessage } from '@ledgerhq/errors';
import { Networks } from '@rango-dev/wallets-shared';
import { ETHEREUM_CHAIN_ID, Networks } from '@rango-dev/wallets-shared';
import bs58 from 'bs58';

const ETHEREUM_CHAIN_ID = '0x1';

export const ETH_BIP32_PATH = "44'/60'/0'/0/0";
export const SOLANA_BIP32_PATH = "44'/501'/0'";

Expand Down
5 changes: 2 additions & 3 deletions wallets/provider-ledger/src/signers/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { TransactionLike } from 'ethers';
import type { GenericSigner } from 'rango-types';
import type { EvmTransaction } from 'rango-types/lib/api/main';

import { DEFAULT_ETHEREUM_RPC_URL } from '@rango-dev/wallets-shared';
import { JsonRpcProvider, Transaction } from 'ethers';
import { SignerError } from 'rango-types';

Expand All @@ -12,8 +13,6 @@ import {
transportDisconnect,
} from '../helpers';

export const RPC_PROVIDER_URL = 'https://rpc.ankr.com/eth';

export class EthereumSigner implements GenericSigner<EvmTransaction> {
async signMessage(): Promise<string> {
// TODO: Should be implemented using eth.signPersonalMessage
Expand All @@ -26,7 +25,7 @@ export class EthereumSigner implements GenericSigner<EvmTransaction> {
chainId: string | null
): Promise<{ hash: string }> {
try {
const provider = new JsonRpcProvider(RPC_PROVIDER_URL); // Provider to broadcast transaction
const provider = new JsonRpcProvider(DEFAULT_ETHEREUM_RPC_URL); // Provider to broadcast transaction

const transactionCount = await provider.getTransactionCount(fromAddress); // Get nonce

Expand Down
33 changes: 33 additions & 0 deletions wallets/provider-trezor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@rango-dev/provider-trezor",
"version": "0.1.0",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
"main": "./dist/index.js",
"exports": {
".": "./dist/index.js"
},
"typings": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"scripts": {
"build": "node ../../scripts/build/command.mjs --path wallets/provider-trezor",
"ts-check": "tsc --declaration --emitDeclarationOnly -p ./tsconfig.json",
"clean": "rimraf dist",
"format": "prettier --write '{.,src}/**/*.{ts,tsx}'",
"lint": "eslint \"**/*.{ts,tsx}\" --ignore-path ../../.eslintignore"
},
"dependencies": {
"@rango-dev/wallets-shared": "^0.34.1-next.4",
"rango-types": "^0.1.59",
"@trezor/connect-web": "^9.2.4",
"ethers": "^6.11.1",
"@rango-dev/signer-evm": "^0.27.2-next.1"
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions wallets/provider-trezor/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @rango-dev/provider-trezor
58 changes: 58 additions & 0 deletions wallets/provider-trezor/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { cleanEvmError } from '@rango-dev/signer-evm';
import { ETHEREUM_CHAIN_ID, Networks } from '@rango-dev/wallets-shared';

export const ETHEREUM_BIP32_PATH = "m/44'/60'/0'/0/0";

export function getTrezorErrorMessage(error: any) {
if (error?.shortMessage) {
/*
* Some error signs have lengthy, challenging-to-read messages.
* shortMessage is used because it is shorter and easier to understand.
*/
return new Error(error.shortMessage, { cause: error });
}
return cleanEvmError(error);
}

export function getTrezorInstance() {
/*
* Instances have a required property which is `chainId` and is using in swap execution.
* Here we are setting it as Ethereum always since we are supporting only eth for now.
*/
const instances = new Map();

instances.set(Networks.ETHEREUM, { chainId: ETHEREUM_CHAIN_ID });

return instances;
}

export async function getEthereumAccounts(path: string): Promise<{
accounts: string[];
chainId: string;
}> {
const { default: TrezorConnect } = await import('@trezor/connect-web');
const result = await TrezorConnect.ethereumGetAddress({
path,
});

if (!result.success) {
throw new Error(result.payload.error);
}

return {
accounts: [result.payload.address],
chainId: ETHEREUM_CHAIN_ID,
};
}

/*
* Using BigInt in the valueToHex function ensures that the function
* can handle very large integer values that exceed the range of standard JavaScript number types.
*/
export const valueToHex = (value: string) => {
const ZERO_BIGINT = BigInt(0);
const HEX_BASE = 16;
return BigInt(value) > ZERO_BIGINT
? `0x${BigInt(value).toString(HEX_BASE)}`
: '0x0';
};
Loading

0 comments on commit 6edecbb

Please sign in to comment.