1

I have a list of bitcoin addresses and private keys, I need to write an automation script which checks for the balance in each address and transfer the balance funds to another bitcoin address. My question is how I perform the fund transfer with address and privateKey using nodejs

Thanks in Advance

3
  • i.e. "How do I steal bitcoin from accounts I've hacked..."
    – JBaczuk
    Sep 10, 2018 at 13:38
  • No, it's not stealing bitcoins. I have the list of accounts and privateKeys that I have generated. I am using these accounts for receiving funds. For privacy reasons, I keep on switching the incoming address and monitor all the address and if any funds deposited on these address I move those funds to my hot wallet address. Sep 10, 2018 at 15:14
  • Oh, ok :). Your use case is exactly why HD wallets (BIP32) were introduced. You can generate many addresses from a single private key and not have to store a list of private keys.
    – JBaczuk
    Sep 10, 2018 at 15:38

2 Answers 2

0

Given a list of bitcoin private keys, first you need to check the balance of the corresponding address, probably best done using a block explorer, e.g. blockchain.info:

Single Address

https://blockchain.info/rawaddr/$bitcoin_address
Address can be base58 or hash160

Optional limit parameter to show n transactions e.g. &limit=50 (Default: 50, Max: 50)
Optional offset parameter to skip the first n transactions e.g. &offset=100 (Page 2 for limit 50)

{
    "hash160":"660d4ef3a743e3e696ad990364e555c271ad504b",
    "address":"1AJbsFZ64EpEfS5UAjAfcUG8pH8Jn3rn1F",
    "n_tx":17,
    "n_unredeemed":2,
    "total_received":1031350000,
    "total_sent":931250000,
    "final_balance":100100000,
    "txs":[--Array of Transactions--]
}

Then, you can use bitcoinjs-lib, for example, to create a raw transaction for each private key:

const alice = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy')
const txb = new bitcoin.TransactionBuilder()

txb.setVersion(1)
txb.addInput('61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', 0) // Alice's previous transaction output, has 15000 satoshis
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000)
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee

txb.sign(0, alice)

For which, you will need unspent outputs to each private key's address.

You will also need to broadcast the raw transaction:

// build and broadcast our RegTest network
regtestUtils.broadcast(txb.build().toHex(), done)
0
0

I had a similar issue for a store I had where I was accepting bitcoin. I ended up creating a nodejs app that would take the input of just a private key and an address. It would gather all the UTXOs that belong to the private key using a third party API, and then send ALL funds to the address specified. Back then I was using bitcore-lib, but that library is now outdated, I would use bitcoin-js if I re-did it today.

These are the basic functions I used:

app.post("/address", function(req,res){
    var pkey = req.body.pkey;
    var addr = req.body.addy;

    validateAddress(addr, function(isAddyValid){
        if(isAddyValid == 0){
          res.render("pages/index.ejs", {
            outMessage: "Destination address invalid"
          });  
        } else {
            validatePrivateKey(pkey, function(isPkValid){
                if(isPkValid == 0){
                    res.render("pages/index.ejs", {
                    outMessage: "Private Key invalid"
                    });
                } else {
                    convertPK(pkey, function(convertedAddy){
                        getUTXO(convertedAddy, function(result, feeAmt, totalToSend){
                            if(result == 1){
                                res.render("pages/index.ejs", {
                                    outMessage: "No UTXO"
                                });     
                            } else if(result == 2){
                                res.render("pages/index.ejs", {
                                    outMessage: "Source offline"
                                });
                            } else if(result == 3){
                                res.render("pages/index.ejs", {
                                    outMessage: "Insufficient funds to pay fee"
                                });
                            } else {
                                buildTX(result, feeAmt, totalToSend, pkey, addr, function(payloadTx){
                                    pushTX(payloadTx, function(txdone){
                                        if(txdone !== 1){
                                            res.render("pages/index.ejs", {
                                            outMessage: "TX ID: " + txdone
                                            });
                                        } else {
                                            res.render("pages/index.ejs", {
                                            outMessage: "broadcast failed try later"
                                            });
                                        }
                                    });
                                });
                            }
                        });
                    });
                }
            });//end validatePrivateKey
        }
    });//end validate address
}); //end app post

function validateAddress(output, result){
    addyValue = output.replace(/[^\w\s]/gi, '');
    if(bitcore.Address.isValid(addyValue)){
    result(1);
    } else {
    result(0);    
    };
};

//convert pk to addr
function convertPK(pkeyValue, result){
    var address = new bitcore.PrivateKey(pkeyValue).toAddress();
    result(address);
};

function validatePrivateKey(wif, result){
pkeyValue = wif.replace(/[^\w\s]/gi, '');
        if(bitcore.PrivateKey.isValid(pkeyValue)){
        //private key is valid
        result(1);
        } else {
        result(0);    
        };
};

//get outputs
function getUTXO(address, callback){
    request({
        url: "https://chain.so/api/v2/get_tx_unspent/btc/"+address,
        json: true
    }, function(error, response, body){
        if(!error && response.statusCode == 200){
            if(body.data.txs.length < 1){
                //no utxos
                console.log("no utxo");
                var err = 1;
                callback(err);
            }
            var status = body.status;
            var num = body.data.txs.length;
            var utxos = [];
            var totalSats = 0;  
            var txSize = 44;
                //loop through all UTXOs
                for(i=0;i < num; i++){
            var convertSats = body.data.txs[i].value * 100000000;
            convertSats = parseInt(convertSats);

                    var utxo = {
                    "txId": body.data.txs[i].txid,
                    "outputIndex": body.data.txs[i].output_no,
                    "address": address,
                    "script": body.data.txs[i].script_hex,
                    "satoshis": convertSats
                    };
            utxos.push(utxo);
            totalSats = totalSats + convertSats;
            //calc tx size for fee
            txSize = txSize + 180;
                }; //end utxo loop
            getBestFee(function(bestHourFee){
                var fee = txSize * bestHourFee;
                totalSats = totalSats - fee;
                console.log(totalSats);
                console.log(fee);
                    if(totalSats < 1){
                        //not enough funds to send
                        var err = 3;
                        callback(err);
                    } else {
                        callback(utxos, fee, totalSats);  
                    }
            });            
       } else {
           //err or no response from api
           console.log("no response from api");
            var error = 2;
           callback(error);
       }
    });
};

//build transaction
function buildTX(utxo, fee, total, pkeyValue, output, callback){

        var transaction = new bitcore.Transaction()
        .from(utxo)
        .to(output, total)
        .sign(pkeyValue);

        //payload to push tx
        var txjson = transaction.toString();
        var pload = {
            "tx": txjson
        };
        callback(pload);  
};


//push transaction
function pushTX(pload, callback){
    request({
    url: "https://api.blockcypher.com/v1/btc/main/txs/push",
    method: "POST",
    json: true,
    headers: {"content-type": "application/json"},
    body: pload
    }, function(err, response, body){
            if(err){ 
         //no response or error POST to chainso
             callback(1);    
        } else {
                console.log(JSON.stringify(body));
                completeTxId = body.tx.hash;
                console.log("done");
                callback(completeTxId);
            };                
    });
};

function getBestFee(bestFee){
  var findfee = "https://bitcoinfees.21.co/api/v1/fees/recommended";
    request({
        url: findfee,
        json: true
    }, function(error, response, body){
        if(!body.hourFee){
            //no response from api use 150 sats per byte
            var fee = 150;
            bestFee(fee);
        }
        if(body.hourFee){
            var fee = body.hourFee;
            fee = fee * 0.5;
            fee = Math.ceil(fee);
            bestFee(fee);
        }
    });  
}

If you want to see the entire project here's the repo, it's a simple nodejs express app with a form submission POST option and a GET endpoint /sweep option. Be aware the way I wrote it, it will only work with legacy P2PKH addresses (addresses that start with a 1), no segwit.

https://github.com/coinables/sweepkey

2
  • Sending private key through API post request body is not a good approach. Sep 14, 2018 at 9:06
  • you're using api.blockcypher.com/v1/btc/main/txs/push to post transactions. what is the testnet version of this? I tried test and testnet but both fail
    – ekkis
    Jan 7, 2020 at 7:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.