// Given inputs and outputs, and given that the maximum fee has already been deducted // such that sum(inputs) - maximum_fee = sum(outputs), // Adjust outputs such that leftover fees go into changeAddress. // To prevent dust, if outputs[changeAddress] ends up being dust, // it is omitted. // 'privKeys' will be updated include all the private keys for output addresses. func adjustMinerFee(coin string, inputs []*bitcoin.Payment, outputs map[string]uint64, changeAddress string, privKeys map[string]string) (uint64, error) { c := Config.GetCoin(coin) maxMinerFee := maxMinerFeeForCoin(coin) inputSum := sumInputs(inputs) outputSum := sumOutputs(outputs) if inputSum < outputSum+maxMinerFee { return 0, NewError("Inputs didn't exceed outputs + maximum miner fee") } collectPrivateKeys(coin, inputs, privKeys) // Make RPC call to sign. s := rpc.CreateSignedRawTransaction(coin, bitcoin.ToRPCPayments(inputs), outputs, privKeys) // Figure out how many base fees we actually need. numKBytes := len(s) / 2 / 1000 requiredBaseFees := numKBytes + 1 if requiredBaseFees > MAX_BASE_FEES { return 0, NewError("Whoa, too many base fees required: %v (max %v)", requiredBaseFees, MAX_BASE_FEES) } requiredFee := uint64(requiredBaseFees) * uint64(c.MinerFee) // Add remainder back to changeAddress if maxMinerFee > requiredFee { outputs[changeAddress] += (maxMinerFee - requiredFee) } // Remove dust if outputs[changeAddress] < c.MinerFee { delete(outputs, changeAddress) } return requiredFee, nil }
// Sweeps inputs into new address(s) generated for mpk. // - outMPK: Set outMPK to offline wallet MPK to move funds offline // - minOutput, maxOutput: Range of permittable output transaction sizes. // The outputs will be spread out evenly between minOutput & maxOutput linearly // though if maxOutput is greater than the sum of all the inputs minus fees, // there will only be one output. // This means you could set maxOutput to MaxInt64 and you'll be guaranteed to have one output. // - dry: dry run. The output addresses will be throwaway addresses. func ComputeSweepTransaction(inputs []*bitcoin.Payment, outMPK *bitcoin.MPK, minOutput, maxOutput uint64, maxNumOutputs int, dry bool) (string, []*bitcoin.Payment, uint64, map[string]uint64, error) { returnErr := func(err error) (string, []*bitcoin.Payment, uint64, map[string]uint64, error) { return "", nil, 0, nil, err } var coin = inputs[0].Coin for _, payment := range inputs { if payment.Coin != coin { return returnErr(NewError("Expected all sweep inputs to be for coin %v", coin)) } } // compute total inputs total := uint64(0) for _, payment := range inputs { total += payment.Amount } // compute sweep output spread outputAmounts, ok := computeSweepOutputs(total, minOutput, maxOutput, maxNumOutputs) if !ok { return returnErr(NewError("Could not satisfy output requirements")) } // Remove maxMinerFees from output initially. // We'll readjust later outputAmounts[0] -= maxMinerFeeForCoin(coin) // Adjust miner fees & collect private keys outputs := map[string]uint64{} changeAddress := "" // not really a change address, but remaining fees get added back here. for _, amount := range outputAmounts { address := createNewSweepAddress(coin, outMPK, dry) if changeAddress == "" { changeAddress = address } outputs[address] = amount } privKeys := map[string]string{} minerFee, err := adjustMinerFee(coin, inputs, outputs, changeAddress, privKeys) if err != nil { return returnErr(err) } // Sign transaction s := rpc.CreateSignedRawTransaction(coin, bitcoin.ToRPCPayments(inputs), outputs, privKeys) return s, inputs, minerFee, outputs, nil }
// Finds payments & constructs transaction to satisfy the given output amounts. // This function could fail, in which case we'll call it again later. // This means that this function should be largely side-effect free. // However, note that 'outputs' may be modified to account for // fees & change addresses. func ComputeWithdrawalTransaction(coin string, outputs map[string]uint64) (string, []*bitcoin.Payment, uint64, string, error) { returnErr := func(err error) (string, []*bitcoin.Payment, uint64, string, error) { return "", nil, 0, "", err } reqHeight := bitcoin.ReqHeight(coin) payments := []*bitcoin.Payment{} changeAddress := createNewChangeAddress(coin) sumAmount := int64(0) for _, amount := range outputs { sumAmount += int64(amount) } // Add base fees to sumAmount. Be generous, we'll adjust later. sumAmount += int64(bitcoin.MinerFee(coin)) * MAX_BASE_FEES sumAmountCopy := sumAmount // Then load payments greater than remainder for sumAmountCopy > 0 { // We shouldn't use too many inputs. if len(payments) > len(outputs)*2 { return returnErr(NewError("[%v] Too many inputs required for %v", coin, sumAmount)) } // Try to do it with one input payment := bitcoin.LoadSmallestSpendablePaymentGreaterThan(hotMPK.Id, coin, uint64(sumAmountCopy), reqHeight, payments) if payment == nil { // Try to fill it as much as possible payment = bitcoin.LoadLargestSpendablePaymentLessThan(hotMPK.Id, coin, uint64(sumAmountCopy), reqHeight, payments) } if payment == nil { return returnErr(NewError("[%v] Unable to gather enough inputs for %v", coin, sumAmount)) } sumAmountCopy -= int64(payment.Amount) payments = append(payments, payment) } // If we need to create a change address, do so. if sumAmountCopy != 0 { outputs[changeAddress] = uint64(-1 * sumAmountCopy) } // Adjust miner fees & collect private keys privKeys := map[string]string{} minerFee, err := adjustMinerFee(coin, payments, outputs, changeAddress, privKeys) if err != nil { return returnErr(err) } // Sign transaction s := rpc.CreateSignedRawTransaction(coin, bitcoin.ToRPCPayments(payments), outputs, privKeys) return s, payments, minerFee, changeAddress, nil }