// txToOutputs creates a signed transaction which includes each output from // outputs. Previous outputs to reedeem are chosen from the passed account's // UTXO set and minconf policy. An additional output may be added to return // change to the wallet. An appropriate fee is included based on the wallet's // current relay fee. The wallet must be unlocked to create the transaction. func (w *Wallet) txToOutputs(outputs []*wire.TxOut, account uint32, minconf int32) (*txauthor.AuthoredTx, error) { // Address manager must be unlocked to compose transaction. Grab // the unlock if possible (to prevent future unlocks), or return the // error if already locked. heldUnlock, err := w.HoldUnlock() if err != nil { return nil, err } defer heldUnlock.Release() chainClient, err := w.requireChainClient() if err != nil { return nil, err } // Get current block's height and hash. bs, err := chainClient.BlockStamp() if err != nil { return nil, err } eligible, err := w.findEligibleOutputs(account, minconf, bs) if err != nil { return nil, err } inputSource := makeInputSource(eligible) changeSource := func() ([]byte, error) { // Derive the change output script. As a hack to allow spending from // the imported account, change addresses are created from account 0. var changeAddr btcutil.Address if account == waddrmgr.ImportedAddrAccount { changeAddr, err = w.NewChangeAddress(0, waddrmgr.WitnessPubKey) } else { changeAddr, err = w.NewChangeAddress(account, waddrmgr.WitnessPubKey) } if err != nil { return nil, err } return txscript.PayToAddrScript(changeAddr) } tx, err := txauthor.NewUnsignedTransaction(outputs, w.RelayFee(), inputSource, changeSource) if err != nil { return nil, err } // Randomize change position, if change exists, before signing. This // doesn't affect the serialize size, so the change amount will still be // valid. if tx.ChangeIndex >= 0 { tx.RandomizeChangePosition() } err = tx.AddAllInputScripts(secretSource{w.Manager}) if err != nil { return nil, err } err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues) if err != nil { return nil, err } if tx.ChangeIndex >= 0 && account == waddrmgr.ImportedAddrAccount { changeAmount := btcutil.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value) log.Warnf("Spend from imported account produced change: moving"+ " %v from imported account into default account.", changeAmount) } return tx, nil }
func sweep() error { rpcPassword, err := promptSecret("Wallet RPC password") if err != nil { return errContext(err, "failed to read RPC password") } // Open RPC client. rpcCertificate, err := ioutil.ReadFile(opts.RPCCertificateFile) if err != nil { return errContext(err, "failed to read RPC certificate") } rpcClient, err := btcrpcclient.New(&btcrpcclient.ConnConfig{ Host: opts.RPCConnect, User: opts.RPCUsername, Pass: rpcPassword, Certificates: rpcCertificate, HTTPPostMode: true, }, nil) if err != nil { return errContext(err, "failed to create RPC client") } defer rpcClient.Shutdown() // Fetch all unspent outputs, ignore those not from the source // account, and group by their destination address. Each grouping of // outputs will be used as inputs for a single transaction sending to a // new destination account address. unspentOutputs, err := rpcClient.ListUnspent() if err != nil { return errContext(err, "failed to fetch unspent outputs") } sourceOutputs := make(map[string][]btcjson.ListUnspentResult) for _, unspentOutput := range unspentOutputs { if !unspentOutput.Spendable { continue } if unspentOutput.Confirmations < opts.RequiredConfirmations { continue } if unspentOutput.Account != opts.SourceAccount { continue } sourceAddressOutputs := sourceOutputs[unspentOutput.Address] sourceOutputs[unspentOutput.Address] = append(sourceAddressOutputs, unspentOutput) } var privatePassphrase string if len(sourceOutputs) != 0 { privatePassphrase, err = promptSecret("Wallet private passphrase") if err != nil { return errContext(err, "failed to read private passphrase") } } var totalSwept btcutil.Amount var numErrors int var reportError = func(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, format, args...) os.Stderr.Write(newlineBytes) numErrors++ } for _, previousOutputs := range sourceOutputs { inputSource := makeInputSource(previousOutputs) destinationSource := makeDestinationScriptSource(rpcClient, opts.DestinationAccount) tx, err := txauthor.NewUnsignedTransaction(nil, opts.FeeRate.Amount, inputSource, destinationSource) if err != nil { if err != (noInputValue{}) { reportError("Failed to create unsigned transaction: %v", err) } continue } // Unlock the wallet, sign the transaction, and immediately lock. err = rpcClient.WalletPassphrase(privatePassphrase, 60) if err != nil { reportError("Failed to unlock wallet: %v", err) continue } signedTransaction, complete, err := rpcClient.SignRawTransaction(tx.Tx) _ = rpcClient.WalletLock() if err != nil { reportError("Failed to sign transaction: %v", err) continue } if !complete { reportError("Failed to sign every input") continue } // Publish the signed sweep transaction. txHash, err := rpcClient.SendRawTransaction(signedTransaction, false) if err != nil { reportError("Failed to publish transaction: %v", err) continue } outputAmount := btcutil.Amount(tx.Tx.TxOut[0].Value) fmt.Printf("Swept %v to destination account with transaction %v\n", outputAmount, txHash) totalSwept += outputAmount } numPublished := len(sourceOutputs) - numErrors transactionNoun := pickNoun(numErrors, "transaction", "transactions") if numPublished != 0 { fmt.Printf("Swept %v to destination account across %d %s\n", totalSwept, numPublished, transactionNoun) } if numErrors > 0 { return fmt.Errorf("Failed to publish %d %s", numErrors, transactionNoun) } return nil }