Introduction
In this section, we will guide you through the creation of an application that will call a smart contract to read and write.
Prerequisites
Before starting, make sure you have gone through the prerequisites.
Send Ether token to your Ledger Nano ethereum account
To send some ethers on the Ropsten network, go to one of the ropsten faucet websites:
The Ropsten network is not visible on Ledger Live, you can then check the transaction passed on ropsten.etherscan.io.
If the Ropsten and Dimension faucets don’t work or the queue is too long, please use another faucet of your choice to receive testnet Ether.
Ropsten Ethereum Network
Go to the Ropsten Ethereum Faucet website put your Wallet Public Key on the input and click on “Send me test Ether”
Fig. 1: Ropsten Ethereum Faucet
Dimensions Network
Go to the Dimensions Network website put your Wallet Public Key on the input, do the captcha and click on “Send me test Ether”
Fig. 2: Ropsten Ethereum Faucet
Web App Bluetooth (only Nano X)
The Ledger Nano S and S Plus do not have the Bluetooth functionality. This tutorial will only work with a Ledger Nano X.
Please be aware that the Bluetooth implementation is only supported by a few browsers.
You can check the browser support for the Web Bluetooth transport.
Project Initialization
The app is build with React, which is a frontend Javascript framework.
First, open a terminal and create a new project. For this tutorial the project will be named “e2e-tutorial-contract”.
Run:
npx create-react-app e2e-tutorial-contract
cd e2e-tutorial-contract
Open the folder in an editor.
The React app initialization creates a “src” folder where you will find all the code.
Run:
touch ./src/ConnectLedger.js
touch ./src/SmartContract.js
touch ./src/ethereum.js
touch ./src/polyfill.js
Your folder must look like this.
Fig. 1: Folder of the Application
To implement the Ledger connexion you will only modify “App.js”, “index.js”, “ConnectLedger.js”,”SmartContract.js”, and ethereum.js”
Code Implementation
App.js
In App.js copy-paste the following code:
import React, { useState } from 'react';
import ConnectLedger from './ConnectLedger.js';
import SmartContract from './SmartContract.js';
function App() {
const [transport, setTransport] = useState(undefined);
const [eth, setEth] = useState(undefined);
const [address, setAddress] = useState(undefined);
const saveInfo = (info) => {
setAddress(info.address);
setEth(info.eth);
setTransport(info.transport);
}
return (
<div className='container'>
{
!transport ?
<ConnectLedger onTransport={(info) => saveInfo(info)}></ConnectLedger> :
<SmartContract address={address} eth={eth}></SmartContract>
}
</div>
);
}
export default App;
polyfill.js
In App.js copy-paste the following code:
global.Buffer = require("buffer").Buffer;
index.js
in the index.js add these line
import './polyfill'
import 'bootstrap/dist/css/bootstrap.min.css';
ConnectLedger.js
In ConnectLedger.js, copy-paste the following code:
import React from 'react';
import TransportWebBLE from "@ledgerhq/hw-transport-web-ble";
import Eth from "@ledgerhq/hw-app-eth";
function ConnectLedger({onTransport}) {
const connectLedger = async() => {
const transport = await TransportWebBLE.create();
const eth = new Eth(transport);
const {address} = await eth.getAddress("44'/60'/0'/0/0", false);
onTransport({address,eth,transport})
}
return (
<div className='container'>
<div className='row'>
<div className='col-sm-4 mt-5 mx-auto'>
<button onClick={connectLedger}>Connect Your Ledger</button>
</div>
</div>
</div>
);
}
export default ConnectLedger;
SmartContract.js
In “SmartContract.js”, copy-paste the following code:
import React, { useState } from 'react';
import getBlockchain from './ethereum.js';
import { ethers } from 'ethers';
function SmartContract({eth,address}) {
const [simpleStorage, setSimpleStorage] = useState(undefined);
const [data, setData] = useState(undefined);
const [provider, setProvider] = useState(undefined);
const [url, setUrl] = useState(undefined);
const smartContractRead = async() => {
const provider = new ethers.providers.JsonRpcProvider('https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161');
const { simpleStorage } = await getBlockchain(provider);
console.log(simpleStorage);
const data = await simpleStorage.readData();
setProvider(provider);
setSimpleStorage(simpleStorage);
setData(data);
};
const updateData = async e => {
e.preventDefault();
const dataInput = e.target.elements[0].value;
console.log(simpleStorage);
const { data } = await simpleStorage.populateTransaction['updateData(uint256)'](dataInput);
const unsignedTx = {
to: simpleStorage.address,
gasPrice: (await provider.getGasPrice())._hex,
gasLimit: ethers.utils.hexlify(100000),
nonce: await provider.getTransactionCount(address, "latest"),
chainId: 3,
data: data,
}
console.log(unsignedTx);
const serializedTx = ethers.utils.serializeTransaction(unsignedTx).slice(2);
console.log(serializedTx);
const signature = await eth.signTransaction(
"44'/60'/0'/0/0",
serializedTx
);
console.log(signature);
//Parse the signature
signature.r = "0x"+signature.r;
signature.s = "0x"+signature.s;
signature.v = parseInt("0x"+signature.v);
signature.from = address;
console.log(signature);
//Serialize the same transaction as before, but adding the signature on it
const signedTx = ethers.utils.serializeTransaction(unsignedTx, signature);
console.log(signedTx);
const hash = (await provider.sendTransaction(signedTx)).hash;
console.log(hash);
setUrl("https://ropsten.etherscan.io/tx/" + hash);
};
return (
<div className='container'>
<div className='row'>
<div className='col-sm-4'>
<h2>Data:</h2>
<p>{data ? data.toString() : "..." }</p>
<button onClick={() => smartContractRead()}>Get Data</button>
</div>
<div className='col-sm-4'>
<h2>Change data</h2>
<form className="form-inline" onSubmit={e => updateData(e)}>
<input
type="text"
className="form-control"
placeholder="data"
/>
<button
type="submit"
className="btn btn-primary"
>
Submit
</button>
</form>
</div>
<div className="mt-5 mx-auto d-flex flex-column">
<p>
Ropsten Etherscan :
</p>
<p><a href={url} target="_blank" rel="noreferrer">{url}</a></p>
</div>
</div>
</div>
);
}
export default SmartContract;
ethereum.js
In “ethereum.js”, copy-paste the following code:
import { Contract } from 'ethers';
const getBlockchain = (provider) =>
new Promise( async (resolve, reject) => {
if(provider) {
const simpleStorage = new Contract(
"0x989c810f64ac577683d49a318adfd98b8d482472",
[
{
"inputs": [],
"name": "data",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "readData",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_data",
"type": "uint256"
}
],
"name": "updateData",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
provider
);
resolve({simpleStorage});
return;
}
reject('Provider not recognized');
});
export default getBlockchain;
Dependencies Installation
Run:
npm install --save bootstrap
npm install --save ethers
npm install --save @ledgerhq/hw-app-eth
npm install --save @ledgerhq/hw-transport-web-ble
npm install --save buffer
Package |
What does it do? |
bootstrap |
It allows you to use the Bootstrap CSS framework. |
ethers |
It provides you with all the methods to interact with the Ethereum blockchain |
@ledgerhq/hw-app-eth |
It will help you ask your Nano to access the ethereum address. |
@ledgerhq/hw-transport-web-ble |
It provides you with all the methods to interact with your Ledger Nano X with a Bluetooth connexion. |
buffer |
The goal is to provide an API that is 100% identical to node's Buffer API. |
Package.json Dependencies
Now that the dependencies are installed you can find them in the “package.js”.
This is how your “package.json” shoud look like:
{
"name": "e2e-tutorial",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ledgerhq/hw-app-eth": "^6.26.0",
"@ledgerhq/hw-transport-web-ble": "^6.24.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.1.3",
"buffer": "^6.0.3",
"ethers": "^5.5.4",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Web App Test
Start the Development Server
Run:
Warning
All the browser do not support the Bluetooth please look at the browser support .
Now the application is up and running. Open the browser and go to localhost:3000
, it will display :
Fig. 2: Application Running on Browser
Don’t click on the button yet.
Launch Ethereum App
Before clicking on the button, unlock your Nano X and run the Ethereum application.
The steps are described below.

Fig. 3: Ledger Enter Code Pin
Fig. 4: Ledger Application
Fig. 5: Ledger Run Application
Connect Your Nano to the Application
Now you can click on the button and a popup open. Choose your Ledger Nano X and click connexion
Fig. 6: Connect the Ledger with Bluetooth
Read the data of a Smart Contract
Now you can click on the button “Get Data” to read the data of the smart contract. Then the data will be displayed on the screen.
Fig. 7: Get data from a smart contract
Update the data of a Smart Contract
Now instead of reading data, we will overwrite the data by calling a function of the smart contract which is “UpdateData”.
Fig. 8: Change data from a smart contract
Verify the Address on your Nano
For security reasons, the address will also be displayed on your Ledger Nano X to verify and confirm the address.
Fig. 9: Nano Review Screen \ Fig. 10: Nano Amount Screen
Fig. 11: Nano Address Screen \ Fig. 12: Nano Network Screen
Fig. 13: Nano Max Fees Screen \ Fig. 14: Nano Accept and Send Screen
Warning
For the Smart Contract call you need to allow blind signing because the smart contract that is called in the tutorial is not yet verified and reviewed by Ledger. But if the smart contract you are calling is accepted by Ledger do not enable blind signing. Moreover, we do not recommend enabling blind signing in other situations as the main purpose to sign with Ledger is the 'Sign what you see' system. And by enabling blind signing it can not ensure that what you see is what you get.
Review the Transaction on Ropsten Etherscan
By updating the data a transaction is created to change this data, it can be verified on Ropsten Etherscan.
Fig. 15: Ropsten Etherscan
Wait till the status passes to Success.
Fig. 16: Ropsten Etherscan
Verify the update of data
Finally, to verify if data was updated, open the web application and click on “Get data”.
Fig. 17: Verify the data
Fig. 18: Verify the data
Congratulations, you have successfully built your first application connected with Ledger!