// Collects up to maxTotal coins of tx outs between minInput and maxInput coins in size. // - inMPK: Set inMPK to hotMPK to move funds offline from the hot wallet. // - minInput, maxInput: Range of permittable input transaction sizes. // - maxTotal: Maximum amount of coins to collect. // NOTE: should be deterministic, because of dryRun options. func CollectSweepInputs(coin string, inMPK *bitcoin.MPK, minInput, maxInput, maxTotal uint64, maxNumInputs int) ([]*bitcoin.Payment, uint64, error) { reqHeight := bitcoin.ReqHeight(coin) if minInput < bitcoin.MinerFee(coin) { return nil, 0, NewError("minInput must be at least as large as the miner fee for %v: %v", coin, maxMinerFeeForCoin(coin)) } // Gather oldest spendable inputs in range until maxTotal. candidates := bitcoin.LoadOldestSpendablePaymentsBetween(inMPK.Id, coin, minInput, maxInput, maxNumInputs, reqHeight) inputs := []*bitcoin.Payment{} total := uint64(0) for _, payment := range candidates { if total+payment.Amount > maxTotal { continue } total += payment.Amount inputs = append(inputs, payment) } // total must meet some threshold for sweep to be worth it. if total < maxMinerFeeForCoin(coin) { return nil, 0, NewError("Could not gather enough inputs. Needed at least %v, only got %v", maxMinerFeeForCoin(coin), total) } return inputs, total, nil }
func GetSpendablePayments(w http.ResponseWriter, r *http.Request, user *auth.User) { if !user.HasRole("treasury") { ReturnJSON(API_UNAUTHORIZED, UNAUTH_MSG) } // All handlers here should have this. mpkId := GetParamInt64(r, "mpk_id") coin := GetParamRegexp(r, "coin", RE_COIN, false) min := GetParamUint64(r, "min") max := GetParamUint64(r, "max") limit := GetParamInt32(r, "limit") reqHeight := bitcoin.ReqHeight(coin) payments := bitcoin.LoadSpendablePaymentsByAmount(mpkId, coin, min, max, reqHeight, uint(limit)) ReturnJSON(API_OK, payments) }
// 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 }