-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
538 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
declare module '@ledgerhq/hw-transport-mocker/RecordStore' { | ||
export type Queue = [string, string][]; | ||
|
||
export class RecordStore { | ||
/** | ||
* Create an instance of RecordStore from a string. | ||
* | ||
* @param {string} str The string to create the RecordStore from. | ||
* @return {RecordStore} An instance of RecordStore; | ||
*/ | ||
public static fromString(str: string): RecordStore; | ||
|
||
public queue: Queue; | ||
|
||
/** | ||
* Create a new instance of the RecordStore class. | ||
* | ||
* @param {Queue} queue An optional queue to use. | ||
*/ | ||
public constructor(queue?: Queue); | ||
|
||
/** | ||
* Get whether the queue is empty. | ||
* | ||
* @return {boolean} TRUE if the queue is empty, FALSE otherwise | ||
*/ | ||
public isEmpty(): boolean; | ||
|
||
/** | ||
* Record an APDU exchange to the queue. | ||
* | ||
* @param {Buffer} apdu The input data. | ||
* @param {Buffer} out The output data. | ||
*/ | ||
public recordExchange(apdu: Buffer, out: Buffer): void; | ||
|
||
/** | ||
* Replay a previously recorded APDU exchange. Throws an error if the queue is empty or if the | ||
* recorded APDU is invalid. | ||
* | ||
* @param {Buffer} apdu The input to replay | ||
* @return {Buffer} A Buffer with the previously recorded output data. | ||
*/ | ||
public replayExchange(apdu: Buffer): Buffer; | ||
|
||
/** | ||
* Ensure the queue is empty. Throws an error if the queue isn't empty. | ||
*/ | ||
public ensureQueueEmpty(): void; | ||
|
||
/** | ||
* Get the current queue as string. | ||
* | ||
* @return {string} The queue as string. | ||
*/ | ||
public toString(): string; | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/@types/ledgerhq/hw-transport-mocker/createTransportRecorder.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
declare module '@ledgerhq/hw-transport-mocker/createTransportRecorder' { | ||
import Transport from '@ledgerhq/hw-transport'; | ||
import { RecordStore } from '@ledgerhq/hw-transport-mocker/RecordStore'; | ||
|
||
class TransportRecorder<T> extends Transport<T> { | ||
public static recordStore: RecordStore; | ||
|
||
public static isSupported: typeof Transport.isSupported; | ||
|
||
public static list: typeof Transport.list; | ||
} | ||
|
||
type TransportConstructor<T> = new (...args: any[]) => Transport<T>; | ||
|
||
type TransportRecorderConstructor<T> = typeof TransportRecorder & | ||
(new (...args: any[]) => TransportRecorder<T>); | ||
|
||
/** | ||
* Create a decorated transport, which records any APDU exchanges. | ||
* | ||
* @param {TransportConstructor<T>} DecoratedTransport The transport class to decorate. | ||
* @param {RecordStore} recordStore The RecordStore to record to. | ||
* @return {TransportRecorder<T>} The decorated transport. | ||
*/ | ||
export default function<T>( | ||
DecoratedTransport: TransportConstructor<T>, | ||
recordStore: RecordStore | ||
): TransportRecorderConstructor<T>; | ||
} |
31 changes: 31 additions & 0 deletions
31
src/@types/ledgerhq/hw-transport-mocker/createTransportReplayer.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
declare module '@ledgerhq/hw-transport-mocker/createTransportReplayer' { | ||
import Transport, { Observer, Subscription } from '@ledgerhq/hw-transport'; | ||
import { RecordStore } from '@ledgerhq/hw-transport-mocker/RecordStore'; | ||
|
||
class TransportReplayer<T> extends Transport<T> { | ||
public static isSupported(): Promise<true>; | ||
|
||
public static list(): Promise<[null]>; | ||
|
||
public static listen(observer: Observer<{ type: 'add'; descriptor: null }>): Subscription; | ||
|
||
public static open(): Promise<TransportReplayer<any>>; | ||
} | ||
|
||
type TransportConstructor<T> = new (...args: any[]) => Transport<T>; | ||
|
||
type TransportReplayerConstructor<T> = typeof TransportReplayer & | ||
(new (...args: any[]) => TransportReplayer<T>); | ||
|
||
/** | ||
* Create a decorated transport, which replays any APDU exchanges. | ||
* | ||
* @param {TransportConstructor<T>} DecoratedTransport The transport class to decorate. | ||
* @param {RecordStore} recordStore The RecordStore to replay from. | ||
* @return {TransportReplayer<T>} The decorated transport. | ||
*/ | ||
export default function<T>( | ||
DecoratedTransport: TransportConstructor<T>, | ||
recordStore: RecordStore | ||
): TransportReplayerConstructor<T>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* Note: This stuff isn't documented at all, so these declarations may be inaccurate. | ||
*/ | ||
declare module '@ledgerhq/hw-transport-mocker' { | ||
export { | ||
default as createTransportRecorder | ||
} from '@ledgerhq/hw-transport-mocker/createTransportRecorder'; | ||
export { | ||
default as createTransportReplayer | ||
} from '@ledgerhq/hw-transport-mocker/createTransportReplayer'; | ||
export * from '@ledgerhq/hw-transport-mocker/RecordStore'; | ||
} |
51 changes: 51 additions & 0 deletions
51
src/@types/ledgerhq/hw-transport-node-hid-noevents/index.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
declare module '@ledgerhq/hw-transport-node-hid-noevents' { | ||
import Transport, { DescriptorEvent, Observer, Subscription } from '@ledgerhq/hw-transport'; | ||
import { DeviceModel } from '@ledgerhq/devices'; | ||
|
||
export default class TransportNodeHid extends Transport<string> { | ||
/** | ||
* List all available descriptors. For a better granularity, use `listen()`. | ||
* | ||
* @return {Promise<string[]>} All available descriptors. | ||
*/ | ||
public static list(): Promise<string[]>; | ||
|
||
/** | ||
* Listen to all device events for a given Transport. The method takes an Observer of | ||
* DescriptorEvent and returns a Subscription (according to Observable paradigm | ||
* https://github.com/tc39/proposal-observable). Each `listen()` call will first emit all | ||
* potential devices already connected and then will emit events that can come over time, for | ||
* instance if you plug a USB device after `listen()` or a Bluetooth device becomes | ||
* discoverable. | ||
* | ||
* Must be called in the context of a UI click. | ||
* | ||
* @param {Observer} observer The observer object. | ||
* @return A Subcription object on which you can `.unsubscribe()`, to stop listening to | ||
* descriptors. | ||
*/ | ||
public static listen(observer: Observer<DescriptorEvent<string>>): Subscription; | ||
|
||
/** | ||
* Attempt to create an instance of the Transport with the descriptor. | ||
* | ||
* @param {string} descriptor The descriptor to open the Transport with. If none provided, the | ||
* first available device will be used. | ||
* @return {Promise<Transport<TransportNodeHid>} A Promise with the Transport instance. | ||
*/ | ||
public static open(descriptor?: string): Promise<TransportNodeHid>; | ||
|
||
public readonly device: any; | ||
public readonly deviceModel?: DeviceModel; | ||
public readonly channel: number; | ||
public readonly packetSize: number; | ||
public readonly disconnected: boolean; | ||
|
||
public constructor(device: USBDevice); | ||
|
||
/** | ||
* Not used by this specific Transport. | ||
*/ | ||
public setScramblekey(): void; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import Transport from '@ledgerhq/hw-transport'; | ||
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid-noevents'; | ||
import EthereumApp from '@ledgerhq/hw-app-eth'; | ||
import Ledger from './Ledger'; | ||
import { createTransportRecorder, RecordStore } from '@ledgerhq/hw-transport-mocker'; | ||
|
||
/** | ||
* Can in theory be used to unit test Ledger devices. | ||
*/ | ||
export default class LedgerMock extends Ledger { | ||
public readonly store: RecordStore = new RecordStore(); | ||
protected transport: Transport<any> | null = null; | ||
protected app: EthereumApp | null = null; | ||
|
||
protected async checkConnection(): Promise<void> { | ||
if (this.transport === null) { | ||
this.transport = await this.getTransport(); | ||
this.app = new EthereumApp(this.transport); | ||
} | ||
} | ||
|
||
private async getTransport(): Promise<Transport<any>> { | ||
const DecoratedTransport = createTransportRecorder(TransportNodeHid, this.store); | ||
const recorder = await DecoratedTransport.open(undefined); | ||
|
||
recorder.on('disconnect', () => { | ||
this.transport = null; | ||
}); | ||
|
||
return recorder; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`fetches token metadata for ERC-20 tokens 1`] = ` | ||
Object { | ||
"address": "0xa74476443119A942dE498590Fe1f2454d7D4aC0d", | ||
"decimals": 18, | ||
"name": "Golem Network Token", | ||
"symbol": "GNT", | ||
} | ||
`; | ||
|
||
exports[`fetches token metadata for non-compliant ERC-20 tokens 1`] = ` | ||
Object { | ||
"address": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", | ||
"decimals": 18, | ||
"name": "Dai Stablecoin v1.0", | ||
"symbol": "DAI", | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { providers } from 'ethers'; | ||
import { Address } from '../../src/store/network'; | ||
import { getEtherBalances, getTokenBalances } from '../../src/utils'; | ||
import { Token } from '../../src/store/tokens'; | ||
|
||
const provider = new providers.InfuraProvider(1, 'bfea47cc97c440a687c8762553739a94'); | ||
|
||
it('fetches Ether balances for multiple addresses', async () => { | ||
const addresses: Address[] = [ | ||
{ | ||
address: '0x0000000000000000000000000000000000000000', | ||
path: `m/44'/60'/0'/0/0` | ||
}, | ||
{ | ||
address: '0x0000000000000000000000000000000000000001', | ||
path: `m/44'/60'/0'/0/1` | ||
}, | ||
{ | ||
address: '0x0000000000000000000000000000000000000002', | ||
path: `m/44'/60'/0'/0/2` | ||
} | ||
]; | ||
|
||
const balances = await getEtherBalances(provider, addresses); | ||
|
||
expect(balances.length).toBe(3); | ||
expect(balances[0].address).toBe(addresses[0].address); | ||
expect(balances[0].balance).not.toBe('0'); | ||
}); | ||
|
||
it('fetches token balances for multiple addresses', async () => { | ||
const addresses: Address[] = [ | ||
{ | ||
address: '0x0000000000000000000000000000000000000000', | ||
path: `m/44'/60'/0'/0/0` | ||
}, | ||
{ | ||
address: '0x0000000000000000000000000000000000000001', | ||
path: `m/44'/60'/0'/0/1` | ||
}, | ||
{ | ||
address: '0x0000000000000000000000000000000000000002', | ||
path: `m/44'/60'/0'/0/2` | ||
} | ||
]; | ||
|
||
const token: Token = { | ||
name: 'Dai Stablecoin v1.0', | ||
symbol: 'DAI', | ||
decimals: 18, | ||
address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359' | ||
}; | ||
|
||
const balances = await getTokenBalances(provider, addresses, token); | ||
|
||
expect(balances.length).toBe(3); | ||
expect(balances[0].address).toBe(addresses[0].address); | ||
expect(balances[0].balance).not.toBe('0'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { chunk } from '../../src/utils'; | ||
|
||
it('creates chunks from an array with a fixed length', () => { | ||
const array = ['foo', 'bar', 'baz', 'qux']; | ||
const chunks = chunk(array, 2); | ||
|
||
expect(chunks.length).toBe(2); | ||
expect(chunks[0]).toStrictEqual(['foo', 'bar']); | ||
expect(chunks[1]).toStrictEqual(['baz', 'qux']); | ||
}); | ||
|
||
it('includes items if the length is uneven', () => { | ||
const array = ['foo', 'bar', 'baz', 'qux']; | ||
const chunks = chunk(array, 3); | ||
|
||
expect(chunks.length).toBe(2); | ||
expect(chunks[0]).toStrictEqual(['foo', 'bar', 'baz']); | ||
expect(chunks[1]).toStrictEqual(['qux']); | ||
}); | ||
|
||
it('does not mutate the original array', () => { | ||
const array = ['foo', 'bar', 'baz', 'qux']; | ||
const copy = [...array]; | ||
const chunks = chunk(array, 2); | ||
|
||
expect(chunks.length).toBe(2); | ||
expect(array).toStrictEqual(copy); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { getFullPath } from '../../src/utils'; | ||
import { DEFAULT_ETH, LEDGER_LIVE_ETH } from '../../src/config'; | ||
|
||
it('returns a derivation path with an address index (non-hardened)', () => { | ||
expect(getFullPath(DEFAULT_ETH, 5)).toBe(`m/44'/60'/0'/0/5`); | ||
expect(getFullPath(DEFAULT_ETH, 10)).toBe(`m/44'/60'/0'/0/10`); | ||
}); | ||
|
||
it('returns a derivation path with an account index (hardened)', () => { | ||
expect(getFullPath(LEDGER_LIVE_ETH, 5)).toBe(`m/44'/60'/5'/0/0`); | ||
expect(getFullPath(LEDGER_LIVE_ETH, 10)).toBe(`m/44'/60'/10'/0/0`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { isEnsName } from '../../src/utils'; | ||
|
||
it('checks for valid ens names', () => { | ||
expect(isEnsName('foobarbaz.eth')).toEqual(true); | ||
expect(isEnsName('abcde12345.eth')).toEqual(true); | ||
}); | ||
|
||
it('checks for invalid ens names', () => { | ||
expect(isEnsName('foobarbaz.ethfoo')).toEqual(false); | ||
expect(isEnsName('0x0000000000000000000000000000000000000000')).toEqual(false); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { providers } from 'ethers'; | ||
import { getTokenInfo } from '../../src/utils'; | ||
|
||
jest.setTimeout(100000); | ||
|
||
const provider = new providers.InfuraProvider(1, 'bfea47cc97c440a687c8762553739a94'); | ||
|
||
it('fetches token metadata for ERC-20 tokens', async () => { | ||
await expect( | ||
getTokenInfo(provider, '0xa74476443119A942dE498590Fe1f2454d7D4aC0d') | ||
).resolves.toMatchSnapshot(); | ||
}); | ||
|
||
it('fetches token metadata for non-compliant ERC-20 tokens', async () => { | ||
await expect( | ||
getTokenInfo(provider, '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359') | ||
).resolves.toMatchSnapshot(); | ||
}); | ||
|
||
it('throws an error if an address is not a token', async () => { | ||
await expect( | ||
getTokenInfo(provider, '0x0000000000000000000000000000000000000000') | ||
).rejects.toThrow(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// TODO: Figure out if and how to unit test Ledger | ||
test.todo('Ledger'); |
Oops, something went wrong.