├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── electron.js ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── preload.js └── robots.txt ├── src ├── App.css ├── App.tsx ├── index.css ├── index.tsx ├── logo.svg └── react-app-env.d.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Test Applicaion for Firma-JS-Ledger (React-Web and Electron Desktop App) 2 | 3 | ## Install 4 | 5 | In the project directory, you can run: 6 | ``` 7 | yarn 8 | ``` 9 | 10 | ## Run React Web 11 | ``` 12 | npm run react:start 13 | ``` 14 | - Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 15 | 16 | image 17 | 18 |
19 | 20 | ## Run Electron Desktop App (Windows/MacOs/Linux support) 21 | ``` 22 | npm run react:start 23 | 24 | // open another terminal and run 25 | npm run electron:start 26 | ``` 27 | 28 | - The application will be launched automatically in the development mode 29 | image 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ledger-test", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "./", 6 | "main": "./public/electron.js", 7 | "build": { 8 | "appId": "com.firmachain.wallet", 9 | "files": [ 10 | "public/**/*", 11 | "build/**/*", 12 | "node_modules/**/*" 13 | ], 14 | "directories": { 15 | "buildResources": "assets" 16 | } 17 | }, 18 | "dependencies": { 19 | "@firmachain/firma-js": "0.2.45", 20 | "@firmachain/firma-js-ledger": "0.0.8", 21 | "@ledgerhq/hw-transport-node-hid": "^6.20.0", 22 | "@ledgerhq/hw-transport-node-hid-singleton": "^6.11.2", 23 | "@ledgerhq/hw-transport-webhid": "^6.20.0", 24 | "@testing-library/jest-dom": "^5.11.4", 25 | "@testing-library/react": "^11.1.0", 26 | "@testing-library/user-event": "^12.1.10", 27 | "@types/jest": "^26.0.15", 28 | "@types/node": "^12.0.0", 29 | "@types/react": "^17.0.0", 30 | "@types/react-dom": "^17.0.0", 31 | "react": "^17.0.2", 32 | "react-dom": "^17.0.2", 33 | "react-scripts": "4.0.3", 34 | "typescript": "^4.1.2", 35 | "web-vitals": "^1.0.1" 36 | }, 37 | "devDependencies": { 38 | "concurrently": "^6.2.1", 39 | "cross-env": "^7.0.3", 40 | "electron": "^14.0.1", 41 | "electron-builder": "^22.11.7", 42 | "wait-on": "^6.0.0" 43 | }, 44 | "scripts": { 45 | "start": "concurrently \"npm run react:start\" \"npm run electron:start\"", 46 | "react:start": "react-scripts start", 47 | "react:build": "react-scripts build", 48 | "electron:start": "wait-on http://localhost:3000 && electron ." 49 | }, 50 | "eslintConfig": { 51 | "extends": [ 52 | "react-app", 53 | "react-app/jest" 54 | ] 55 | }, 56 | "browserslist": { 57 | "production": [ 58 | ">0.2%", 59 | "not dead", 60 | "not op_mini all" 61 | ], 62 | "development": [ 63 | "last 1 chrome version", 64 | "last 1 firefox version", 65 | "last 1 safari version" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /public/electron.js: -------------------------------------------------------------------------------- 1 | 2 | const { app, BrowserWindow, shell, ipcMain } = require("electron"); 3 | const electron = require("electron"); 4 | const path = require("path"); 5 | 6 | const TransportHID = require('@ledgerhq/hw-transport-node-hid').default 7 | 8 | const { FirmaWebLedgerWallet } = require("@firmachain/firma-js-ledger"); 9 | 10 | electron.app.setPath("userData", path.join(electron.app.getPath("home"), ".firma-station")); 11 | 12 | let ledgerWallet = new FirmaWebLedgerWallet(TransportHID); 13 | 14 | function initialize() { 15 | function createWindow() { 16 | const size = electron.screen.getPrimaryDisplay().workAreaSize; 17 | const originWidth = size.width; 18 | const width = originWidth > 1080 ? parseInt(1080 + (originWidth - 1080) * 0.5) : originWidth; 19 | const height = parseInt(width / (1920 / 1080)); 20 | 21 | const windowOptions = { 22 | minWidth: width, 23 | minHeight: height, 24 | width: width, 25 | height: height, 26 | title: app.getName(), 27 | titleBarStyle: "hiddenInset", 28 | webPreferences: { 29 | nodeIntegration: false, 30 | enableRemoteModule: false, 31 | nativeWindowOpen: true, 32 | webSecurity: false, 33 | preload: path.resolve(__dirname, "preload.js"), 34 | }, 35 | resizable: true, 36 | }; 37 | mainWindow = new BrowserWindow(windowOptions); 38 | mainWindow.setMenu(null); 39 | mainWindow.loadURL( 40 | "http://localhost:3000" 41 | // url.format({ 42 | // pathname: path.join(__dirname, "/../build/index.html"), 43 | // protocol: "file", 44 | // slashes: true, 45 | // }) 46 | ); 47 | 48 | mainWindow.webContents.openDevTools(); 49 | 50 | mainWindow.once("ready-to-show", () => { 51 | mainWindow.show(); 52 | }); 53 | 54 | mainWindow.on("closed", () => { 55 | mainWindow = null; 56 | }); 57 | mainWindow.on("will-resize", (event) => { 58 | event.preventDefault(); 59 | }); 60 | } 61 | 62 | app.on("ready", createWindow); 63 | 64 | app.on("window-all-closed", function () { 65 | app.quit(); 66 | }); 67 | 68 | app.on("web-contents-created", (e, webContents) => { 69 | webContents.on("new-window", (event, url) => { 70 | event.preventDefault(); 71 | 72 | if (url.match(/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/g)) { 73 | shell.openExternal(url); 74 | } 75 | }); 76 | }); 77 | 78 | ipcMain.on("ledger-showAddressOnDevice", async (event, arg) => { 79 | 80 | await ledgerWallet.showAddressOnDevice(); 81 | event.returnValue = ""; 82 | }); 83 | 84 | ipcMain.on("ledger-getAddress", async (event, arg) => { 85 | 86 | let address = await ledgerWallet.getAddress(); 87 | event.returnValue = address; 88 | }); 89 | 90 | ipcMain.on("ledger-getAddressAndPublicKey", async (event, arg) => { 91 | 92 | let data = await ledgerWallet.getAddressAndPublicKey(); 93 | event.returnValue = data; 94 | }); 95 | 96 | ipcMain.on("ledger-sign", async (event, arg) => { 97 | 98 | let message = await ledgerWallet.sign(arg["message"]); 99 | event.returnValue = message; 100 | }); 101 | 102 | ipcMain.on("ledger-getPublicKey", async (event, arg) => { 103 | 104 | let message = await ledgerWallet.getPublicKey(); 105 | event.returnValue = message; 106 | }); 107 | 108 | } 109 | 110 | initialize(); 111 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirmaChain/ledger-test-app/afc1be916eea0e6432bd9f60efe0accd69391437/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | React App 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirmaChain/ledger-test-app/afc1be916eea0e6432bd9f60efe0accd69391437/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirmaChain/ledger-test-app/afc1be916eea0e6432bd9f60efe0accd69391437/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require("electron"); 2 | 3 | contextBridge.exposeInMainWorld("electron", { 4 | sendSync: ipcRenderer.sendSync, 5 | on: ipcRenderer.on, 6 | }); 7 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | 3 | import { FirmaSDK, FirmaConfig } from "@firmachain/firma-js" 4 | import { FirmaWebLedgerWallet } from "@firmachain/firma-js-ledger"; 5 | import { FirmaBridgeLedgerWallet } from "@firmachain/firma-js-ledger"; 6 | 7 | import TransportHID from "@ledgerhq/hw-transport-webhid"; 8 | 9 | declare global { 10 | interface Window { 11 | require: NodeRequire; 12 | electron: any; 13 | } 14 | } 15 | 16 | function App() { 17 | 18 | // Incase of IPC 19 | let bridgeLedgerWallet = new FirmaBridgeLedgerWallet(); 20 | 21 | bridgeLedgerWallet.registerShowAddressOnDevice(async (): Promise => { 22 | window.electron.sendSync("ledger-showAddressOnDevice", {}); 23 | }); 24 | 25 | bridgeLedgerWallet.registerGetAddressAndPublicKeyCallback(async (): Promise<{ address: string, publicKey: Uint8Array }> => { 26 | return window.electron.sendSync("ledger-getAddressAndPublicKey", {}); 27 | }); 28 | 29 | bridgeLedgerWallet.registerGetAddressCallback(async (): Promise => { 30 | return window.electron.sendSync("ledger-getAddress", {}); 31 | }); 32 | 33 | bridgeLedgerWallet.registerGetPublicKeyCallback(async (): Promise => { 34 | return window.electron.sendSync("ledger-getPublicKey", {}); 35 | }); 36 | 37 | bridgeLedgerWallet.registerGetSignCallback(async (message: string): Promise => { 38 | return window.electron.sendSync("ledger-sign", { message: message }); 39 | }); 40 | 41 | const sendIPC_ShowAddressOnDevice = async () => { 42 | await bridgeLedgerWallet.showAddressOnDevice(); 43 | }; 44 | 45 | const sendIPC_GetAddress = async () => { 46 | let address = await bridgeLedgerWallet.getAddress(); 47 | console.log(address); 48 | }; 49 | 50 | const sendIPC_Send = async () => { 51 | 52 | const firma = new FirmaSDK(FirmaConfig.TestNetConfig); 53 | const wallet = await firma.Wallet.initFromLedger(bridgeLedgerWallet); 54 | 55 | const amount = 1; 56 | 57 | try { 58 | var result1 = await firma.Bank.send(wallet, "firma1epg9kx7nqz32dykj23p6jreqfh5x0wdy5a43qc", amount); 59 | console.log(result1); 60 | } catch (error) { 61 | console.log("error!!!!"); 62 | } 63 | }; 64 | 65 | let webLedgerWallet = new FirmaWebLedgerWallet(TransportHID); 66 | 67 | const send_ShowAddressOnDevice = async () => { 68 | await webLedgerWallet.showAddressOnDevice(); 69 | }; 70 | 71 | const send_GetAddress = async () => { 72 | let address = await webLedgerWallet.getAddress(); 73 | console.log(address); 74 | }; 75 | 76 | const send_Send = async () => { 77 | 78 | try { 79 | const firma = new FirmaSDK(FirmaConfig.TestNetConfig); 80 | const wallet = await firma.Wallet.initFromLedger(webLedgerWallet); 81 | 82 | const amount = 1; 83 | 84 | var result1 = await firma.Bank.send(wallet, "firma1epg9kx7nqz32dykj23p6jreqfh5x0wdy5a43qc", amount); 85 | console.log(result1); 86 | 87 | } catch (error) { 88 | console.log("error!!!!"); 89 | } 90 | }; 91 | 92 | return ( 93 |
94 |
95 | 96 | 97 | 98 | 99 |

100 |

101 |

102 | 103 | 104 | 105 |
106 |
107 | ); 108 | } 109 | 110 | export default App; 111 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById("root") 11 | ); 12 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------