From a7dd958585439de0e1323c8a95e3f3c1d98d1442 Mon Sep 17 00:00:00 2001 From: mleku Date: Wed, 6 Aug 2025 10:03:16 +0100 Subject: [PATCH] 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 --- cmd/nwcclient/main.go | 285 -------------- cmd/{nwcclient => walletcli}/README.md | 0 cmd/walletcli/main.go | 511 +++++++++++++++++++++++++ pkg/crypto/p256k/helpers.go | 2 +- pkg/encoders/hex/aliases.go | 1 + pkg/encoders/tag/tag.go | 5 +- pkg/protocol/nwc/client.go | 89 ++++- pkg/protocol/nwc/methods.go | 156 +++++++- pkg/protocol/nwc/uri.go | 6 + 9 files changed, 764 insertions(+), 291 deletions(-) delete mode 100644 cmd/nwcclient/main.go rename cmd/{nwcclient => walletcli}/README.md (100%) create mode 100644 cmd/walletcli/main.go diff --git a/cmd/nwcclient/main.go b/cmd/nwcclient/main.go deleted file mode 100644 index 6dfb1df..0000000 --- a/cmd/nwcclient/main.go +++ /dev/null @@ -1,285 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - "strconv" - "strings" - - "orly.dev/pkg/protocol/nwc" -) - -func printUsage() { - fmt.Println("Usage: nwcclient \"\" [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 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 = ¶mValue - 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)) -} diff --git a/cmd/nwcclient/README.md b/cmd/walletcli/README.md similarity index 100% rename from cmd/nwcclient/README.md rename to cmd/walletcli/README.md diff --git a/cmd/walletcli/main.go b/cmd/walletcli/main.go new file mode 100644 index 0000000..0660ac6 --- /dev/null +++ b/cmd/walletcli/main.go @@ -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 '' []") + 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: [] [] []") + fmt.Println(" pay_invoice - Pay an invoice") + fmt.Println(" Args: [] []") + fmt.Println(" pay_keysend - Pay to a node using keysend") + fmt.Println(" Args: [] [ ...]") + fmt.Println(" lookup_invoice - Look up an invoice") + fmt.Println(" Args: ") + fmt.Println(" list_transactions - List transactions") + fmt.Println(" Args: [] [] [] []") + fmt.Println(" make_hold_invoice - Create a hold invoice") + fmt.Println(" Args: [] [] []") + fmt.Println(" settle_hold_invoice - Settle a hold invoice") + fmt.Println(" Args: ") + fmt.Println(" cancel_hold_invoice - Cancel a hold invoice") + fmt.Println(" Args: ") + fmt.Println(" sign_message - Sign a message") + fmt.Println(" Args: ") + fmt.Println(" create_connection - Create a connection") + fmt.Println(" Args: [] [] [] []") +} + +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 make_invoice [] [] []") + 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 pay_invoice [] []") + 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 lookup_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 make_hold_invoice [] [] []") + 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 settle_hold_invoice ") + 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 cancel_hold_invoice ") + 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 sign_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 pay_keysend [] [ ...]") + 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 create_connection [] [] [] []") + 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)) +} diff --git a/pkg/crypto/p256k/helpers.go b/pkg/crypto/p256k/helpers.go index 51ce94f..3d4101f 100644 --- a/pkg/crypto/p256k/helpers.go +++ b/pkg/crypto/p256k/helpers.go @@ -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 diff --git a/pkg/encoders/hex/aliases.go b/pkg/encoders/hex/aliases.go index 9add71c..34a8256 100644 --- a/pkg/encoders/hex/aliases.go +++ b/pkg/encoders/hex/aliases.go @@ -4,6 +4,7 @@ package hex import ( "encoding/hex" + "github.com/templexxx/xhex" "orly.dev/pkg/utils/chk" "orly.dev/pkg/utils/errorf" diff --git a/pkg/encoders/tag/tag.go b/pkg/encoders/tag/tag.go index 32f657a..61639e2 100644 --- a/pkg/encoders/tag/tag.go +++ b/pkg/encoders/tag/tag.go @@ -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) diff --git a/pkg/protocol/nwc/client.go b/pkg/protocol/nwc/client.go index 71f0905..93bc3de 100644 --- a/pkg/protocol/nwc/client.go +++ b/pkg/protocol/nwc/client.go @@ -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 +} diff --git a/pkg/protocol/nwc/methods.go b/pkg/protocol/nwc/methods.go index 73e5fe9..3374f8d 100644 --- a/pkg/protocol/nwc/methods.go +++ b/pkg/protocol/nwc/methods.go @@ -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 +} diff --git a/pkg/protocol/nwc/uri.go b/pkg/protocol/nwc/uri.go index 898ccf3..4571fce 100644 --- a/pkg/protocol/nwc/uri.go +++ b/pkg/protocol/nwc/uri.go @@ -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) {