// Returns false if no withdrawals are available to process. func ProcessUserWithdrawals(coin string) (bool, error) { // Checkout withdrawals // TODO: Gather multiple small withdrawals. wths := account.CheckoutWithdrawals(coin, 1) if len(wths) == 0 { return false, nil } wthIds := Map(wths, "Id") amounts := map[string]uint64{} amountSum := uint64(0) for _, wth := range wths { if wth.Amount <= 0 { panic(NewError("Invalid send amount %v", wth.Amount)) } amounts[wth.ToAddress] += uint64(wth.Amount) amountSum += uint64(wth.Amount) } // figure out which payments to use. signedTx, payments, minerFees, chgAddress, err := ComputeWithdrawalTransaction(coin, amounts) if err != nil { account.StallWithdrawals(wthIds) return false, err } paymentIds := Map(payments, "Id") // save withdrawal info for bookkeeping. wthTx := SaveWithdrawalTx(&WithdrawalTx{ Coin: coin, Type: WITHDRAWAL_TX_TYPE_WITHDRAWAL, Amount: amountSum, MinerFee: minerFees, ChgAddress: chgAddress, RawTx: signedTx, TxId: bitcoin.ComputeTxId(signedTx), }) // checkout those payments. bitcoin.CheckoutPaymentsToSpend(paymentIds, wthTx.Id) // TODO: the Tx should go out to our partners who sign them for us. // TODO: receive the signed Tx. // deduct change amount from system user's "change" wallet. // this creates a negative balance, which will revert to zero // when the change is received. if chgAddress != "" { changeAmount := amounts[chgAddress] err := db.DoBeginSerializable(func(tx *db.ModelTx) { account.UpdateBalanceByWallet(tx, 0, account.WALLET_CHANGE, coin, -int64(changeAmount), false) }) if err != nil { panic(err) } } // broadcast transaction. rpc.SendRawTransaction(coin, signedTx) // update payments as spent. bitcoin.MarkPaymentsAsSpent(paymentIds, wthTx.Id) // update withdrawals as complete. account.CompleteWithdrawals(wths, wthTx.Id) return true, nil }
func main() { // Input parameters var coin = flag.String("coin", "", "Coin to sweep") var inMPKPubKey = flag.String("in_mpk", "", "Input MPK pubkey") var minInput = flag.Uint64("min_input", 0, "Minimum permittable input amount") var maxInput = flag.Uint64("max_input", math.MaxInt64, "Maximum permittable input amount") var maxTotal = flag.Uint64("max_total", 0, "Maximum amount of coins to move") var maxNumInputs = flag.Int("max_num_inputs", 10, "Maximum number of inputs per sweep transaction") var dryRun = flag.Bool("dry", true, "Run a dry run") // Output parameters var outMPKPubKey = flag.String("out_mpk", "", "Output MPK pubkey") var minOutput = flag.Uint64("min_output", 0, "Minimum permittable output amount") var maxOutput = flag.Uint64("max_output", math.MaxInt64, "Maximum permittable output amount") var maxNumOutputs = flag.Int("max_num_outputs", 10, "Maximum number of outputs per sweep transaction") flag.Parse() fmt.Printf(`Input parameters: coin: %v in_mpk: %v min_input: %v max_input: %v max_total: %v max_num_inputs: %v dry: %v Output parameters: out_mpk: %v min_output: %v max_output: %v max_num_outputs %v `, *coin, *inMPKPubKey, *minInput, *maxInput, *maxTotal, *maxNumInputs, *dryRun, *outMPKPubKey, *minOutput, *maxOutput, *maxNumOutputs) if *coin == "" { log.Panicf("Invalid coin. Wanted: BTC, LTC, etc.") } inMPK := bitcoin.LoadMPKByPubKey(*inMPKPubKey) if inMPK == nil { log.Panicf("Invalid in_mpk_pubkey") } outMPK := bitcoin.LoadMPKByPubKey(*outMPKPubKey) if outMPK == nil { log.Panicf("Invalid out_mpk_pubkey") } fmt.Println("Enter in_mpk_privkey:") inMPKPrivKey := string(gopass.GetPasswd()) treasury.StorePrivateKeyForMPKPubKey(*inMPKPubKey, inMPKPrivKey) // Select a bunch of inputs for sweeping. inputs, total, err := treasury.CollectSweepInputs(*coin, inMPK, *minInput, *maxInput, *maxTotal, *maxNumInputs) if err != nil { log.Panicf("Error in CollectSweepInputs: %v", err) } inputIds := Map(inputs, "Id") fmt.Printf("Found %v inputs (total: %v)\n", len(inputs), UI64ToF64(total)) for _, input := range inputs { fmt.Printf(" %v\t%v %v\n", input.Address, input.Coin, UI64ToF64(input.Amount)) } // Create sweep transaction. signedTx, _, minerFee, outputs, err := treasury.ComputeSweepTransaction(inputs, outMPK, *minOutput, *maxOutput, *maxNumOutputs, *dryRun) if err != nil { log.Panicf("Error in ComputeSweepTransaction: %v", err) } fmt.Printf("Computed signed sweep transaction (minerFee: %v)\n", UI64ToF64(minerFee)) sum := uint64(minerFee) for addr, amount := range outputs { fmt.Printf(" %v:\t%v\n", addr, UI64ToF64(amount)) sum += amount } fmt.Printf("Total: %v\n", UI64ToF64(sum)) if *dryRun { fmt.Println("Dry run complete") return } // DRY RUN ENDS HERE // DRY RUN ENDS HERE // save WithdrawalTx for bookkeeping wthTx := treasury.SaveWithdrawalTx(&treasury.WithdrawalTx{ Coin: *coin, Type: treasury.WITHDRAWAL_TX_TYPE_SWEEP, Amount: total, MinerFee: minerFee, RawTx: signedTx, TxId: bitcoin.ComputeTxId(signedTx), }) // checkout those payments. bitcoin.CheckoutPaymentsToSpend(inputIds, wthTx.Id) // broadcast transaction. err = bitcoin.SendRawTransaction(*coin, signedTx) if err != nil { panic(err) } // update payments as spent. bitcoin.MarkPaymentsAsSpent(inputIds, wthTx.Id) fmt.Println("Success! Expected TxId: %v (but may change due to malleability)", wthTx.TxId) }
func makeHash() { rawtx := "0100000001058f9896490e89664f599fb0e89a17da744749df316a0b7bbceb1169e2fb5879010000006a473044022064795e01493db0e09d0795c5f74d4e5b0e13889704cbcb5e49bd24343cfeecf80220208882fba54276cab98963f4667a7b19b468054b95f9ff828fa2a95abec7b694012102505ca2ceb2157aaf6205eb4b546833acd7308a80750a73229256b8d2f7c16e0cffffffff0200a014e3322600001976a9140d4040b14280779b6a084751d581a305b9046e0488ac00204aa9d10100001976a91414e07c0ead3d436f904c18ff72c1ef862dbf17d488ac00000000" txId := bitcoin.ComputeTxId(rawtx) Info("txid: %v", txId) }