Files
ynab-portfolio-monitor/providers/bitcoinElectrum/spv.go

77 lines
2.1 KiB
Go
Raw Normal View History

2025-09-30 22:49:16 -06:00
package bitcoinElectrum
import (
"bufio"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"time"
"github.com/btcsuite/btcd/btcutil"
)
// --- Electrum JSON-RPC Structures ---
type ElectrumRequest struct {
ID int `json:"id"`
Method string `json:"method"`
Params []interface{} `json:"params"`
}
type ElectrumBalanceResponse struct {
Result struct {
Confirmed int64 `json:"confirmed"`
Unconfirmed int64 `json:"unconfirmed"`
} `json:"result"`
Error *struct {
Message string `json:"message"`
} `json:"error"`
}
// Connects to Electrum and queries the balance for a single address.
func getAddressBalance(electrumServer string, address btcutil.Address) (*ElectrumBalanceResponse, error) {
scripthash, err := addressToScriptHash(address)
if err != nil {
return nil, fmt.Errorf("could not create scripthash: %w", err)
}
// Connect to the server with SSL/TLS
conf := &tls.Config{InsecureSkipVerify: true} // Use InsecureSkipVerify for public servers, but be aware of MITM risks
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}, "tcp", electrumServer, conf)
if err != nil {
return nil, fmt.Errorf("could not connect to electrum server: %w", err)
}
defer conn.Close()
// Prepare the JSON-RPC request
request := ElectrumRequest{
ID: 1,
Method: "blockchain.scripthash.get_balance",
Params: []interface{}{scripthash},
}
requestBytes, _ := json.Marshal(request)
// Send the request (with a newline delimiter)
_, err = fmt.Fprintf(conn, "%s\n", requestBytes)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
// Read the response
reader := bufio.NewReader(conn)
responseLine, err := reader.ReadString('\n')
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var response ElectrumBalanceResponse
if err := json.Unmarshal([]byte(responseLine), &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if response.Error != nil {
return nil, fmt.Errorf("electrum server error: %s", response.Error.Message)
}
return &response, nil
}