diff --git a/cmd/walletcli/main.go b/cmd/walletcli/main.go index 7055dcd..f698105 100644 --- a/cmd/walletcli/main.go +++ b/cmd/walletcli/main.go @@ -6,9 +6,11 @@ import ( "strconv" "strings" + "orly.dev/pkg/encoders/event" "orly.dev/pkg/protocol/nwc" "orly.dev/pkg/utils/chk" "orly.dev/pkg/utils/context" + "orly.dev/pkg/utils/interrupt" ) func printUsage() { @@ -38,6 +40,7 @@ func printUsage() { fmt.Println(" Args: ") fmt.Println(" create_connection - Create a connection") fmt.Println(" Args: [] [] [] []") + fmt.Println(" subscribe - Subscribe to payment_received, payment_sent and hold_invoice_accepted notifications visible in the scope of the connection") } func main() { @@ -49,11 +52,11 @@ func main() { method := os.Args[2] args := os.Args[3:] // Create context - // ctx, cancel := context.Cancel(context.Bg()) - ctx := context.Bg() + // c, cancel := context.Cancel(context.Bg()) + c := context.Bg() // defer cancel() // Create NWC client - client, err := nwc.NewClient(ctx, connectionURL) + cl, err := nwc.NewClient(c, connectionURL) if err != nil { fmt.Printf("Error creating client: %v\n", err) os.Exit(1) @@ -61,33 +64,35 @@ func main() { // Execute the requested method switch method { case "get_wallet_service_info": - handleGetWalletServiceInfo(ctx, client) + handleGetWalletServiceInfo(c, cl) case "get_info": - handleGetInfo(ctx, client) + handleGetInfo(c, cl) case "get_balance": - handleGetBalance(ctx, client) + handleGetBalance(c, cl) case "get_budget": - handleGetBudget(ctx, client) + handleGetBudget(c, cl) case "make_invoice": - handleMakeInvoice(ctx, client, args) + handleMakeInvoice(c, cl, args) case "pay_invoice": - handlePayInvoice(ctx, client, args) + handlePayInvoice(c, cl, args) case "pay_keysend": - handlePayKeysend(ctx, client, args) + handlePayKeysend(c, cl, args) case "lookup_invoice": - handleLookupInvoice(ctx, client, args) + handleLookupInvoice(c, cl, args) case "list_transactions": - handleListTransactions(ctx, client, args) + handleListTransactions(c, cl, args) case "make_hold_invoice": - handleMakeHoldInvoice(ctx, client, args) + handleMakeHoldInvoice(c, cl, args) case "settle_hold_invoice": - handleSettleHoldInvoice(ctx, client, args) + handleSettleHoldInvoice(c, cl, args) case "cancel_hold_invoice": - handleCancelHoldInvoice(ctx, client, args) + handleCancelHoldInvoice(c, cl, args) case "sign_message": - handleSignMessage(ctx, client, args) + handleSignMessage(c, cl, args) case "create_connection": - handleCreateConnection(ctx, client, args) + handleCreateConnection(c, cl, args) + case "subscribe": + handleSubscribe(c, cl) default: fmt.Printf("Unknown method: %s\n", method) printUsage() @@ -95,31 +100,31 @@ func main() { } } -func handleGetWalletServiceInfo(ctx context.T, client *nwc.Client) { - if _, raw, err := client.GetWalletServiceInfo(ctx, true); !chk.E(err) { +func handleGetWalletServiceInfo(c context.T, cl *nwc.Client) { + if _, raw, err := cl.GetWalletServiceInfo(c, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleGetInfo(ctx context.T, client *nwc.Client) { - if _, raw, err := client.GetInfo(ctx, true); !chk.E(err) { +func handleGetInfo(c context.T, cl *nwc.Client) { + if _, raw, err := cl.GetInfo(c, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleGetBalance(ctx context.T, client *nwc.Client) { - if _, raw, err := client.GetBalance(ctx, true); !chk.E(err) { +func handleGetBalance(c context.T, cl *nwc.Client) { + if _, raw, err := cl.GetBalance(c, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleGetBudget(ctx context.T, client *nwc.Client) { - if _, raw, err := client.GetBudget(ctx, true); !chk.E(err) { +func handleGetBudget(c context.T, cl *nwc.Client) { + if _, raw, err := cl.GetBudget(c, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleMakeInvoice(ctx context.T, client *nwc.Client, args []string) { +func handleMakeInvoice(c context.T, cl *nwc.Client, args []string) { if len(args) < 1 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli make_invoice [] [] []") @@ -148,12 +153,12 @@ func handleMakeInvoice(ctx context.T, client *nwc.Client, args []string) { params.Expiry = &expiry } var raw []byte - if _, raw, err = client.MakeInvoice(ctx, params, true); !chk.E(err) { + if _, raw, err = cl.MakeInvoice(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handlePayInvoice(ctx context.T, client *nwc.Client, args []string) { +func handlePayInvoice(c context.T, cl *nwc.Client, args []string) { if len(args) < 1 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli pay_invoice [] []") @@ -176,12 +181,12 @@ func handlePayInvoice(ctx context.T, client *nwc.Client, args []string) { Comment: &comment, } } - if _, raw, err := client.PayInvoice(ctx, params, true); !chk.E(err) { + if _, raw, err := cl.PayInvoice(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleLookupInvoice(ctx context.T, client *nwc.Client, args []string) { +func handleLookupInvoice(c context.T, cl *nwc.Client, args []string) { if len(args) < 1 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli lookup_invoice ") @@ -198,12 +203,12 @@ func handleLookupInvoice(ctx context.T, client *nwc.Client, args []string) { } var err error var raw []byte - if _, raw, err = client.LookupInvoice(ctx, params, true); !chk.E(err) { + if _, raw, err = cl.LookupInvoice(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleListTransactions(ctx context.T, client *nwc.Client, args []string) { +func handleListTransactions(c context.T, cl *nwc.Client, args []string) { params := &nwc.ListTransactionsParams{} if len(args) > 0 { limit, err := strconv.ParseUint(args[0], 10, 16) @@ -241,12 +246,12 @@ func handleListTransactions(ctx context.T, client *nwc.Client, args []string) { } var raw []byte var err error - if _, raw, err = client.ListTransactions(ctx, params, true); !chk.E(err) { + if _, raw, err = cl.ListTransactions(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleMakeHoldInvoice(ctx context.T, client *nwc.Client, args []string) { +func handleMakeHoldInvoice(c context.T, cl *nwc.Client, args []string) { if len(args) < 2 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli make_hold_invoice [] [] []") @@ -276,12 +281,12 @@ func handleMakeHoldInvoice(ctx context.T, client *nwc.Client, args []string) { params.Expiry = &expiry } var raw []byte - if _, raw, err = client.MakeHoldInvoice(ctx, params, true); !chk.E(err) { + if _, raw, err = cl.MakeHoldInvoice(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleSettleHoldInvoice(ctx context.T, client *nwc.Client, args []string) { +func handleSettleHoldInvoice(c context.T, cl *nwc.Client, args []string) { if len(args) < 1 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli settle_hold_invoice ") @@ -292,12 +297,12 @@ func handleSettleHoldInvoice(ctx context.T, client *nwc.Client, args []string) { } var raw []byte var err error - if raw, err = client.SettleHoldInvoice(ctx, params, true); !chk.E(err) { + if raw, err = cl.SettleHoldInvoice(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleCancelHoldInvoice(ctx context.T, client *nwc.Client, args []string) { +func handleCancelHoldInvoice(c context.T, cl *nwc.Client, args []string) { if len(args) < 1 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli cancel_hold_invoice ") @@ -309,12 +314,12 @@ func handleCancelHoldInvoice(ctx context.T, client *nwc.Client, args []string) { } var err error var raw []byte - if raw, err = client.CancelHoldInvoice(ctx, params, true); !chk.E(err) { + if raw, err = cl.CancelHoldInvoice(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleSignMessage(ctx context.T, client *nwc.Client, args []string) { +func handleSignMessage(c context.T, cl *nwc.Client, args []string) { if len(args) < 1 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli sign_message ") @@ -326,12 +331,12 @@ func handleSignMessage(ctx context.T, client *nwc.Client, args []string) { } var raw []byte var err error - if _, raw, err = client.SignMessage(ctx, params, true); !chk.E(err) { + if _, raw, err = cl.SignMessage(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handlePayKeysend(ctx context.T, client *nwc.Client, args []string) { +func handlePayKeysend(c context.T, cl *nwc.Client, args []string) { if len(args) < 2 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli pay_keysend [] [ ...]") @@ -371,12 +376,12 @@ func handlePayKeysend(ctx context.T, client *nwc.Client, args []string) { } } var raw []byte - if _, raw, err = client.PayKeysend(ctx, params, true); !chk.E(err) { + if _, raw, err = cl.PayKeysend(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } -func handleCreateConnection(ctx context.T, client *nwc.Client, args []string) { +func handleCreateConnection(c context.T, cl *nwc.Client, args []string) { if len(args) < 3 { fmt.Println("Error: Missing required arguments") fmt.Println("Usage: walletcli create_connection [] [] [] []") @@ -411,7 +416,38 @@ func handleCreateConnection(ctx context.T, client *nwc.Client, args []string) { } var raw []byte var err error - if raw, err = client.CreateConnection(ctx, params, true); !chk.E(err) { + if raw, err = cl.CreateConnection(c, params, true); !chk.E(err) { fmt.Println(string(raw)) } } + +func handleSubscribe(c context.T, cl *nwc.Client) { + // Create a context with a cancel + c, cancel := context.Cancel(c) + interrupt.AddHandler(cancel) + + // Get wallet service info to check if notifications are supported + wsi, _, err := cl.GetWalletServiceInfo(c, false) + if err != nil { + fmt.Printf("Error getting wallet service info: %v\n", err) + return + } + + // Check if the wallet supports notifications + if len(wsi.NotificationTypes) == 0 { + fmt.Println("Wallet does not support notifications") + return + } + var evc event.C + if evc, err = cl.Subscribe(c); chk.E(err) { + return + } + for { + select { + case <-c.Done(): + return + case ev := <-evc: + fmt.Println(ev.Marshal(nil)) + } + } +} diff --git a/pkg/crypto/p256k/helpers.go b/pkg/crypto/p256k/helpers.go index 7d1c268..26e27d2 100644 --- a/pkg/crypto/p256k/helpers.go +++ b/pkg/crypto/p256k/helpers.go @@ -6,7 +6,6 @@ import ( "orly.dev/pkg/encoders/hex" "orly.dev/pkg/interfaces/signer" "orly.dev/pkg/utils/chk" - "orly.dev/pkg/utils/log" ) func NewSecFromHex[V []byte | string](skh V) (sign signer.I, err error) { @@ -34,10 +33,8 @@ func NewPubFromHex[V []byte | string](pkh V) (sign signer.I, err error) { } func HexToBin(hexStr string) (b []byte, err error) { - // b = make([]byte, 0, len(hexStr)/2) if b, err = hex.DecAppend(b, []byte(hexStr)); chk.E(err) { return } - log.I.F("hex to bin: %s -> %s", hexStr, hex.Enc(b)) return } diff --git a/pkg/protocol/nwc/client.go b/pkg/protocol/nwc/client.go index 9c723c0..545e64a 100644 --- a/pkg/protocol/nwc/client.go +++ b/pkg/protocol/nwc/client.go @@ -157,3 +157,36 @@ func (cl *Client) RPC( } return } + +func (cl *Client) Subscribe(c context.T) (evc event.C, err error) { + var rc *ws.Client + if rc, err = ws.RelayConnect(c, cl.relay); chk.E(err) { + return + } + defer rc.Close() + var sub *ws.Subscription + if sub, err = rc.Subscribe( + c, filters.New( + &filter.F{ + Kinds: kinds.New( + kind.WalletNotification, kind.WalletNotificationNip4, + ), + Authors: tag.New(cl.walletPublicKey), + }, + ), + ); chk.E(err) { + return + } + defer sub.Unsub() + go func() { + for { + select { + case <-c.Done(): + return + case ev := <-sub.Events: + evc <- ev + } + } + }() + return +} diff --git a/pkg/protocol/ws/client.go b/pkg/protocol/ws/client.go index b4efc81..5edd440 100644 --- a/pkg/protocol/ws/client.go +++ b/pkg/protocol/ws/client.go @@ -276,16 +276,18 @@ func (r *Client) ConnectWithTLS( } r.challenge = env.Challenge case eventenvelope.L: + log.I.F("%s", rem) var env *eventenvelope.Result env = eventenvelope.NewResult() if _, err = env.Unmarshal(rem); chk.E(err) { continue } - sub, ok := r.Subscriptions.Load(subIdToSerial(env.Subscription.String())) + subid := env.Subscription.String() + sub, ok := r.Subscriptions.Load(subIdToSerial(subid)) if !ok { log.W.F( "unknown subscription with id '%s'\n", - env.Subscription.String(), + subid, ) continue }