Skip to content

Commit

Permalink
feat: add ethereum for ledger
Browse files Browse the repository at this point in the history
  • Loading branch information
RyukTheCoder authored and yeager-eren committed Apr 24, 2024
1 parent 1424015 commit 084aae2
Show file tree
Hide file tree
Showing 14 changed files with 848 additions and 417 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,8 @@
},
"resolutions": {
"react-refresh": "0.9.0"
},
"@parcel/resolver-default": {
"packageExports": true
}
}
3 changes: 2 additions & 1 deletion wallets/provider-all/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@rango-dev/provider-halo": "^0.31.0",
"@rango-dev/provider-keplr": "^0.31.0",
"@rango-dev/provider-leap-cosmos": "^0.31.0",
"@rango-dev/provider-ledger": "^0.1.0",
"@rango-dev/provider-math-wallet": "^0.31.0",
"@rango-dev/provider-metamask": "^0.31.0",
"@rango-dev/provider-okx": "^0.31.0",
Expand All @@ -53,4 +54,4 @@
"publishConfig": {
"access": "public"
}
}
}
2 changes: 2 additions & 0 deletions wallets/provider-all/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as frontier from '@rango-dev/provider-frontier';
import * as halo from '@rango-dev/provider-halo';
import * as keplr from '@rango-dev/provider-keplr';
import * as leapCosmos from '@rango-dev/provider-leap-cosmos';
import * as ledger from '@rango-dev/provider-ledger';
import * as mathwallet from '@rango-dev/provider-math-wallet';
import * as metamask from '@rango-dev/provider-metamask';
import * as okx from '@rango-dev/provider-okx';
Expand Down Expand Up @@ -61,5 +62,6 @@ export const allProviders = (enviroments?: Enviroments) => {
frontier,
taho,
braavos,
ledger,
];
};
Empty file.
33 changes: 33 additions & 0 deletions wallets/provider-ledger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@rango-dev/provider-ledger",
"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-ledger",
"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": {
"@ledgerhq/hw-app-eth": "^6.35.7",
"@ledgerhq/hw-transport-webhid": "^6.28.5",
"@rango-dev/wallets-shared": "^0.31.0",
"ethers": "^6.11.1",
"rango-types": "^0.1.59"
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions wallets/provider-ledger/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @rango-dev/provider-ledger
71 changes: 71 additions & 0 deletions wallets/provider-ledger/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type Transport from '@ledgerhq/hw-transport';

const ETHEREUM_CHAIN_ID = '0x1';

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

const ledgerErrorMessages: { [statusCode: number | string]: string } = {
21781: 'The device is locked',
25871: 'Related application is not ready on your device',
27013: 'Action denied by user',
INSUFFICIENT_FUNDS: 'Insufficient funds for transaction',
};

export function getLedgerInstance() {
/*
* 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.
*/
return { chainId: ETHEREUM_CHAIN_ID };
}

export async function getLedgerAccounts(): Promise<{
accounts: string[];
chainId: string;
}> {
try {
const transport = await transportConnect();

const eth = new (await import('@ledgerhq/hw-app-eth')).default(transport);

const accounts: string[] = [];

const result = await eth.getAddress(ETH_BIP32_PATH, false, true);
accounts.push(result.address);

return {
accounts: accounts,
chainId: ETHEREUM_CHAIN_ID,
};
} catch (error: any) {
throw getLedgerError(error);
} finally {
await transportDisconnect();
}
}

export function getLedgerError(error: any) {
const errorCode = error?.statusCode || error?.code; // ledger error || broadcast error

if (errorCode && !!ledgerErrorMessages[errorCode]) {
return new Error(ledgerErrorMessages[errorCode]);
}
return error;
}

let transportConnection: Transport | null = null;

export async function transportConnect() {
transportConnection = await (
await import('@ledgerhq/hw-transport-webhid')
).default.create();

return transportConnection;
}

export async function transportDisconnect() {
if (transportConnection) {
await transportConnection.close();
transportConnection = null;
}
}
52 changes: 52 additions & 0 deletions wallets/provider-ledger/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type {
Connect,
Disconnect,
WalletInfo,
} from '@rango-dev/wallets-shared';
import type { BlockchainMeta, SignerFactory } from 'rango-types';

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

import {
getLedgerAccounts,
getLedgerInstance,
transportDisconnect,
} from './helpers';
import signer from './signer';

const WALLET = WalletTypes.LEDGER;

export const config = {
type: WALLET,
};

export const getInstance = getLedgerInstance;
export const connect: Connect = async () => {
const ledgerAccounts = await getLedgerAccounts();

return ledgerAccounts;
};

export const disconnect: Disconnect = async () => {
void transportDisconnect();
};

export const getSigners: (provider: any) => SignerFactory = signer;

export const getWalletInfo: (allBlockChains: BlockchainMeta[]) => WalletInfo = (
allBlockChains
) => {
const ethereumBlockchain = allBlockChains.find(
(chain) => chain.name === Networks.ETHEREUM
);
return {
name: 'Ledger',
img: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/ledger/icon.svg',
installLink: {
DEFAULT:
'https://support.ledger.com/hc/en-us/articles/4404389606417-Download-and-install-Ledger-Live?docs=true',
},
color: 'black',
supportedChains: ethereumBlockchain ? [ethereumBlockchain] : [],
};
};
11 changes: 11 additions & 0 deletions wallets/provider-ledger/src/signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { SignerFactory } from 'rango-types';

import { DefaultSignerFactory, TransactionType as TxType } from 'rango-types';

import { EthereumSigner } from './signers/ethereum';

export default function getSigners(): SignerFactory {
const signers = new DefaultSignerFactory();
signers.registerSigner(TxType.EVM, new EthereumSigner());
return signers;
}
80 changes: 80 additions & 0 deletions wallets/provider-ledger/src/signers/ethereum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { TransactionLike } from 'ethers';
import type { GenericSigner } from 'rango-types';
import type { EvmTransaction } from 'rango-types/lib/api/main';

import { JsonRpcProvider, Transaction } from 'ethers';
import { SignerError } from 'rango-types';

import {
ETH_BIP32_PATH,
getLedgerError,
transportConnect,
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
throw SignerError.UnimplementedError('signMessage');
}

async signAndSendTx(
tx: EvmTransaction,
fromAddress: string,
chainId: string | null
): Promise<{ hash: string }> {
try {
const provider = new JsonRpcProvider(RPC_PROVIDER_URL); // Provider to broadcast transaction

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

const transaction: TransactionLike<string> = {
to: tx.to,
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
nonce: transactionCount,
chainId: chainId,
data: tx.data,
value: tx.value,
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
maxFeePerGas: tx.maxFeePerGas,
};

const unsignedTx =
Transaction.from(transaction).unsignedSerialized.substring(2); // Create unsigned transaction

const resolution = await (
await import('@ledgerhq/hw-app-eth')
).ledgerService.resolveTransaction(unsignedTx, {}, {}); // metadata necessary to allow the device to clear sign information

const transport = await transportConnect();

const eth = new (await import('@ledgerhq/hw-app-eth')).default(transport);

const signature = await eth.signTransaction(
ETH_BIP32_PATH,
unsignedTx,
resolution
);

const signedTx = Transaction.from({
...transaction,
signature: {
r: '0x' + signature.r,
s: '0x' + signature.s,
v: parseInt(signature.v),
},
}).serialized;

const broadcastResult = await provider.broadcastTransaction(signedTx);

return { hash: broadcastResult.hash };
} catch (error) {
throw getLedgerError(error);
} finally {
await transportDisconnect();
}
}
}
11 changes: 11 additions & 0 deletions wallets/provider-ledger/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
// see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
"extends": "../../tsconfig.lib.json",
"include": ["src", "types", "../../global-wallets-env.d.ts"],
"compilerOptions": {
"outDir": "dist",
"rootDir": "./src",
"lib": ["dom", "esnext"]
// match output dir to input dir. e.g. dist/index instead of dist/src/index
}
}
1 change: 1 addition & 0 deletions wallets/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ For better user experience, wallet provider tries to connect to a wallet only wh
| Halo | - | - | &cross; | https://halo.social/ |
| Keplr | Cosmos | - | &cross; | https://www.keplr.app/ |
| Leap Cosmos | Cosmos | Cosmos | &cross; | https://www.leapwallet.io/cosmos |
| Ledger | Ethereum |- | &cross; | https://www.ledger.com/ |
| Math Wallet | BTC,EVM,Solana,Aptos,Tron,Polkadot,Cosmos | BTC,Aptos,Tron,Polkadot,Cosmos | &check; | https://mathwallet.org/en-us/ |
| Metamask | EVM | - | &check; | - |
| OKX | EVM,Solana,Cosmos | Cosmos | &check; | https://www.okx.com/web3 |
Expand Down
1 change: 1 addition & 0 deletions wallets/shared/src/rango.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export enum WalletTypes {
TAHO = 'taho',
MY_TON_WALLET = 'mytonwallet',
SOLFLARE_SNAP = 'solflare-snap',
LEDGER = 'ledger',
}

export enum Networks {
Expand Down
Loading

0 comments on commit 084aae2

Please sign in to comment.