227 lines
5.5 KiB
Go
Raw Normal View History

2019-10-01 12:22:30 -03:00
package libwallet
import (
2021-01-29 18:51:08 -03:00
"fmt"
2019-10-01 12:22:30 -03:00
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
2020-11-09 10:05:29 -03:00
"github.com/muun/libwallet/addresses"
2021-11-12 19:06:13 -03:00
"github.com/muun/libwallet/btcsuitew/btcutilw"
2021-01-29 18:51:08 -03:00
"github.com/muun/libwallet/errors"
2020-11-09 10:05:29 -03:00
2019-10-01 12:22:30 -03:00
"github.com/btcsuite/btcd/txscript"
2020-11-09 10:05:29 -03:00
"google.golang.org/protobuf/proto"
2019-10-01 12:22:30 -03:00
)
2019-12-16 17:59:11 -03:00
// These constants are here for clients usage.
const (
2021-11-12 19:06:13 -03:00
AddressVersionV1 = addresses.V1
AddressVersionV2 = addresses.V2
AddressVersionV3 = addresses.V3
AddressVersionV4 = addresses.V4
AddressVersionV5 = addresses.V5
2020-11-09 10:05:29 -03:00
AddressVersionSwapsV1 = addresses.SubmarineSwapV1
AddressVersionSwapsV2 = addresses.SubmarineSwapV2
2019-12-16 17:59:11 -03:00
)
2019-10-01 12:22:30 -03:00
// MuunPaymentURI is muun's uri struct
type MuunPaymentURI struct {
Address string
Label string
Message string
Amount string
2022-03-16 11:14:47 -03:00
Uri string
Bip70Url string
2019-10-01 12:22:30 -03:00
CreationTime string
ExpiresTime string
Invoice *Invoice
}
const (
bitcoinScheme = "bitcoin:"
muunScheme = "muun:"
)
2019-12-17 17:32:12 -03:00
// GetPaymentURI builds a MuunPaymentURI from text (Bitcoin Uri, Muun Uri or address) and a network
func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) {
2019-10-01 12:22:30 -03:00
2020-11-09 10:05:29 -03:00
bitcoinUri, components := buildUriFromString(rawInput, bitcoinScheme)
if components == nil {
2021-01-29 18:51:08 -03:00
return nil, errors.Errorf(ErrInvalidURI, "failed to parse uri %v", rawInput)
2019-10-01 12:22:30 -03:00
}
if components.Scheme != "bitcoin" {
2021-01-29 18:51:08 -03:00
return nil, errors.New(ErrInvalidURI, "Invalid scheme")
2019-10-01 12:22:30 -03:00
}
2021-11-12 19:06:13 -03:00
address := components.Opaque
2019-10-01 12:22:30 -03:00
// When URIs are bitcoin:// the address comes in host
// this happens in iOS that mostly ignores bitcoin: format
2021-11-12 19:06:13 -03:00
if len(address) == 0 {
address = components.Host
2019-10-01 12:22:30 -03:00
}
queryValues, err := url.ParseQuery(components.RawQuery)
if err != nil {
2021-01-29 18:51:08 -03:00
return nil, errors.Errorf(ErrInvalidURI, "Couldn't parse query: %v", err)
2019-10-01 12:22:30 -03:00
}
var label, message, amount string
if len(queryValues["label"]) != 0 {
label = queryValues["label"][0]
}
if len(queryValues["message"]) != 0 {
message = queryValues["message"][0]
}
if len(queryValues["amount"]) != 0 {
amount = queryValues["amount"][0]
}
if len(queryValues["lightning"]) != 0 {
invoice, err := ParseInvoice(queryValues["lightning"][0], network)
if err == nil {
2019-12-16 17:59:11 -03:00
return &MuunPaymentURI{Invoice: invoice}, nil
2019-10-01 12:22:30 -03:00
}
}
2022-03-16 11:14:47 -03:00
// legacy Apollo P2P/contacts check
if (strings.Contains(rawInput, "contacts/")) {
return &MuunPaymentURI{
Label: label,
Message: message,
Amount: amount,
Uri: bitcoinUri,
}, nil
}
2019-10-01 12:22:30 -03:00
//BIP70 check
if len(queryValues["r"]) != 0 {
2021-11-12 19:06:13 -03:00
if len(address) > 0 {
2019-10-01 12:22:30 -03:00
return &MuunPaymentURI{
2021-11-12 19:06:13 -03:00
Address: address,
2019-10-01 12:22:30 -03:00
Label: label,
Message: message,
Amount: amount,
2022-03-16 11:14:47 -03:00
Uri: bitcoinUri,
Bip70Url: queryValues["r"][0],
2019-10-01 12:22:30 -03:00
}, nil
}
2022-03-16 11:14:47 -03:00
2019-10-01 12:22:30 -03:00
return &MuunPaymentURI{
Label: label,
Message: message,
Amount: amount,
2022-03-16 11:14:47 -03:00
Uri: bitcoinUri,
Bip70Url: queryValues["r"][0],
2019-10-01 12:22:30 -03:00
}, nil
}
// Bech32 check
2021-11-12 19:06:13 -03:00
decodedAddress, err := btcutilw.DecodeAddress(address, network.network)
2019-10-01 12:22:30 -03:00
if err != nil {
2021-01-29 18:51:08 -03:00
return nil, fmt.Errorf("invalid address: %w", err)
2019-10-01 12:22:30 -03:00
}
2021-11-12 19:06:13 -03:00
if !decodedAddress.IsForNet(network.network) {
2021-01-29 18:51:08 -03:00
return nil, errors.New(ErrInvalidURI, "Network mismatch")
2019-10-01 12:22:30 -03:00
}
return &MuunPaymentURI{
2021-11-12 19:06:13 -03:00
Address: decodedAddress.String(),
2019-10-01 12:22:30 -03:00
Label: label,
Message: message,
Amount: amount,
2022-03-16 11:14:47 -03:00
Uri: bitcoinUri,
2019-10-01 12:22:30 -03:00
}, nil
}
// DoPaymentRequestCall builds a MuunPaymentUri from a url and a network. Handling BIP70 to 72
func DoPaymentRequestCall(url string, network *Network) (*MuunPaymentURI, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
2021-01-29 18:51:08 -03:00
return nil, fmt.Errorf("failed to create request to: %s", url)
2019-10-01 12:22:30 -03:00
}
req.Header.Set("Accept", "application/bitcoin-paymentrequest")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
2021-01-29 18:51:08 -03:00
return nil, errors.Errorf(ErrNetwork, "failed to make request to: %s", url)
2019-10-01 12:22:30 -03:00
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
2021-01-29 18:51:08 -03:00
return nil, errors.Errorf(ErrNetwork, "Failed to read body response: %w", err)
2019-10-01 12:22:30 -03:00
}
payReq := &PaymentRequest{}
err = proto.Unmarshal(body, payReq)
if err != nil {
2021-01-29 18:51:08 -03:00
return nil, fmt.Errorf("failed to unmarshal payment request: %w", err)
2019-10-01 12:22:30 -03:00
}
payDetails := &PaymentDetails{}
err = proto.Unmarshal(payReq.SerializedPaymentDetails, payDetails)
if err != nil {
2021-01-29 18:51:08 -03:00
return nil, fmt.Errorf("failed to unmarshall payment details: %w", err)
2019-10-01 12:22:30 -03:00
}
if len(payDetails.Outputs) == 0 {
2021-01-29 18:51:08 -03:00
return nil, fmt.Errorf("no outputs provided")
2019-10-01 12:22:30 -03:00
}
address, err := getAddressFromScript(payDetails.Outputs[0].Script, network)
if err != nil {
2021-01-29 18:51:08 -03:00
return nil, fmt.Errorf("failed to get address: %w", err)
2019-10-01 12:22:30 -03:00
}
2022-03-16 11:14:47 -03:00
amount := float64(payDetails.Outputs[0].Amount) / 100_000_000
2019-10-01 12:22:30 -03:00
return &MuunPaymentURI{
Address: address,
2020-11-09 10:05:29 -03:00
Message: payDetails.Memo,
2022-03-16 11:14:47 -03:00
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
Bip70Url: url,
2020-11-09 10:05:29 -03:00
CreationTime: strconv.FormatUint(payDetails.Time, 10),
ExpiresTime: strconv.FormatUint(payDetails.Expires, 10),
2019-10-01 12:22:30 -03:00
}, nil
}
func getAddressFromScript(script []byte, network *Network) (string, error) {
pkScript, err := txscript.ParsePkScript(script)
if err != nil {
return "", err
}
address, err := pkScript.Address(network.network)
if err != nil {
return "", err
}
return address.String(), nil
}
2020-11-09 10:05:29 -03:00
func buildUriFromString(rawInput string, targetScheme string) (string, *url.URL) {
2022-03-16 11:14:47 -03:00
newUri := strings.Replace(rawInput, muunScheme, targetScheme, 1)
2020-11-09 10:05:29 -03:00
if !strings.HasPrefix(strings.ToLower(newUri), targetScheme) {
newUri = targetScheme + rawInput
}
components, err := url.Parse(newUri)
if err != nil {
return "", nil
2019-10-01 12:22:30 -03:00
}
2020-11-09 10:05:29 -03:00
return newUri, components
2019-10-01 12:22:30 -03:00
}