Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@miohtama
Last active September 25, 2019 13:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save miohtama/c947b4232ec9165f6503c1e9070f9fcc to your computer and use it in GitHub Desktop.
Save miohtama/c947b4232ec9165f6503c1e9070f9fcc to your computer and use it in GitHub Desktop.
Ledger topping up the gas for stuck Ethereum nonce/transaction
//
// Based on https://gist.github.com/miguelmota/62559d02a1b99cb291635de4b224349c
//
const Transport = require('@ledgerhq/hw-transport-node-hid').default
// https://github.com/LedgerHQ/ledgerjs/
// https://www.npmjs.com/package/@ledgerhq/hw-app-eth
const AppEth = require('@ledgerhq/hw-app-eth').default
// https://github.com/ethereumjs/ethereumjs-tx
const Tx = require('ethereumjs-tx').Transaction
const Web3 = require('web3')
// Use your personal Infura.io URL here
const web3 = new Web3('https://mainnet.infura.io/v3/xxx')
// Get this from etherscan IO
const STUCK_NOCE = 12
// 50 Gwei for gas should be good as writing of this
const GOOD_GAS_PRICE = 50e9
// Get your Ledger address
// Note that the derivate path "44'/60'/0'/0/0" or m/44'/60'/0' has changed between Ledger app v1 and v2
// https://github.com/gnosis/MultiSigWallet/issues/199
//const DERIVATE_PATH = "44'/60'/0'/0/0"
const DERIVATE_PATH = "m/44'/60'/0'/0"
async function main() {
const devices = await Transport.list()
if (devices.length === 0) throw 'No Ledger connected? Please connect Ledger to USB, unlock, open Ethereum app'
console.log("Check your Ledger screen if the script gets stuck here")
const transport = await Transport.create()
const eth = new AppEth(transport)
// https://www.npmjs.com/package/@ledgerhq/hw-app-eth#getaddress
const addressData = await eth.getAddress(DERIVATE_PATH)
console.log("Using address", addressData.address, "from derivate path", DERIVATE_PATH)
const balance = await web3.eth.getBalance(addressData.address)
console.log("We have", web3.utils.fromWei(balance, 'ether'), "ETH")
// Construct a transaction that replaces currenc stuck TX
// with one that sends 1 wei to yourself
const txData = {
nonce: web3.utils.toHex(STUCK_NOCE),
gasLimit: web3.utils.toHex(200000),
gasPrice: web3.utils.toHex(GOOD_GAS_PRICE),
to: addressData.address,
from: addressData.address,
value: web3.utils.toHex(3), // 3 wei
}
console.log("Knocker off transaction parameters are", txData)
const tx = new Tx(txData, { chain: 'mainnet'})
console.log(tx.toJSON());
const unsignedTx = tx.serialize().toString('hex')
console.log("Sign the transaction on your ledger screen")
console.log("Raw unsigned transaction", unsignedTx)
// https://www.npmjs.com/package/@ledgerhq/hw-app-eth#signtransaction
const sig = await eth.signTransaction(DERIVATE_PATH, unsignedTx)
console.log("Sig", sig)
txData.v = Buffer.from(sig.v, "hex")
txData.r = Buffer.from(sig.r, "hex")
txData.s = Buffer.from(sig.s, "hex")
console.log(txData.v, txData.r, txData.s)
console.log("Signing transaction");
const signedTx = new Tx(txData)
// BigNum lib cheatsheet https://github.com/indutny/bn.js/
const gasPrice = web3.utils.toBN(signedTx.gasPrice.toString('hex'))
const gasLimit = web3.utils.toBN(signedTx.gasLimit.toString('hex'))
const gasCostWei = gasPrice.mul(gasLimit)
const gasCost = web3.utils.fromWei(gasCostWei, 'ether')
console.log("Gas price:", gasPrice.toString(), "Gas limit:", gasLimit.toString(), "Max gas cost:", gasCost, "ETH")
console.log(signedTx, signedTx.toJSON(), signedTx.value, signedTx.getSenderAddress());
const signedSerializedTx = signedTx.serialize().toString('hex')
if(!signedTx.validate()) {
throw "Signature is not valid?"
}
const signedByAddress = "0x" + signedTx.getSenderAddress().toString('hex');
if(signedByAddress.toLowerCase() != addressData.address.toLowerCase()) {
console.lo
throw "Ledger signed transaction using wrong address:" + signedByAddress + " vs. " + addressData.address;
}
// Useful for debugging if signedSerializedTx gives broadcasting errors:
// https://flightwallet.org/decode-eth-tx/
console.log("Broadcasting the transaction", signedSerializedTx)
const txHash = await web3.eth.sendSignedTransaction('0x' + signedSerializedTx)
console.log("Broadcasted the transaction, view https://etherscan.io/tx/" + txHash)
}
main()

For any questions contact me https://twitter.com/moo9000 or mikko@opensourcehacker.com

Get Node and yarn from Homebrew

brew install yarn

Pull in dependencies with Yarn

  # Ledger deps
  yarn add @ledgerhq/hw-transport-node-hid
  yarn add @ledgerhq/hw-app-eth

  # Ethereum JS core
  yarn add ethereumjs-tx
  yarn add web3

  # Some JavaScript source code transformers needed for the JavaScript features that are used
  # by JavaScript packages above, but not yet supported by Node:
  yarn add babel-cli
  yarn add babel-plugin-transform-runtime
  yarn add @babel/node

Get a personal Ethereum node endpoint from infura.io.

Get the nonce of stuck transaction from EtherScan.io.

Create and edit file main.js.

Run the script:

npx babel-node main.js

Watch EtherScan.io to see that the stuck transaction gets replaced with new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment