package main import ( "bytes" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "log" "net/http" "os/exec" "path" "strings" "github.com/d4l3k/go-electrum/electrum" ) // NOTE: This was initially hacked together for a demo. It doesn't do proper // error checking etc. If this comment is still here it's probably not ready // yet. const PathBroadcastTransaction = "/transaction/broadcast" const PathDecodeTransaction = "/transaction/decode" const PathPSBT = "/transaction/psbt" var LBRYCRD = path.Join(LBRYCRD_PATH, "lbrycrd-cli") var node *electrum.Node func getUnspentForAmount( outputAddress string, desiredAmount float64, ) (inputs []*electrum.Transaction, totalAmount float64, enough bool) { unspent, _ := node.BlockchainAddressListUnspent(outputAddress) for _, u := range unspent { if desiredAmount > totalAmount { totalAmount += float64(u.Value) / 100000000.0 inputs = append(inputs, u) } } if desiredAmount <= totalAmount { enough = true } return } func makePsbt(inputs []*electrum.Transaction, outputAddress string, amount float64) (string, error) { var inputTxnParts []string for _, input := range inputs { inputTxnParts = append( inputTxnParts, fmt.Sprintf(`{"txid": "%s", "vout": %d}`, input.Hash, input.Pos), ) } var inputTxnsParam = "[" + strings.Join(inputTxnParts, ", ") + "]" var outputAddressesParam = fmt.Sprintf(`[{"%s": %f}]`, outputAddress, amount) var out bytes.Buffer var errOut bytes.Buffer fmt.Println(LBRYCRD, "createpsbt", string(inputTxnsParam), string(outputAddressesParam)) cmd := exec.Command(LBRYCRD, "createpsbt", string(inputTxnsParam), string(outputAddressesParam)) cmd.Stdout = &out cmd.Stderr = &errOut err := cmd.Run() if err != nil { fmt.Println(err.Error()) fmt.Println(out.String()) fmt.Println(errOut.String()) return "", err } else { psbtBytes, err := base64.StdEncoding.DecodeString(out.String()) if err != nil { fmt.Println(err.Error()) } return hex.EncodeToString(psbtBytes), nil } } func decodeTransaction(txn string) (decoded string, err error) { var out bytes.Buffer var errOut bytes.Buffer cmd := exec.Command(LBRYCRD, "decoderawtransaction", txn) cmd.Stdout = &out cmd.Stderr = &errOut err = cmd.Run() if err != nil { err = fmt.Errorf(err.Error() + "\n" + out.String() + "\n" + errOut.String()) } else { decoded = out.String() } return } func generateBlockRegtest() (cmdOut string, err error) { var out bytes.Buffer var errOut bytes.Buffer cmd := exec.Command(LBRYCRD, "generate", "1") cmd.Stdout = &out cmd.Stderr = &errOut err = cmd.Run() if err != nil { err = fmt.Errorf(err.Error() + "\n" + out.String() + "\n" + errOut.String()) } else { cmdOut = out.String() } return } func main() { // TODO Throw error if lbrycrdd is not running on mainnet node = electrum.NewNode() if err := node.ConnectTCP(ELECTRUM_SERVER); err != nil { log.Fatal(err) } http.HandleFunc(PathBroadcastTransaction, broadcastTransaction) http.HandleFunc(PathDecodeTransaction, getDecodedTransaction) http.HandleFunc(PathPSBT, getPSBT) fmt.Println("Serving at :8090") http.ListenAndServe(":8090", nil) } type BroadcastTransactionRequest struct { TransactionHex string `json:"transactionHex"` } type DecodeTransactionRequest struct { TransactionHex string `json:"transactionHex"` } type PSBTRequest struct { ToAddress string `json:"toAddress"` FromAddress string `json:"fromAddress"` DesiredAmount float64 `json:"desiredAmount"` } type PSBTResponse struct { NonWitnessUtxoHexes []string `json:"nonWitnessUtxoHex"` PSBTHex string `json:"psbtHex"` Error string `json:"error"` ActualAmount float64 `json:"actualAmount"` } func getPSBT(w http.ResponseWriter, req *http.Request) { var psbtr PSBTRequest var pr PSBTResponse if err := json.NewDecoder(req.Body).Decode(&psbtr); err != nil { http.Error(w, string("Malformed request body JSON"), http.StatusBadRequest) return } w.Header().Set("Access-Control-Allow-Origin", CORS) outputs, actualAmount, success := getUnspentForAmount(psbtr.FromAddress, psbtr.DesiredAmount) if !success { pr = PSBTResponse{Error: "Not enough funds"} response, _ := json.Marshal(pr) fmt.Fprintf(w, string(response)) return } // Doing the `desiredAmount` so that the remainder can go to fees. This // might lead to super high fees but the server would stop us from // broadcasting it. If we used `actualAmount`, we'd have zero fees. If we // used `actualAmount` minus fees, it might be way more than `desiredAmount` // and the server *wouldn't* stop us from broadcasting. psbt, err := makePsbt(outputs, psbtr.ToAddress, psbtr.DesiredAmount) if err != nil { pr = PSBTResponse{Error: "getPsbt - err: " + err.Error()} response, _ := json.Marshal(pr) http.Error(w, string(response), http.StatusInternalServerError) return } var fullTxns []string for _, output := range outputs { fullTxn, err := node.BlockchainTransactionGet(output.Hash) if err != nil { pr = PSBTResponse{Error: "getPsbt - err: " + err.Error()} response, _ := json.Marshal(pr) http.Error(w, string(response), http.StatusInternalServerError) return } fullTxns = append(fullTxns, fullTxn) } pr = PSBTResponse{ NonWitnessUtxoHexes: fullTxns, PSBTHex: psbt, ActualAmount: actualAmount, } // TODO - is this check future-proof enough? (change in prices, etc) if actualAmount-psbtr.DesiredAmount > 0.01 { pr.Error = "Absurdly high fee" } response, _ := json.Marshal(pr) fmt.Fprintf(w, string(response)) fmt.Println("get psbt") } type BroadcastTransactionResponse struct { Txid string `json:"txid"` } func broadcastTransaction(w http.ResponseWriter, req *http.Request) { var btr BroadcastTransactionRequest if err := json.NewDecoder(req.Body).Decode(&btr); err != nil { http.Error(w, string("Malformed request body JSON"), http.StatusBadRequest) return } // TODO need some sort of actual feedback for web app for errors w.Header().Set("Access-Control-Allow-Origin", CORS) broadcastResult, err := node.BlockchainTransactionBroadcast([]byte(btr.TransactionHex)) if err != nil { fmt.Println("broadcast failure") http.Error(w, err.Error(), http.StatusInternalServerError) return } broadcastResultStr := fmt.Sprintf("+v", broadcastResult) btresp := BroadcastTransactionResponse{Txid: broadcastResultStr} response, _ := json.Marshal(btresp) fmt.Fprintf(w, string(response)) fmt.Println("broadcast success:\n" + broadcastResultStr) return } type DecodeTransactionResponse struct { DecodedTransaction string `json:"decodedTransaction"` } func getDecodedTransaction(w http.ResponseWriter, req *http.Request) { var dtr DecodeTransactionRequest if err := json.NewDecoder(req.Body).Decode(&dtr); err != nil { http.Error(w, string("Malformed request body JSON"), http.StatusBadRequest) return } // TODO need some sort of actual feedback for web app for errors w.Header().Set("Access-Control-Allow-Origin", CORS) decodedTransaction, err := decodeTransaction(dtr.TransactionHex) if err != nil { fmt.Println("decode failure") http.Error(w, err.Error(), http.StatusInternalServerError) return } dtresp := DecodeTransactionResponse{ DecodedTransaction: decodedTransaction, } response, _ := json.Marshal(dtresp) fmt.Fprintf(w, string(response)) fmt.Printf(decodedTransaction) fmt.Println("decode success") return }