Let's suppose I have 100 unlocked accounts with different balances and I want to have in a target account an amount X.

Knowing that I have to make an individual transaction for every account, what would be a good approach to consider the best combination of transactions, taking into account the fees, that would result in a total X of ether transferred to the target account?


I made a heuristic approach for now. I'm sure it's not the most optimal, but it can help to find a better way to do it:

type AddressBalance struct {
    Address *common.Address
    Balance *big.Int

func (s *Ethereum) transferFromAvailableAccounts(addressStr string, targetAmount *big.Int, commonPassword string) error {

    log.Info("Multi-Transfer from available accounts", "to", addressStr, "amount", targetAmount.String())

    var accAPI        *ethapi.PublicAccountAPI  = ethapi.NewPublicAccountAPI(s.accountManager)
    var accPrivateAPI *ethapi.PrivateAccountAPI = ethapi.NewPrivateAccountAPI(s.APIBackend, new(ethapi.AddrLocker))

    accs := accAPI.Accounts()

    if len(accs) == 0 {
        return errors.New("No account available.")
    } else {
        log.Info("Current accounts", "number", len(accs))

    // Get the eth client
    client, ok := getClient()
    if !ok {
        return errors.New("Could not get ethclient.")

    // Gas and gas calculation
    gasPrice, err := client.SuggestGasPrice(context.Background())
    if err != nil {
        return errors.New(fmt.Sprintf("Gas price error. %s", err.Error()))

    log.Info("GasPrice", "value", gasPrice)

    gas := hexutil.Uint64(52500)
    fee := big.NewInt(0)
    fee.Mul(big.NewInt(52500), gasPrice)

    selectedAccounts := []AddressBalance{}

    accumulatedAmount := big.NewInt(0)
    totalAvailable := big.NewInt(0)

    big0 := big.NewInt(0)

    for i := 0; i < len(accs); i++ {

        address := accs[i].String()
        balance, balanceOk := getBalance(address)

        // If there was an error getting the balance
        if !balanceOk || balance == nil {
            log.Warn("Could not get balance in multi-transfer", "address", address)

        // If the balance is less than the required fee
        if balance.Cmp(fee) <= 0 {
            log.Warn("Not enough balance to make a transaction", "i", i, "address", address)

        // Determine the available balance
        availableBalance := big.NewInt(0)
        availableBalance.Sub(balance, fee)

        if availableBalance.Cmp(big0) <= 0 {
            log.Warn("Not enough balance to make a transaction", "i", i, "address", address)

        // Try to Unlock the account with default duration
        ok, err := accPrivateAPI.UnlockAccount(accs[i], commonPassword, nil)
        if !ok {
            log.Warn("Cannot Unlock account", "address", address, "error", err.Error())

        selectedAccounts = append(selectedAccounts, AddressBalance { Address : &accs[i], Balance: big.NewInt(0).Set(availableBalance) })
        totalAvailable.Add(totalAvailable, availableBalance)
        log.Info("Account pre-selected for transaction", "i", i, "address", address, "balance", availableBalance)

    // Check if sum of all accounts balances is less than the amount to send
    if totalAvailable.Cmp(targetAmount) < 0 {
        return errors.New("Not enough Eth.")

    finalTransactionsArgs := []ethapi.SendTxArgs{}
    remainingAmount := big.NewInt(0)

    // Send transactions until the amount is finished
    for i := 0; i < len(selectedAccounts) ; i++ {

        // Determine the amount to transfer from this account
        amountToSend := big.NewInt(0)

        remainingAmount.Sub(targetAmount, accumulatedAmount)
        if remainingAmount.Cmp(big0) <= 0 {

        if remainingAmount.Cmp(selectedAccounts[i].Balance) <= 0 {
        } else {

        address := common.HexToAddress(addressStr)
        nonce := (hexutil.Uint64)(i)

        args := &ethapi.SendTxArgs{
            From: *selectedAccounts[i].Address,
            To: &address,
            Gas: &gas,
            GasPrice: (*hexutil.Big)(gasPrice),
            Value: (*hexutil.Big)(amountToSend),
            Nonce: &nonce,

        args.SetDefaults(context.Background(), s.APIBackend)

        json2, _ := json.Marshal(args)
        log.Info("Transaction Args ", "", string(json2))

        finalTransactionsArgs = append(finalTransactionsArgs, *args)
        accumulatedAmount.Add(accumulatedAmount, amountToSend)

    if remainingAmount.Sub(targetAmount, accumulatedAmount).Cmp(big0) > 0 {

        log.Warn("The possible transactions did not fulfill the full requested amount.", "target", targetAmount, "set", accumulatedAmount, "left", remainingAmount)
        return errors.New("The possible transactions did not fulfill the full requested amount.")

    } else {

        log.Info("Sending transactions...", "count", len(finalTransactionsArgs), "targetAmount", targetAmount)

        for i := 0; i < len(finalTransactionsArgs); i++ {

            hash, txErr := accPrivateAPI.SendTransaction(context.Background(), finalTransactionsArgs[i], commonPassword)
            if txErr != nil {
                log.Error("There was an error sending the transaction.", "err", txErr.Error(), "address", finalTransactionsArgs[i].From, "amount", finalTransactionsArgs[i].Value.ToInt())
            } else {
                log.Info("Transaction sent", "n", i, "address", finalTransactionsArgs[i].From, "hash", hash.String(), "amount", finalTransactionsArgs[i].Value.ToInt())


        return nil

In Bitcoin, there are various coin selection strategies designed to reduce Utxos set & transaction fees due to the ability to transfer several Utxos in one transaction.

But Ethereum uses the account model so it's quite not quite possible, the best approach considering the dust issue (i.e the value transferred is less than the required transaction fees required to pay for it ) and paying fewer transaction fees would be to combine several large amounts together where the amount remaining is greater than the dust threshold configured

  • Thanks... I edited the question with the approach I'm using right now... maybe you could take a look – Carlos Fuentes Jul 19 '19 at 21:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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