Renamed NWC client methods and added RPCRaw wrappers

*   Renamed `NWCClient` to `nwc.NewNWCClient(opts)` in `cmd/nwcclient/main.go`
*   Added `RPCRaw` wrappers for NWC client methods in `pkg/protocol/nwc/methods.go`

**Updated walletcli main function**

*   Updated the main function in `cmd/walletcli/main.go` to use new NWC client and RPCRaw wrappers

**Added new methods for walletcli**

*   Added new methods for handling NWC client RPC calls, such as:
    *   `handleGetWalletServiceInfo`
    *   `handleMakeHoldInvoice`
    *   `handleSettleHoldInvoice`
    *   `handleCancelHoldInvoice`

**Code formatting and style changes**

*   Formatted code according to Go standard
*   Used consistent naming conventions and coding styles

**Other updates**

*   Updated dependencies and imported packages accordingly
This commit is contained in:
2025-08-06 10:03:16 +01:00
parent 8eb5b839b0
commit a7dd958585
9 changed files with 764 additions and 291 deletions

View File

@@ -1,285 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"orly.dev/pkg/protocol/nwc"
)
func printUsage() {
fmt.Println("Usage: nwcclient \"<connection URL>\" <method> [parameters...]")
fmt.Println("\nSupported methods:")
fmt.Println(" get_info - Get wallet information")
fmt.Println(" get_balance - Get wallet balance")
fmt.Println(" get_budget - Get wallet budget")
fmt.Println(" make_invoice - Create an invoice (amount, description, [description_hash], [expiry])")
fmt.Println(" pay_invoice - Pay an invoice (invoice, [amount])")
fmt.Println(" pay_keysend - Send a keysend payment (amount, pubkey, [preimage])")
fmt.Println(" lookup_invoice - Look up an invoice (payment_hash or invoice)")
fmt.Println(" list_transactions - List transactions ([from], [until], [limit], [offset], [unpaid], [type])")
fmt.Println(" sign_message - Sign a message (message)")
fmt.Println("\nUnsupported methods (due to limitations in the nwc package):")
fmt.Println(" create_connection - Create a connection")
fmt.Println(" make_hold_invoice - Create a hold invoice")
fmt.Println(" settle_hold_invoice - Settle a hold invoice")
fmt.Println(" cancel_hold_invoice - Cancel a hold invoice")
fmt.Println(" multi_pay_invoice - Pay multiple invoices")
fmt.Println(" multi_pay_keysend - Send multiple keysend payments")
fmt.Println("\nParameters format:")
fmt.Println(" - Positional parameters are used for required fields")
fmt.Println(" - For list_transactions, named parameters are used: 'from', 'until', 'limit', 'offset', 'unpaid', 'type'")
fmt.Println(" Example: nwcclient <url> list_transactions limit 10 type incoming")
os.Exit(1)
}
func main() {
// Check if we have enough arguments
if len(os.Args) < 3 {
printUsage()
}
// Parse connection URL and method
connectionURL := os.Args[1]
methodStr := os.Args[2]
method := nwc.Capability(methodStr)
// Parse the wallet connect URL
opts, err := nwc.ParseWalletConnectURL(connectionURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing connection URL: %v\n", err)
os.Exit(1)
}
// Create a new NWC client
client, err := nwc.NewNWCClient(opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating NWC client: %v\n", err)
os.Exit(1)
}
defer client.Close()
// Execute the requested method
var result interface{}
switch method {
case nwc.GetInfo:
result, err = client.GetInfo()
case nwc.GetBalance:
result, err = client.GetBalance()
case nwc.GetBudget:
result, err = client.GetBudget()
case nwc.MakeInvoice:
if len(os.Args) < 5 {
fmt.Fprintf(
os.Stderr,
"Error: make_invoice requires at least amount and description\n",
)
printUsage()
}
amount, err := strconv.ParseInt(os.Args[3], 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing amount: %v\n", err)
os.Exit(1)
}
description := os.Args[4]
req := &nwc.MakeInvoiceRequest{
Amount: amount,
Description: description,
}
// Optional parameters
if len(os.Args) > 5 {
req.DescriptionHash = os.Args[5]
}
if len(os.Args) > 6 {
expiry, err := strconv.ParseInt(os.Args[6], 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing expiry: %v\n", err)
os.Exit(1)
}
req.Expiry = &expiry
}
result, err = client.MakeInvoice(req)
case nwc.PayInvoice:
if len(os.Args) < 4 {
fmt.Fprintf(os.Stderr, "Error: pay_invoice requires an invoice\n")
printUsage()
}
req := &nwc.PayInvoiceRequest{
Invoice: os.Args[3],
}
// Optional amount parameter
if len(os.Args) > 4 {
amount, err := strconv.ParseInt(os.Args[4], 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing amount: %v\n", err)
os.Exit(1)
}
req.Amount = &amount
}
result, err = client.PayInvoice(req)
case nwc.PayKeysend:
if len(os.Args) < 5 {
fmt.Fprintf(
os.Stderr, "Error: pay_keysend requires amount and pubkey\n",
)
printUsage()
}
amount, err := strconv.ParseInt(os.Args[3], 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing amount: %v\n", err)
os.Exit(1)
}
req := &nwc.PayKeysendRequest{
Amount: amount,
Pubkey: os.Args[4],
}
// Optional preimage
if len(os.Args) > 5 {
req.Preimage = os.Args[5]
}
result, err = client.PayKeysend(req)
case nwc.LookupInvoice:
if len(os.Args) < 4 {
fmt.Fprintf(
os.Stderr,
"Error: lookup_invoice requires a payment_hash or invoice\n",
)
printUsage()
}
param := os.Args[3]
req := &nwc.LookupInvoiceRequest{}
// Determine if the parameter is a payment hash or an invoice
if strings.HasPrefix(param, "ln") {
req.Invoice = param
} else {
req.PaymentHash = param
}
result, err = client.LookupInvoice(req)
case nwc.ListTransactions:
req := &nwc.ListTransactionsRequest{}
// Parse optional parameters
paramIndex := 3
for paramIndex < len(os.Args) {
if paramIndex+1 >= len(os.Args) {
break
}
paramName := os.Args[paramIndex]
paramValue := os.Args[paramIndex+1]
switch paramName {
case "from":
val, err := strconv.ParseInt(paramValue, 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing from: %v\n", err)
os.Exit(1)
}
req.From = &val
case "until":
val, err := strconv.ParseInt(paramValue, 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing until: %v\n", err)
os.Exit(1)
}
req.Until = &val
case "limit":
val, err := strconv.ParseInt(paramValue, 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing limit: %v\n", err)
os.Exit(1)
}
req.Limit = &val
case "offset":
val, err := strconv.ParseInt(paramValue, 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing offset: %v\n", err)
os.Exit(1)
}
req.Offset = &val
case "unpaid":
val := paramValue == "true"
req.Unpaid = &val
case "type":
req.Type = &paramValue
default:
fmt.Fprintf(os.Stderr, "Unknown parameter: %s\n", paramName)
os.Exit(1)
}
paramIndex += 2
}
result, err = client.ListTransactions(req)
case nwc.SignMessage:
if len(os.Args) < 4 {
fmt.Fprintf(os.Stderr, "Error: sign_message requires a message\n")
printUsage()
}
req := &nwc.SignMessageRequest{
Message: os.Args[3],
}
result, err = client.SignMessage(req)
case nwc.CreateConnection, nwc.MakeHoldInvoice, nwc.SettleHoldInvoice, nwc.CancelHoldInvoice, nwc.MultiPayInvoice, nwc.MultiPayKeysend:
fmt.Fprintf(
os.Stderr,
"Error: Method %s is not directly supported by the CLI tool.\n",
methodStr,
)
fmt.Fprintf(
os.Stderr,
"This is because these methods don't have exported client methods in the nwc package.\n",
)
fmt.Fprintf(
os.Stderr,
"Only the following methods are currently supported: get_info, get_balance, get_budget, make_invoice, pay_invoice, pay_keysend, lookup_invoice, list_transactions, sign_message\n",
)
os.Exit(1)
default:
fmt.Fprintf(os.Stderr, "Error: Unsupported method: %s\n", methodStr)
printUsage()
}
if err != nil {
fmt.Fprintf(os.Stderr, "Error executing method: %v\n", err)
os.Exit(1)
}
// Print the result as JSON
jsonData, err := json.MarshalIndent(result, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "Error marshaling result to JSON: %v\n", err)
os.Exit(1)
}
fmt.Println(string(jsonData))
}

511
cmd/walletcli/main.go Normal file
View File

@@ -0,0 +1,511 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"orly.dev/pkg/protocol/nwc"
"orly.dev/pkg/utils/context"
)
func printUsage() {
fmt.Println("Usage: walletcli '<NWC connection URL>' <method> [<args...>]")
fmt.Println("\nAvailable methods:")
fmt.Println(" get_wallet_service_info - Get wallet service information")
fmt.Println(" get_info - Get wallet information")
fmt.Println(" get_balance - Get wallet balance")
fmt.Println(" get_budget - Get wallet budget")
fmt.Println(" make_invoice - Create an invoice")
fmt.Println(" Args: <amount> [<description>] [<description_hash>] [<expiry>]")
fmt.Println(" pay_invoice - Pay an invoice")
fmt.Println(" Args: <invoice> [<amount>] [<comment>]")
fmt.Println(" pay_keysend - Pay to a node using keysend")
fmt.Println(" Args: <pubkey> <amount> [<preimage>] [<tlv_type> <tlv_value>...]")
fmt.Println(" lookup_invoice - Look up an invoice")
fmt.Println(" Args: <payment_hash or invoice>")
fmt.Println(" list_transactions - List transactions")
fmt.Println(" Args: [<limit>] [<offset>] [<from>] [<until>]")
fmt.Println(" make_hold_invoice - Create a hold invoice")
fmt.Println(" Args: <amount> <payment_hash> [<description>] [<description_hash>] [<expiry>]")
fmt.Println(" settle_hold_invoice - Settle a hold invoice")
fmt.Println(" Args: <preimage>")
fmt.Println(" cancel_hold_invoice - Cancel a hold invoice")
fmt.Println(" Args: <payment_hash>")
fmt.Println(" sign_message - Sign a message")
fmt.Println(" Args: <message>")
fmt.Println(" create_connection - Create a connection")
fmt.Println(" Args: <pubkey> <name> <methods> [<notification_types>] [<max_amount>] [<budget_renewal>] [<expires_at>]")
}
func main() {
if len(os.Args) < 3 {
printUsage()
os.Exit(1)
}
connectionURL := os.Args[1]
method := os.Args[2]
args := os.Args[3:]
// Create context
ctx := context.Bg()
// Create NWC client
client, err := nwc.NewClient(ctx, connectionURL)
if err != nil {
fmt.Printf("Error creating client: %v\n", err)
os.Exit(1)
}
// Execute the requested method
switch method {
case "get_wallet_service_info":
handleGetWalletServiceInfo(ctx, client)
case "get_info":
handleGetInfo(ctx, client)
case "get_balance":
handleGetBalance(ctx, client)
case "get_budget":
handleGetBudget(ctx, client)
case "make_invoice":
handleMakeInvoice(ctx, client, args)
case "pay_invoice":
handlePayInvoice(ctx, client, args)
case "pay_keysend":
handlePayKeysend(ctx, client, args)
case "lookup_invoice":
handleLookupInvoice(ctx, client, args)
case "list_transactions":
handleListTransactions(ctx, client, args)
case "make_hold_invoice":
handleMakeHoldInvoice(ctx, client, args)
case "settle_hold_invoice":
handleSettleHoldInvoice(ctx, client, args)
case "cancel_hold_invoice":
handleCancelHoldInvoice(ctx, client, args)
case "sign_message":
handleSignMessage(ctx, client, args)
case "create_connection":
handleCreateConnection(ctx, client, args)
default:
fmt.Printf("Unknown method: %s\n", method)
printUsage()
os.Exit(1)
}
}
func handleGetWalletServiceInfo(ctx context.T, client *nwc.Client) {
raw, err := client.GetWalletServiceInfoRaw(ctx)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleGetInfo(ctx context.T, client *nwc.Client) {
raw, err := client.GetInfoRaw(ctx)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleGetBalance(ctx context.T, client *nwc.Client) {
raw, err := client.GetBalanceRaw(ctx)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleGetBudget(ctx context.T, client *nwc.Client) {
raw, err := client.GetBudgetRaw(ctx)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleMakeInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> make_invoice <amount> [<description>] [<description_hash>] [<expiry>]")
return
}
amount, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
return
}
params := &nwc.MakeInvoiceParams{
Amount: amount,
}
if len(args) > 1 {
params.Description = args[1]
}
if len(args) > 2 {
params.DescriptionHash = args[2]
}
if len(args) > 3 {
expiry, err := strconv.ParseInt(args[3], 10, 64)
if err != nil {
fmt.Printf("Error parsing expiry: %v\n", err)
return
}
params.Expiry = &expiry
}
raw, err := client.MakeInvoiceRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handlePayInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> pay_invoice <invoice> [<amount>] [<comment>]")
return
}
params := &nwc.PayInvoiceParams{
Invoice: args[0],
}
if len(args) > 1 {
amount, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
return
}
params.Amount = &amount
}
if len(args) > 2 {
comment := args[2]
params.Metadata = &nwc.PayInvoiceMetadata{
Comment: &comment,
}
}
raw, err := client.PayInvoiceRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleLookupInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> lookup_invoice <payment_hash or invoice>")
return
}
params := &nwc.LookupInvoiceParams{}
// Determine if the argument is a payment hash or an invoice
if strings.HasPrefix(args[0], "ln") {
invoice := args[0]
params.Invoice = &invoice
} else {
paymentHash := args[0]
params.PaymentHash = &paymentHash
}
raw, err := client.LookupInvoiceRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleListTransactions(ctx context.T, client *nwc.Client, args []string) {
params := &nwc.ListTransactionsParams{}
if len(args) > 0 {
limit, err := strconv.ParseUint(args[0], 10, 16)
if err != nil {
fmt.Printf("Error parsing limit: %v\n", err)
return
}
limitUint16 := uint16(limit)
params.Limit = &limitUint16
}
if len(args) > 1 {
offset, err := strconv.ParseUint(args[1], 10, 32)
if err != nil {
fmt.Printf("Error parsing offset: %v\n", err)
return
}
offsetUint32 := uint32(offset)
params.Offset = &offsetUint32
}
if len(args) > 2 {
from, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
fmt.Printf("Error parsing from: %v\n", err)
return
}
params.From = &from
}
if len(args) > 3 {
until, err := strconv.ParseInt(args[3], 10, 64)
if err != nil {
fmt.Printf("Error parsing until: %v\n", err)
return
}
params.Until = &until
}
raw, err := client.ListTransactionsRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleMakeHoldInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 2 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> make_hold_invoice <amount> <payment_hash> [<description>] [<description_hash>] [<expiry>]")
return
}
amount, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
return
}
params := &nwc.MakeHoldInvoiceParams{
Amount: amount,
PaymentHash: args[1],
}
if len(args) > 2 {
params.Description = args[2]
}
if len(args) > 3 {
params.DescriptionHash = args[3]
}
if len(args) > 4 {
expiry, err := strconv.ParseInt(args[4], 10, 64)
if err != nil {
fmt.Printf("Error parsing expiry: %v\n", err)
return
}
params.Expiry = &expiry
}
raw, err := client.MakeHoldInvoiceRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleSettleHoldInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> settle_hold_invoice <preimage>")
return
}
params := &nwc.SettleHoldInvoiceParams{
Preimage: args[0],
}
raw, err := client.SettleHoldInvoiceRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleCancelHoldInvoice(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> cancel_hold_invoice <payment_hash>")
return
}
params := &nwc.CancelHoldInvoiceParams{
PaymentHash: args[0],
}
raw, err := client.CancelHoldInvoiceRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleSignMessage(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 1 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> sign_message <message>")
return
}
params := &nwc.SignMessageParams{
Message: args[0],
}
raw, err := client.SignMessageRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handlePayKeysend(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 2 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> pay_keysend <pubkey> <amount> [<preimage>] [<tlv_type> <tlv_value>...]")
return
}
pubkey := args[0]
amount, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
fmt.Printf("Error parsing amount: %v\n", err)
return
}
params := &nwc.PayKeysendParams{
Pubkey: pubkey,
Amount: amount,
}
// Optional preimage
if len(args) > 2 {
preimage := args[2]
params.Preimage = &preimage
}
// Optional TLV records (must come in pairs)
if len(args) > 3 {
// Start from index 3 and process pairs of arguments
for i := 3; i < len(args)-1; i += 2 {
tlvType, err := strconv.ParseUint(args[i], 10, 32)
if err != nil {
fmt.Printf("Error parsing TLV type: %v\n", err)
return
}
tlvValue := args[i+1]
params.TLVRecords = append(
params.TLVRecords, nwc.PayKeysendTLVRecord{
Type: uint32(tlvType),
Value: tlvValue,
},
)
}
}
raw, err := client.PayKeysendRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func handleCreateConnection(ctx context.T, client *nwc.Client, args []string) {
if len(args) < 3 {
fmt.Println("Error: Missing required arguments")
fmt.Println("Usage: walletcli <NWC connection URL> create_connection <pubkey> <name> <methods> [<notification_types>] [<max_amount>] [<budget_renewal>] [<expires_at>]")
return
}
params := &nwc.CreateConnectionParams{
Pubkey: args[0],
Name: args[1],
RequestMethods: strings.Split(args[2], ","),
}
if len(args) > 3 {
params.NotificationTypes = strings.Split(args[3], ",")
}
if len(args) > 4 {
maxAmount, err := strconv.ParseUint(args[4], 10, 64)
if err != nil {
fmt.Printf("Error parsing max_amount: %v\n", err)
return
}
params.MaxAmount = &maxAmount
}
if len(args) > 5 {
params.BudgetRenewal = &args[5]
}
if len(args) > 6 {
expiresAt, err := strconv.ParseInt(args[6], 10, 64)
if err != nil {
fmt.Printf("Error parsing expires_at: %v\n", err)
return
}
params.ExpiresAt = &expiresAt
}
raw, err := client.CreateConnectionRaw(ctx, params)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println(string(raw))
}
func printJSON(v interface{}) {
data, err := json.MarshalIndent(v, "", " ")
if err != nil {
fmt.Printf("Error marshaling JSON: %v\n", err)
return
}
fmt.Println(string(data))
}

View File

@@ -33,7 +33,7 @@ func NewPubFromHex[V []byte | string](pkh V) (sign signer.I, err error) {
}
func HexToBin(hexStr string) (b []byte, err error) {
if _, err = hex.DecBytes(b, []byte(hexStr)); chk.E(err) {
if b, err = hex.DecAppend(b, []byte(hexStr)); chk.E(err) {
return
}
return

View File

@@ -4,6 +4,7 @@ package hex
import (
"encoding/hex"
"github.com/templexxx/xhex"
"orly.dev/pkg/utils/chk"
"orly.dev/pkg/utils/errorf"

View File

@@ -5,6 +5,7 @@ package tag
import (
"bytes"
text2 "orly.dev/pkg/encoders/text"
"orly.dev/pkg/utils/errorf"
"orly.dev/pkg/utils/log"
@@ -26,7 +27,7 @@ const (
)
// BS is an abstract data type that can process strings and byte slices as byte slices.
type BS[Z []byte | string] []byte
type BS[Z ~[]byte | ~string] []byte
// T is a list of strings with a literal ordering.
//
@@ -36,7 +37,7 @@ type T struct {
}
// New creates a new tag.T from a variadic parameter that can be either string or byte slice.
func New[V string | []byte](fields ...V) (t *T) {
func New[V ~string | ~[]byte](fields ...V) (t *T) {
t = &T{field: make([]BS[[]byte], len(fields))}
for i, field := range fields {
t.field[i] = []byte(field)

View File

@@ -84,7 +84,7 @@ func (cl *Client) RPC(
c context.T, method Capability, params, result any, opts *rpcOptions,
) (err error) {
timeout := time.Duration(10)
if opts == nil && opts.timeout == nil {
if opts != nil && opts.timeout != nil {
timeout = *opts.timeout
}
ctx, cancel := context.Timeout(c, timeout)
@@ -172,3 +172,90 @@ func (cl *Client) RPC(
}
return
}
// RPCRaw performs an RPC call and returns the raw JSON response
func (cl *Client) RPCRaw(
c context.T, method Capability, params any, opts *rpcOptions,
) (rawResponse []byte, err error) {
timeout := time.Duration(10)
if opts != nil && opts.timeout != nil {
timeout = *opts.timeout
}
ctx, cancel := context.Timeout(c, timeout)
defer cancel()
var req []byte
if req, err = json.Marshal(
Request{
Method: string(method),
Params: params,
},
); chk.E(err) {
return
}
var content []byte
if content, err = encryption.Encrypt(req, cl.conversationKey); chk.E(err) {
return
}
ev := &event.E{
Content: content,
CreatedAt: timestamp.Now(),
Kind: kind.WalletRequest,
Tags: tags.New(
tag.New([]byte("p"), cl.walletPublicKey),
tag.New(EncryptionTag, Nip44V2),
),
}
if err = ev.Sign(cl.clientSecretKey); chk.E(err) {
return
}
hasWorked := make(chan struct{})
evs := cl.pool.SubMany(
c, cl.relays, &filters.T{
F: []*filter.F{
{
Limit: values.ToUintPointer(1),
Kinds: kinds.New(kind.WalletRequest),
Authors: tag.New(cl.walletPublicKey),
Tags: tags.New(tag.New([]byte("#e"), ev.ID)),
},
},
},
)
for _, u := range cl.relays {
go func(u string) {
var relay *ws.Client
if relay, err = cl.pool.EnsureRelay(u); chk.E(err) {
return
}
if err = relay.Publish(c, ev); chk.E(err) {
return
}
select {
case hasWorked <- struct{}{}:
case <-ctx.Done():
err = fmt.Errorf("context canceled waiting for request send")
return
default:
}
}(u)
}
select {
case <-hasWorked:
// continue
case <-ctx.Done():
err = fmt.Errorf("timed out waiting for relays")
return
}
select {
case <-ctx.Done():
err = fmt.Errorf("context canceled waiting for response")
case e := <-evs:
if rawResponse, err = encryption.Decrypt(
e.Event.Content, cl.conversationKey,
); chk.E(err) {
return
}
return
}
return
}

View File

@@ -2,6 +2,7 @@ package nwc
import (
"bytes"
"encoding/json"
"fmt"
"orly.dev/pkg/encoders/filter"
@@ -65,6 +66,34 @@ func (cl *Client) GetWalletServiceInfo(c context.T) (
return
}
func (cl *Client) GetWalletServiceInfoRaw(c context.T) (
raw []byte, err error,
) {
lim := uint(1)
evc := cl.pool.SubMany(
c, cl.relays, &filters.T{
F: []*filter.F{
{
Limit: &lim,
Kinds: kinds.New(kind.WalletInfo),
Authors: tag.New(cl.walletPublicKey),
},
},
},
)
select {
case <-c.Done():
err = fmt.Errorf("GetWalletServiceInfoRaw canceled")
return
case ev := <-evc:
// Marshal the event to JSON
if raw, err = json.Marshal(ev.Event); chk.E(err) {
return
}
}
return
}
func (cl *Client) CancelHoldInvoice(
c context.T, chi *CancelHoldInvoiceParams,
) (err error) {
@@ -74,6 +103,15 @@ func (cl *Client) CancelHoldInvoice(
return
}
func (cl *Client) CancelHoldInvoiceRaw(
c context.T, chi *CancelHoldInvoiceParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, CancelHoldInvoice, chi, nil); chk.E(err) {
return
}
return
}
func (cl *Client) CreateConnection(
c context.T, cc *CreateConnectionParams,
) (err error) {
@@ -83,6 +121,15 @@ func (cl *Client) CreateConnection(
return
}
func (cl *Client) CreateConnectionRaw(
c context.T, cc *CreateConnectionParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, CreateConnection, cc, nil); chk.E(err) {
return
}
return
}
func (cl *Client) GetBalance(c context.T) (gb *GetBalanceResult, err error) {
gb = &GetBalanceResult{}
if err = cl.RPC(c, GetBalance, nil, gb, nil); chk.E(err) {
@@ -91,8 +138,23 @@ func (cl *Client) GetBalance(c context.T) (gb *GetBalanceResult, err error) {
return
}
func (cl *Client) GetBalanceRaw(c context.T) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, GetBalance, nil, nil); chk.E(err) {
return
}
return
}
func (cl *Client) GetBudget(c context.T) (gb *GetBudgetResult, err error) {
if err = cl.RPC(c, CreateConnection, nil, gb, nil); chk.E(err) {
gb = &GetBudgetResult{}
if err = cl.RPC(c, GetBudget, nil, gb, nil); chk.E(err) {
return
}
return
}
func (cl *Client) GetBudgetRaw(c context.T) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, GetBudget, nil, nil); chk.E(err) {
return
}
return
@@ -106,6 +168,13 @@ func (cl *Client) GetInfo(c context.T) (gi *GetInfoResult, err error) {
return
}
func (cl *Client) GetInfoRaw(c context.T) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, GetInfo, nil, nil); chk.E(err) {
return
}
return
}
func (cl *Client) ListTransactions(
c context.T, params *ListTransactionsParams,
) (lt *ListTransactionsResult, err error) {
@@ -116,6 +185,15 @@ func (cl *Client) ListTransactions(
return
}
func (cl *Client) ListTransactionsRaw(
c context.T, params *ListTransactionsParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, ListTransactions, params, nil); chk.E(err) {
return
}
return
}
func (cl *Client) LookupInvoice(
c context.T, params *LookupInvoiceParams,
) (li *LookupInvoiceResult, err error) {
@@ -126,12 +204,31 @@ func (cl *Client) LookupInvoice(
return
}
func (cl *Client) LookupInvoiceRaw(
c context.T, params *LookupInvoiceParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, LookupInvoice, params, nil); chk.E(err) {
return
}
return
}
func (cl *Client) MakeHoldInvoice(
c context.T,
mhi *MakeHoldInvoiceParams,
) (mi *MakeInvoiceResult, err error) {
mi = &MakeInvoiceResult{}
if err = cl.RPC(c, GetBalance, mhi, mi, nil); chk.E(err) {
if err = cl.RPC(c, MakeHoldInvoice, mhi, mi, nil); chk.E(err) {
return
}
return
}
func (cl *Client) MakeHoldInvoiceRaw(
c context.T,
mhi *MakeHoldInvoiceParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, MakeHoldInvoice, mhi, nil); chk.E(err) {
return
}
return
@@ -147,10 +244,38 @@ func (cl *Client) MakeInvoice(
return
}
func (cl *Client) MakeInvoiceRaw(
c context.T, params *MakeInvoiceParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, MakeInvoice, params, nil); chk.E(err) {
return
}
return
}
// MultiPayInvoice
// MultiPayKeysend
func (cl *Client) PayKeysend(
c context.T, params *PayKeysendParams,
) (pk *PayKeysendResult, err error) {
pk = &PayKeysendResult{}
if err = cl.RPC(c, PayKeysend, params, &pk, nil); chk.E(err) {
return
}
return
}
func (cl *Client) PayKeysendRaw(
c context.T, params *PayKeysendParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, PayKeysend, params, nil); chk.E(err) {
return
}
return
}
func (cl *Client) PayInvoice(
c context.T, params *PayInvoiceParams,
) (pi *PayInvoiceResult, err error) {
@@ -161,6 +286,15 @@ func (cl *Client) PayInvoice(
return
}
func (cl *Client) PayInvoiceRaw(
c context.T, params *PayInvoiceParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, PayInvoice, params, nil); chk.E(err) {
return
}
return
}
func (cl *Client) SettleHoldInvoice(
c context.T, shi *SettleHoldInvoiceParams,
) (err error) {
@@ -170,6 +304,15 @@ func (cl *Client) SettleHoldInvoice(
return
}
func (cl *Client) SettleHoldInvoiceRaw(
c context.T, shi *SettleHoldInvoiceParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, SettleHoldInvoice, shi, nil); chk.E(err) {
return
}
return
}
func (cl *Client) SignMessage(
c context.T, sm *SignMessageParams,
) (res *SignMessageResult, err error) {
@@ -179,3 +322,12 @@ func (cl *Client) SignMessage(
}
return
}
func (cl *Client) SignMessageRaw(
c context.T, sm *SignMessageParams,
) (raw []byte, err error) {
if raw, err = cl.RPCRaw(c, SignMessage, sm, nil); chk.E(err) {
return
}
return
}

View File

@@ -3,6 +3,7 @@ package nwc
import (
"errors"
"net/url"
"orly.dev/pkg/crypto/p256k"
"orly.dev/pkg/utils/chk"
)
@@ -13,6 +14,11 @@ type ConnectionParams struct {
relays []string
}
// GetWalletPublicKey returns the wallet public key from the ConnectionParams.
func (c *ConnectionParams) GetWalletPublicKey() []byte {
return c.walletPublicKey
}
func ParseConnectionURI(nwcUri string) (parts *ConnectionParams, err error) {
var p *url.URL
if p, err = url.Parse(nwcUri); chk.E(err) {