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

ledger clear signing upgrades #5966

Merged
merged 9 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 24 additions & 5 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ PODS:
- mobile-wallet-protocol-host (0.1.7):
- CoinbaseWalletSDK/Host
- React-Core
- MultiplatformBleAdapter (0.1.9)
- MultiplatformBleAdapter (0.2.0)
- nanopb (2.30910.0):
- nanopb/decode (= 2.30910.0)
- nanopb/encode (= 2.30910.0)
Expand Down Expand Up @@ -1160,9 +1160,28 @@ PODS:
- React-Core
- react-native-animateable-text (0.12.1):
- React-Core
- react-native-ble-plx (2.0.3):
- MultiplatformBleAdapter (= 0.1.9)
- react-native-ble-plx (3.2.1):
- DoubleConversion
- glog
- hermes-engine
- MultiplatformBleAdapter (= 0.2.0)
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Codegen
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-blur (4.4.0):
- DoubleConversion
- glog
Expand Down Expand Up @@ -2375,7 +2394,7 @@ SPEC CHECKSUMS:
MMKV: 817ba1eea17421547e01e087285606eb270a8dcb
MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9
mobile-wallet-protocol-host: 8ed897dcf4f846d39b35767540e6a695631cab73
MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d
MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d
nanopb: 438bc412db1928dac798aa6fd75726007be04262
PanModal: 421fe72d4af5b7e9016aaa3b4db94a2fb71756d3
Permission-Camera: 9b70902f34a83c10e198d2d01f0e453e58842776
Expand Down Expand Up @@ -2409,7 +2428,7 @@ SPEC CHECKSUMS:
React-Mapbuffer: 9f68550e7c6839d01411ac8896aea5c868eff63a
react-native-aes-crypto: d7e87fd02cee7285983c00957a34063dfc4c94b3
react-native-animateable-text: c95b74a78a13e416e6c4e8b012eea29e14913207
react-native-ble-plx: f10240444452dfb2d2a13a0e4f58d7783e92d76e
react-native-ble-plx: 08539040709361221aa9f8cada60dc730b9168c5
react-native-blur: a2acf22fd7bd13621df5e0b1c130b81adea7009c
react-native-branch: 960c897d57b9f4912b08b9d06a25284b6e9879da
react-native-cameraroll: b5ce04a1ee4081d7eea921918de979f0b41d8e22
Expand Down
368 changes: 184 additions & 184 deletions ios/Rainbow.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,50 @@ if (process.env.CI) {
const rainbowConfig = {
resolver: {
blacklistRE,
resolveRequest: (context, moduleName, platform) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required to resolve ledger packages correctly otherwise it won't find them.

try {
return context.resolveRequest(context, moduleName, platform);
} catch (error) {
console.warn('\n1️⃣ context.resolveRequest cannot resolve: ', moduleName);
}

try {
const resolution = require.resolve(moduleName, {
paths: [path.dirname(context.originModulePath), ...config.resolver.nodeModulesPaths],
});

if (path.isAbsolute(resolution)) {
return {
filePath: resolution,
type: 'sourceFile',
};
}
} catch (error) {
console.warn('\n2️⃣ require.resolve cannot resolve: ', moduleName);
}

try {
return defaultModuleResolver(context, moduleName, platform);
} catch (error) {
console.warn('\n3️⃣ defaultModuleResolver cannot resolve: ', moduleName);
}

try {
return {
filePath: require.resolve(moduleName),
type: 'sourceFile',
};
} catch (error) {
console.warn('\n4️⃣ require.resolve cannot resolve: ', moduleName);
}

try {
const resolution = getDefaultConfig(require.resolve(moduleName)).resolver?.resolveRequest;
return resolution(context, moduleName, platform);
} catch (error) {
console.warn('\n5️⃣ getDefaultConfig cannot resolve: ', moduleName);
}
},
},
transformer,
};
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@
"@gorhom/bottom-sheet": "4.6.1",
"@json-rpc-tools/utils": "1.7.6",
"@lavamoat/preinstall-always-fail": "2.0.0",
"@ledgerhq/hw-app-eth": "6.29.9",
"@ledgerhq/react-native-hw-transport-ble": "6.27.7",
"@ledgerhq/hw-app-eth": "6.39.0",
"@ledgerhq/react-native-hw-transport-ble": "6.33.4",
"@metamask/eth-sig-util": "7.0.2",
"@notifee/react-native": "7.8.2",
"@rainbow-me/provider": "0.1.1",
Expand Down Expand Up @@ -219,7 +219,7 @@
"react-native-actionsheet": "2.4.2",
"react-native-aes-crypto": "rainbow-me/react-native-aes#65c49f7e70266615b2999eaa7db654d3fe4f2e3b",
"react-native-animateable-text": "0.12.1",
"react-native-ble-plx": "2.0.3",
"react-native-ble-plx": "3.2.1",
"react-native-bootsplash": "5.5.3",
"react-native-branch": "5.3.1",
"react-native-change-icon": "4.0.0",
Expand Down
15 changes: 0 additions & 15 deletions patches/@ledgerhq+react-native-hw-transport-ble+6.27.7.patch

This file was deleted.

9 changes: 4 additions & 5 deletions src/handlers/LedgerSigner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

import AppEth, { ledgerService } from '@ledgerhq/hw-app-eth';
import TransportBLE from '@ledgerhq/react-native-hw-transport-ble';
import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util';
import { Signer } from '@ethersproject/abstract-signer';
import { Bytes, hexlify, joinSignature } from '@ethersproject/bytes';
Expand All @@ -14,6 +13,7 @@ import { logger, RainbowError } from '@/logger';
import { Navigation } from '@/navigation';
import Routes from '@/navigation/routesNames';
import { getAddress } from '@ethersproject/address';
import { getEthApp } from '@/utils/ledger';

function waiter(duration: number): Promise<void> {
return new Promise(resolve => {
Expand All @@ -40,10 +40,9 @@ export class LedgerSigner extends Signer {
defineReadOnly(
this,
'_eth',
TransportBLE.open(deviceId).then(
transport => {
const eth = new AppEth(transport);
return eth;
getEthApp(deviceId).then(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using .then vs await to keep it compatible with the Signer interface

ethApp => {
return ethApp;
},
error => {
return Promise.reject(error);
Expand Down
120 changes: 66 additions & 54 deletions src/hooks/useLedgerImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function useLedgerImport({
*/
const handlePairSuccess = useCallback(
(deviceId: string) => {
logger.debug('[useLedgerImport]: Pairing Success', {}, DebugContext.ledger);
logger.debug('[useLedgerImport]: Pairing Success', {});
successCallback?.(deviceId);
handleCleanUp();
},
Expand All @@ -54,61 +54,73 @@ export function useLedgerImport({
*/
const searchAndPair = useCallback(() => {
let currentDeviceId = '';

const newObserver = TransportBLE.observeState({
// havnt seen complete or error fire yet but its in the docs so keeping for reporting purposes
complete: (e: any) => {
logger.debug('[useLedgerImport]: Observer complete', { e }, DebugContext.ledger);
},
error: (e: any) => {
logger.debug('[useLedgerImport]: Observer error ', { e }, DebugContext.ledger);
},
next: async (e: any) => {
// App is not authorized to use Bluetooth
if (e.type === 'Unauthorized') {
logger.debug('[useLedgerImport]: Bluetooth Unauthorized', {}, DebugContext.ledger);
if (IS_IOS) {
await showBluetoothPermissionsAlert();
} else {
await checkAndRequestAndroidBluetooth();
logger.debug('[useLedgerImport]: searchAndPair', {});
try {
const newObserver = TransportBLE.observeState({
// havnt seen complete or error fire yet but its in the docs so keeping for reporting purposes
complete: () => {
logger.debug('[useLedgerImport]: Observer complete', {});
},
error: (e: any) => {
logger.debug('[useLedgerImport]: Observer error ', { e });
},
next: async (e: any) => {
// App is not authorized to use Bluetooth
if (e.type === 'Unauthorized') {
logger.debug('[useLedgerImport]: Bluetooth Unauthorized', {});
if (IS_IOS) {
await showBluetoothPermissionsAlert();
return;
} else {
await checkAndRequestAndroidBluetooth();
return;
}
}
}
// Bluetooth is turned off
if (e.type === 'PoweredOff') {
logger.debug('[useLedgerImport]: Bluetooth Powered Off', {}, DebugContext.ledger);
await showBluetoothPoweredOffAlert();
}
if (e.available) {
const newListener = TransportBLE.listen({
complete: () => {},
error: error => {
logger.error(new RainbowError('[useLedgerImport]: Error Pairing'), { errorMessage: (error as Error).message });
},
next: async e => {
if (e.type === 'add') {
const device = e.descriptor;
// prevent duplicate alerts
if (currentDeviceId === device.id) {
return;
}
// set the current device id to prevent duplicate alerts
currentDeviceId = device.id;
// Bluetooth is turned off
if (e.type === 'PoweredOff') {
logger.debug('[useLedgerImport]: Bluetooth Powered Off', {});
await showBluetoothPoweredOffAlert();
return;
}
if (e.available) {
const newListener = TransportBLE.listen({
complete: () => {
logger.debug('[useLedgerImport]: TransportBLE.listen complete', {});
},
error: error => {
logger.error(new RainbowError('[useLedgerImport]: Error Pairing'), { errorMessage: (error as Error).message });
handlePairError(e);
},
next: async e => {
logger.debug('[useLedgerImport]: TransportBLE.listen next', { e });

try {
const transport = await TransportBLE.open(device.id);
if (e.type === 'add') {
const device = e.descriptor;
// prevent duplicate alerts
if (currentDeviceId === device.id) {
logger.debug('[useLedgerImport]: TransportBLE.listen next dupe', { deviceId: device.id });
return;
}
// set the current device id to prevent duplicate alerts
currentDeviceId = device.id;

logger.debug('[useLedgerImport]: TransportBLE.listen next paired successfully', { deviceId: device.id });
handlePairSuccess(device.id);
} catch (e) {
handlePairError(e as Error);
currentDeviceId === '';
} else {
logger.debug('[useLedgerImport]: TransportBLE.listen next not paired', { e });
handlePairError(e);
}
}
},
});
listener.current = newListener;
}
},
});
observer.current = newObserver;
},
});
listener.current = newListener;
}
},
});
observer.current = newObserver;
} catch (e) {
logger.error(new RainbowError('[useLedgerImport]: Error Pairing'), { errorMessage: (e as Error).message });
handlePairError(e as Error);
}
}, [handlePairError, handlePairSuccess]);

/**
Expand All @@ -118,10 +130,10 @@ export function useLedgerImport({

useEffect(() => {
const asyncFn = async () => {
logger.debug('[useLedgerImport]: init device polling', {}, DebugContext.ledger);
logger.debug('[useLedgerImport]: init device polling', {});

const isBluetoothEnabled = IS_ANDROID ? await checkAndRequestAndroidBluetooth() : true;
logger.debug('[useLedgerImport]: bluetooth enabled? ', { isBluetoothEnabled }, DebugContext.ledger);
logger.debug('[useLedgerImport]: bluetooth enabled? ', { isBluetoothEnabled });

if (isBluetoothEnabled) {
searchAndPair();
Expand Down
13 changes: 3 additions & 10 deletions src/model/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,7 @@ import { findWalletWithAccount } from '@/helpers/findWalletWithAccount';
import { EthereumAddress } from '@/entities';
import { authenticateWithPIN, authenticateWithPINAndCreateIfNeeded } from '@/handlers/authentication';
import { saveAccountEmptyState } from '@/handlers/localstorage/accountLocal';
import {
addHexPrefix,
isHexString,
isHexStringIgnorePrefix,
isValidBluetoothDeviceId,
isValidMnemonic,
web3Provider,
} from '@/handlers/web3';
import { addHexPrefix, isHexString, isHexStringIgnorePrefix, isValidBluetoothDeviceId, isValidMnemonic } from '@/handlers/web3';
import { createSignature } from '@/helpers/signingWallet';
import showWalletErrorAlert from '@/helpers/support';
import walletTypes, { EthereumWalletType } from '@/helpers/walletTypes';
Expand Down Expand Up @@ -300,11 +293,11 @@ export const loadWallet = async <S extends Screen>({
if (isHardwareWalletKey(privateKey)) {
const index = privateKey?.split('/')[1];
const deviceId = privateKey?.split('/')[0];
if (typeof index !== undefined && provider && deviceId) {
if (typeof index !== undefined && deviceId && provider) {
return new LedgerSigner(provider, getHdPath({ type: WalletLibraryType.ledger, index: Number(index) }), deviceId);
}
} else if (privateKey) {
return new Wallet(privateKey, provider || web3Provider);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the line I mentioned before. Now the provider defaults to mainnet instead of being undefined.

return new Wallet(privateKey, provider);
}
if (ios && showErrorIfNotLoaded) {
showWalletErrorAlert();
Expand Down
4 changes: 2 additions & 2 deletions src/navigation/HardwareWalletTxNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ export const HardwareWalletTxNavigator = () => {
);

const successCallback = useCallback(() => {
logger.debug('[HardwareWalletTxNavigator]: submitting tx', {}, DebugContext.ledger);
logger.debug('[HardwareWalletTxNavigator]: submitting tx', {});
if (!isReady) {
setReadyForPolling(false);
setIsReady(true);
setHardwareTXError(false);
submit();
} else {
logger.debug('[HardwareWalletTxNavigator]: already submitted', {}, DebugContext.ledger);
logger.debug('[HardwareWalletTxNavigator]: already submitted', {});
}
}, [isReady, setIsReady, setReadyForPolling, submit]);

Expand Down
3 changes: 1 addition & 2 deletions src/screens/SendSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export default function SendSheet() {
const [debouncedRecipient] = useDebounce(recipient, 500);

const [isValidAddress, setIsValidAddress] = useState(!!recipientOverride);
const [currentProvider, setCurrentProvider] = useState<StaticJsonRpcProvider | undefined>();
const [currentProvider, setCurrentProvider] = useState<StaticJsonRpcProvider | undefined>(getProvider({ chainId: ChainId.mainnet }));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider now defaulting to mainnet. Before it was undefined (breaking ledger mainnet sends)

const theme = useTheme();
const { colors, isDarkMode } = theme;

Expand Down Expand Up @@ -403,7 +403,6 @@ export default function SendSheet() {
const onSubmit = useCallback(
async ({ ens }: OnSubmitProps = {}) => {
if (!selected) return;

const wallet = await performanceTracking.getState().executeFn({
fn: loadWallet,
operation: TimeToSignOperation.KeychainRead,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ export function PairHardwareWalletSigningSheet() {
const importHardwareWallet = useCallback(
async (deviceId: string) => {
if (busy) {
logger.debug('[PairHardwareWalletSigningSheet]: busy, already trying to import', { deviceId }, DebugContext.ledger);
logger.debug('[PairHardwareWalletSigningSheet]: busy, already trying to import', { deviceId });
return;
}
logger.debug('[PairHardwareWalletSigningSheet]: importing Hardware Wallet', { deviceId }, DebugContext.ledger);
logger.debug('[PairHardwareWalletSigningSheet]: importing Hardware Wallet', { deviceId });
handleSetSeedPhrase(deviceId);
handlePressImportButton({
forceAddress: deviceId,
Expand Down
Loading
Loading