277 lines
7.3 KiB
Go
277 lines
7.3 KiB
Go
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
|
|
}
|