Ledger Nano S full WEB development guide : ETH

Ledger Nano S is the most popular secure cryptocurrency wallet which has been sold more than 1,441,683 times ( info ledger off. site 12.06.2019 ). Unfortunately, there is not much information for developers in docs, or at least it is hard to understand for newbie developers. This is why I`ve decided to write this article.

My colleague and I had to make transactions and sign them with ledger Nano S on front part of the app. For now we`ve completed the task with making and signing ETH crypto.

Firstly, we`ll need libraries to make and sign ETH transactions with a cold wallet.

import Web3 from "web3";
import EthereumTx from "ethereumjs-tx";
import AppEth from "@ledgerhq/hw-app-eth";
import TransportU2F from "@ledgerhq/hw-transport-u2f";

“ web3.js ” is a collection of libraries which allow you to interact with a local or remote Ethereum node, using an HTTP, WebSocket or IPC connection.

“ ethereumjs-tx ” is library for making serialized transactions from raw transaction parameters.

“ @ledgerhq/hw-app-eth ” is a library delivered by ledger company to interact with ETH app installed in cold wallet from web.

“ @ledgerhq/hw-transport-u2f ” is a library also delivered by ledger company to transport our queries from web to the ledger wallet.

Start:

We have used async/await in the general function . We need to follow the order of queries to web and cold wallet. As parameters function accept: address where we`ll send crypto(string), amount(number),

const makeSignEth = async (address,amount) => {
try {

Create a custom blockchain provider for check fee, get balance, get numbers of transactions from cold wallet address.

const web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io'));

Get gas Price for blockchain miners (our transaction fee)

const gasPrice = await getFees({ currency: "eth"});
////////////////////////////////////////////////////////////
const getFees = ({ currency }) => {
const result = api.coldWallet
.getParams({ currency })
.then(res => res.data)
.catch(err => {
Alert.error(null, {
customFields: {
title: "Error",
message: err.message ? err.message :
"Can`t check your ledger device. Refresh page and try again"
}
});
});
return result;
};
//////// API /////
getParams: ({ currency }) => axios.get(`https://api.ledgerwallet.com/blockchain/v2/${currency}/fees`),

Then get ETH account from ledger app, user need enter ETH app in ledger already when this function run. TransportU2F method transportU2F.setExchangeTimeout(5000) allow us to set a device interacting timeout.

const myAddress = await getEthAccount();
////////////////////////////////////////////////////////////////
const getEthAccount = async () => {
try {
const path = "44'/60'/0'/0/0";
const transportU2F = await TransportU2F.create();
transportU2F.setExchangeTimeout(5000);
const ethApp = new AppEth(transportU2F);
const result = await ethApp.getAddress(path);
return result.address;
}
catch (e) {
console.log(e);
Alert.error(null, {
customFields: {
title: "Error",
message: e.message ? e.message : "Can`t check your ledger device. Refresh page and try again"
}
});
}
};

Get amount of transactions sent from cold wallet address, need for blockchain`s transactions numbering.

const nonce = await getNonce(myAddress, web3)
.then(result => {
return web3.utils.toHex(String(result));
})
.catch(err => {
console.log(err);
});

Checking for positive balance on cold wallet address , if not throw new Reference error.

const balance = await web3.eth.getBalance(myAddress);if (balance <= 0){
throw new ReferenceError('Not enought money on balance');
}

Get a chain id of ours transaction and we have prepared raw transaction;

const chainId = await  web3.eth.getChainId();const txParams = {
nonce: nonce,
gasLimit: web3.utils.toHex('21000'),
gasPrice: web3.utils.toHex(gasPrice.gas_price.toString()),
from: myAddress,
to: address,
value: web3.utils.numberToHex(web3.utils.toWei((amount).toString(), 'ether')),
chainId: chainId,
v: '0x01',
r: '0x00',
s: '0x00',
};

Serialize txParams with EthereumTx(), convert raw tx to HEX. Then signature transaction by ledger ( need to accept in ledger your trans.)and retrive r, s, v params for blockchain.

const serializedTx = await new EthereumTx(txParams)
.serialize().toString('hex');
const transport = await TransportU2F.create();
transport.setExchangeTimeout(35000);
const app = new AppEth(transport);
const signature = await app.signTransaction("44'/60'/0'/0/0", serializedTx);
txParams.r = `0x${signature.r}`;
txParams.s = `0x${signature.s}`;
txParams.v = `0x${signature.v}`;

Previous txParams added with r, s, v, serialize again and conver to hex.

const signedTxHex = await new EthereumTx(txParams)
.serialize().toString('hex');

Than signedTxHex send to the ledger`s blockchain network .

const  resHash = await sendSignedTransaction({ data: { tx: `0x${signedTxHex}` }, currency: "eth"})
/////////////////////// API ///////////////////
sendSignedTransaction: ({ data, currency }) =>
axios.post(`https://api.ledgerwallet.com/blockchain/v2/${currency}/transactions/send`, data),

Full code:


const
makeSignEth = async (address,amount,coin,id) => {
try {

// CREATE CUSTOM PROVIDER //////////////////////////////////////////////////////////
const
web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io'));

// GET GAS PRICE /////////////////////////////////////////
const
gasPrice = await getFees({ currency: 'eth' });
console.log('START');

// GET ADDRESS /////////////////////////////////////////
const
myAddress = await getEthAccount();
console.log('myAddress', myAddress);

// GET NONSE ///////////////////////////////////////////
const
nonce = await getNonce(myAddress, web3)
.then(result => {
return web3.utils.toHex(String(result));
})
.catch(err => {
console.log(err);
this.resetColdWalletFlag();
});
// GET BALANCE /////////////////////////////////////// const balance = await web3.eth.getBalance(myAddress);
console.log('balance', balance ,'AMOUNT',amount);
if (balance <= 0){
throw new ReferenceError('Not enought money on balance');
}
//GET ChainID//////////
const chainId = await web3.eth.getChainId();
// CREATE RAW TX //
const
txParams = {
nonce: nonce,
gasLimit: web3.utils.toHex('21000'),
gasPrice: web3.utils.toHex(gasPrice.gas_price.toString()),
from: myAddress,
to: address,
value: web3.utils.numberToHex(web3.utils.toWei((amount).toString(), 'ether')),
chainId: chainId,
v: '0x01',
r: '0x00',
s: '0x00',
};

// CONVERT RAW TX TO HEX //////////////////////////////////
const
serializedTx = await new EthereumTx(txParams).serialize().toString('hex');

// SIGNATURE TRANSACTION /////////////////////////////////
const
transport = await TransportU2F.create();
transport.setExchangeTimeout(35000);
const app = new AppEth(transport);
const signature = await app.signTransaction("44'/60'/0'/0/0", serializedTx);
txParams.r = `0x${signature.r}`;
txParams.s = `0x${signature.s}`;
txParams.v = `0x${signature.v}`;

// CONVERT SIGNATURE TRANSACTION TO HEX //////////////////////
const signedTxHex = await new EthereumTx(txParams).serialize().toString('hex');
console.log('SIGNED',signedTxHex);


// SENT SIGNED TRANSACTION TO NETWORK LEDGER BLOCKCHAIN ///////////////////////
const
resHash = await sendSignedTransaction({ data: { tx: `0x${signedTxHex}` }, currency: "eth"});

return await sendHashToServer({
'asset_id':id,
'amount':Number(amount),
'tx_hash':resHash,
'cold_address':myAddress
});
} catch (err) {
console.log(err);
Alert.error(null, {
customFields: {
title: "Error",
message: err.message ? err.message : "Can`t check your ledger device. Refresh page and try again"
}
});
}
};

Thank you for reading, we hope this guide to the ledger web development was useful for you.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store