Skip to content

Commit

Permalink
feat: create provider-ledger-react-native package
Browse files Browse the repository at this point in the history
  • Loading branch information
vivalaakam committed Jan 10, 2023
0 parents commit 7794551
Show file tree
Hide file tree
Showing 16 changed files with 3,321 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
34 changes: 34 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages

name: Node.js Package

on:
release:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: yarn install --frozen-lockfile
- run: yarn test

publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
78 changes: 78 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# OSX
#
.DS_Store

# IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof


# VSCode
.vscode/
jsconfig.json

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace

# Android/IJ
#
.classpath
.cxx
.gradle
.idea
.project
.settings
local.properties
android.iml

# Cocoapods
#
example/ios/Pods

# Ruby
example/vendor/

# node.js
#
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log

# BUCK
buck-out/
\.buckd/
android/app/libs
android/keystores/debug.keystore

# node.js
#
node_modules/
npm-debug.log
yarn-error.log

dist/
.npmrc
.vscode/
15 changes: 15 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.DS_Store

build/
.idea
.gradle
local.properties
*.iml
*.hprof

node_modules/
npm-debug.log
yarn-error.log
.npmrc

.github
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v16.16.0
7 changes: 7 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
arrowParens: 'avoid',
bracketSameLine: true,
bracketSpacing: false,
singleQuote: true,
trailingComma: 'all',
};
7 changes: 7 additions & 0 deletions jestconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}
35 changes: 35 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@haqq/provider-ledger-react-native",
"version": "0.0.1",
"description": "Provider for react-native ledger",
"main": "index.js",
"repository": "https://github.com/haqq-network/haqq-wallet-provider-ledger-react-native",
"author": "vivalaakam",
"license": "MIT",
"private": false,
"scripts": {
"test": "jest --config jestconfig.json",
"build": "tsc",
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
"lint": "tslint -p tsconfig.json"
},
"dependencies": {
"@ethersproject/abstract-provider": "^5.7.0",
"@ledgerhq/hw-app-eth": "^6.30.3",
"@ledgerhq/hw-transport": "^6.27.9",
"@ledgerhq/react-native-hw-transport-ble": "^6.27.12",
"ethers": "^5.7.2",
"react-native-ble-plx": "^2.0.3"
},
"devDependencies": {
"@haqq/provider-base": "^0.0.7",
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"jest": "^29.3.1",
"prettier": "^2.8.1",
"ts-jest": "^29.0.3",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.9.4"
}
}
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Provider for ledger react-native
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './provider';
export * from './types';
168 changes: 168 additions & 0 deletions src/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import {ProviderLedgerReactNativeOptions,} from './types';
import {
compressPublicKey,
Provider,
ProviderInterface
} from '@haqq/provider-base';
import {TransactionRequest} from '@ethersproject/abstract-provider';
import {UnsignedTransaction, utils} from 'ethers';
import AppEth, {ledgerService} from '@ledgerhq/hw-app-eth';
import TransportBLE from '@ledgerhq/react-native-hw-transport-ble';
import {BleManager, State} from 'react-native-ble-plx';
import {sleep} from './sleep';

const connectOptions = {
requestMTU: 156,
connectionPriority: 1,
};

export class ProviderLedgerReactNative extends Provider<ProviderLedgerReactNativeOptions> implements ProviderInterface {
public stop: boolean = false;
private _transport: TransportBLE | null = null
private _bleManager: BleManager | null = null

async getBase64PublicKey() {
let resp = ''
try {
if (!this._wallet.publicKey) {
this.stop = false;

const transport = await this.awaitForTransport(this._options.deviceId);
if (!transport) {
throw new Error('can_not_connected');
}
const eth = new AppEth(transport);

const response = await eth.getAddress(this._options.hdPath);

this._wallet.publicKey = compressPublicKey(response.publicKey);
}

resp = Buffer.from(this._wallet.publicKey, 'hex').toString('base64');
} catch (e) {
if (e instanceof Error) {
this.emit('getBase64PublicKey', false, e.message);
throw new Error(e.message);
}
}
return resp
}

async getSignedTx(transaction: TransactionRequest) {
let resp = ''
try {
this.stop = false;
const unsignedTx = utils
.serializeTransaction(transaction as UnsignedTransaction)
.substring(2);
const resolution = await ledgerService.resolveTransaction(
unsignedTx,
{},
{},
);

const transport = await this.awaitForTransport(this._options.deviceId);

if (!transport) {
throw new Error('can_not_connected');
}

const eth = new AppEth(transport);

const signature = await eth.signTransaction(this._options.hdPath, unsignedTx, resolution);

resp = utils.serializeTransaction(transaction as UnsignedTransaction, {
...signature,
r: '0x' + signature.r,
s: '0x' + signature.s,
v: parseInt(signature.v, 10),
});

this.emit('getSignedTx', true);
} catch (e) {
if (e instanceof Error) {
this.emit('getSignedTx', false, e.message);
throw new Error(e.message);
}
}

return resp
}

async signTypedData(domainHash: string, valuesHash: string) {
let resp = ''
try {
this.stop = false;

const transport = await this.awaitForTransport(this._options.deviceId);

if (!transport) {
throw new Error('can_not_connected')
}

const eth = new AppEth(transport);

const signature = await eth.signEIP712HashedMessage(this._options.hdPath, domainHash, valuesHash);

const v = (signature.v - 27).toString(16).padStart(2, '0');
resp = '0x' + signature.r + signature.s + v;

this.emit('signTypedData', true);
} catch (e) {
if (e instanceof Error) {
this.emit('signTypedData', false, e.message);
throw new Error(e.message);
}
return '';
}

return resp
}

abort() {
this.emit('abortCall');
this.stop = true;
}

async awaitForTransport(deviceId: string) {
if (!this._bleManager) {
this._bleManager = new BleManager();
}
while (!this._transport && !this.stop) {
try {
const state = await this._bleManager.state();

if (state !== State.PoweredOn) {
throw new Error(`not_connected ${state}`);
}

const device = await this._bleManager.connectToDevice(
deviceId,
connectOptions,
);

const isConnected = await device.isConnected();

if (!isConnected) {
await device.connect();
}

this._transport = await TransportBLE.open(device);
if (this._transport) {
this._transport.on('disconnect', this.onDisconnectTransport)
}
} catch (e) {
this.emit('awaitForTransport', new Date(), e)
await sleep(500);
}
}

return this._transport
}

onDisconnectTransport = () => {
if (this._transport) {
this._transport.off('disconnect', this.onDisconnectTransport)
}
}
}
5 changes: 5 additions & 0 deletions src/sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function sleep(duration: number) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type ProviderLedgerReactNativeOptions = {
deviceId: string;
hdPath: string;
}
14 changes: 14 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "./dist",
"strict": true
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"]
}

Loading

0 comments on commit 7794551

Please sign in to comment.