Skip to content

Commit

Permalink
GridPlus integration (#4183)
Browse files Browse the repository at this point in the history
* Add basic WIP integration to test with

* Setup SignTx component

* Fix a few small issues

* Fix wrong wallet id

* Add connections slice

* Move HD wallet session to Redux

* Use saved credentials if possible

* Use saved credentials for signing

* Use default ETH path for testnets with GridPlus

* Fix some types and tests

* Fix wrong import

* Merge legacy HW components

* Use saved credentials for legacy flow

* Update copy and add icon

* Add action to grab wallet connection

* Fix more copy and layout

* Dispatch connectWallet more often

* Add GridPlus illustration

* Remove accidental commit + unused copy

* Unify Hardware sign with Web3 sign page

* Update wallets lib

* Add tests

* Fix circular dep issue

* Add more timeout

* Try to fix tests on CI

* Fix E2E

* Detect if tx was signed by wrong account

* Fix some PR comments

* Reduce code duplication for HW scanning

* Fix PR comments and tests

* Update copy and fix tests

* Add unlock test for HWLegacy

* Use ETH path for every network using GridPlus

* Bump wallets lib

* Add type guard

* Update DPaths
  • Loading branch information
FrederikBolding authored Dec 2, 2021
1 parent 92051ca commit e9b5c88
Show file tree
Hide file tree
Showing 64 changed files with 1,545 additions and 792 deletions.
2 changes: 1 addition & 1 deletion __tests__/export.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ test('Can export AppState to file', async (t) => {
const accountSettingsLens = lensPath(['settings']);

const omitDynamicValues = pipe(
omit(['mtime', 'promoPoaps', 'notifications']),
omit(['mtime', 'promoPoaps', 'notifications', 'connections']),
set(accountAssetsLens, removeKeysFromAccountAsset),
set(accountSettingsLens, removeKeysFromAccountSettings)
);
Expand Down
101 changes: 101 additions & 0 deletions jest_config/__mocks__/gridplus-sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { arrayify, splitSignature } from '@ethersproject/bytes';
import { HDNode } from '@ethersproject/hdnode';
import type { Transaction } from '@ethersproject/transactions';
import { parse } from '@ethersproject/transactions';
import { Wallet } from '@ethersproject/wallet';
import { getPathPrefix, toChecksumAddress } from '@mycrypto/wallets';
import type {
AddressesOpts,
SignMessageOpts,
SignOpts,
SignResult,
SignTxOpts
} from 'gridplus-sdk';

import { stripHexPrefix } from '@utils';

const hdNode = HDNode.fromMnemonic('test test test test test test test test test test test ball');

const convertPathToString = (path: number[]): string =>
path
.map((p) => {
const withoutHardening = p - 0x80000000;
const index = withoutHardening >= 0 ? Math.min(p, withoutHardening) : p;
const isHardened = index === withoutHardening;
return `${index}${isHardened ? "'" : ''}`;
})
.join('/');

export class Client {
isPaired = false;
hasActiveWallet = jest.fn().mockReturnValue(true);
connect = jest
.fn()
.mockImplementation(
(_deviceID: string, callback: (err: Error | null, isPaired: boolean) => void) => {
this.isPaired = true;
callback(null, true);
}
);
sign = jest
.fn()
.mockImplementation(
async (opts: SignOpts, callback: (err: Error | null, data: SignResult) => void) => {
const path = convertPathToString(opts.data.signerPath);
const childNode = hdNode.derivePath(path);
const wallet = new Wallet(childNode.privateKey);
if (opts.currency === 'ETH') {
const { signerPath, chainId, ...transaction } = opts.data as SignTxOpts;

const isEIP1559 =
transaction.maxFeePerGas !== undefined &&
transaction.maxPriorityFeePerGas !== undefined;

const signedTransaction = await wallet.signTransaction({
...transaction,
chainId: parseInt((chainId as unknown) as string, 16),
type: isEIP1559 ? 2 : 0
});
const { v, r, s } = parse(signedTransaction) as Required<Transaction>;
callback(null, {
sig: {
// eslint-disable-next-line no-restricted-globals
v: v === 0 ? Buffer.from([]) : Buffer.from([v]),
r: stripHexPrefix(r),
s: stripHexPrefix(s)
}
});
} else if (opts.currency === 'ETH_MSG') {
const signMessageOpts = opts.data as SignMessageOpts;
const signature = await wallet.signMessage(arrayify(signMessageOpts.payload));
const { v, r, s } = splitSignature(signature);

callback(null, {
sig: {
// eslint-disable-next-line no-restricted-globals
v: v === 0 ? Buffer.from([]) : Buffer.from([v]),
r: stripHexPrefix(r),
s: stripHexPrefix(s)
}
});
}
}
);
getAddresses = jest
.fn()
.mockImplementation(
(opts: AddressesOpts, callback: (err: Error | null, data: string[]) => void) => {
const path = convertPathToString(opts.startPath);
const indices = path.split('/');
const offset = parseInt(indices[indices.length - 1]);

const masterNode = hdNode.derivePath(getPathPrefix(path));
const result = new Array(opts.n).fill(undefined).map((_, i) => {
const index = offset + i;
const node = masterNode.derivePath(index.toString(10));
return toChecksumAddress(node.address);
});
callback(null, result);
}
);
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@mycrypto/eth-scan": "3.4.4",
"@mycrypto/ui": "0.24.1",
"@mycrypto/unlock-scan": "1.2.0",
"@mycrypto/wallets": "1.3.4",
"@mycrypto/wallets": "1.4.2",
"@reduxjs/toolkit": "1.6.0",
"@styled-system/should-forward-prop": "5.1.5",
"@types/papaparse": "5.0.6",
Expand Down Expand Up @@ -267,4 +267,4 @@
"pre-push": "yarn test"
}
}
}
}
18 changes: 18 additions & 0 deletions src/assets/images/wallets/gridplus-large.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/images/wallets/gridplus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions src/components/BusyBottom/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ export const configs: Record<
},
SUPPORT_LINK
],
GRIDPLUS: [
{
copy: 'BUSY_BOTTOM_GRIDPLUS_1',
link: EXT_URLS.GRIDPLUS_REFERRAL.url,
external: true
},
// @todo Add article for this?
/**{
copy: 'BUSY_BOTTOM_TROUBLESHOOTING',
link: getKBHelpArticle(KB_HELP_ARTICLE.TREZOR_TROUBLESHOOTING),
external: true
},**/
SUPPORT_LINK
],
WALLETCONNECT: [
{
copy: 'BUSY_BOTTOM_WALLETCONNECT_1',
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ import membership from '@assets/images/membership/membership-none.svg';
import nodeLogo from '@assets/images/node-logo.svg';
import repLogo from '@assets/images/rep-logo.svg';
import uniLogo from '@assets/images/uni-logo.png';
import gridPlusLgIcon from '@assets/images/wallets/gridplus-large.svg';
import ledgerIcon from '@assets/images/wallets/ledger.svg';
import trezorIcon from '@assets/images/wallets/trezor.svg';

Expand Down Expand Up @@ -178,6 +179,7 @@ const svgIcons = {
'ledger-icon-lg': ledgerLgIcon,
'trezor-icon': trezorIcon,
'trezor-icon-lg': trezorLgIcon,
'gridplus-icon-lg': gridPlusLgIcon,
membership,
feedback,
newsletter,
Expand Down
23 changes: 23 additions & 0 deletions src/components/SignTransactionWallets/GridPlus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TIcon } from '@components/Icon';
import { HARDWARE_CONFIG } from '@config';
import { translateRaw } from '@translations';
import { ISignComponentProps, WalletId } from '@types';

import HardwareSignTransaction from './Hardware';

export default function SignTransactionGridPlus({
senderAccount,
rawTransaction,
onSuccess
}: ISignComponentProps) {
const walletIconType = HARDWARE_CONFIG[WalletId.GRIDPLUS].iconId as TIcon;
return (
<HardwareSignTransaction
signerDescription={translateRaw('SIGN_TX_GRIDPLUS_DESCRIPTION')}
walletIconType={walletIconType}
senderAccount={senderAccount}
rawTransaction={rawTransaction}
onSuccess={onSuccess}
/>
);
}
4 changes: 2 additions & 2 deletions src/components/SignTransactionWallets/Hardware.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { fAccounts } from '@fixtures';
import { translateRaw } from '@translations';
import { BusyBottomConfig, WalletId } from '@types';

import { SignTxHardwareUI } from './Hardware';
import { SignTxHardwareUI, WalletSigningState } from './Hardware';

export default { title: 'Features/SignTransaction/Hardware', component: SignTxHardwareUI };

Expand All @@ -16,7 +16,7 @@ const initialProps: ComponentProps<typeof SignTxHardwareUI> = {
}),
wallet: BusyBottomConfig.LEDGER,
senderAccount: { ...fAccounts[0], wallet: WalletId.LEDGER_NANO_S_NEW },
isTxSignatureRequestDenied: true
signingState: WalletSigningState.REJECTED
};

export const HardwareWalletUI = () => {
Expand Down
Loading

0 comments on commit e9b5c88

Please sign in to comment.