From 30361c879d37ecacce955363673cfa8386e96717 Mon Sep 17 00:00:00 2001 From: chris-4chain <152964795+chris-4chain@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:05:10 +0200 Subject: [PATCH] feat(SPV-912): handle invalid configuration (#270) --- access_keys_test.go | 3 +- admin_contacts_test.go | 3 +- client_options.go | 56 ++++++++++--------- contacts_test.go | 3 +- destinations_test.go | 3 +- errors.go | 18 ++++++ examples/admin_add_user/admin_add_user.go | 8 ++- .../admin_remove_user/admin_remove_user.go | 8 ++- .../create_transaction/create_transaction.go | 6 +- examples/generate_totp/generate_totp.go | 10 +++- examples/get_balance/get_balance.go | 6 +- examples/go.mod | 2 +- examples/go.sum | 4 +- .../handle_exceptions/handle_exceptions.go | 7 ++- .../list_transactions/list_transactions.go | 6 +- examples/send_op_return/send_op_return.go | 6 +- examples/webhooks/webhooks.go | 26 ++++++--- go.mod | 4 +- go.sum | 4 +- regression_tests/utils.go | 33 ++++++++--- totp_test.go | 29 +++++----- transactions_test.go | 3 +- walletclient.go | 22 +++++--- walletclient_test.go | 32 +++++++---- xpubs_test.go | 3 +- 25 files changed, 203 insertions(+), 102 deletions(-) diff --git a/access_keys_test.go b/access_keys_test.go index 179a1b9..42732a8 100644 --- a/access_keys_test.go +++ b/access_keys_test.go @@ -30,7 +30,8 @@ func TestAccessKeys(t *testing.T) { })) defer server.Close() - client := NewWithAccessKey(server.URL, fixtures.AccessKeyString) + client, err := NewWithAccessKey(server.URL, fixtures.AccessKeyString) + require.NoError(t, err) require.NotNil(t, client.accessKey) t.Run("GetAccessKey", func(t *testing.T) { diff --git a/admin_contacts_test.go b/admin_contacts_test.go index 5b2112e..ea0381a 100644 --- a/admin_contacts_test.go +++ b/admin_contacts_test.go @@ -43,7 +43,8 @@ func TestAdminContactActions(t *testing.T) { })) defer server.Close() - client := NewWithAdminKey(server.URL, fixtures.XPrivString) + client, err := NewWithAdminKey(server.URL, fixtures.XPrivString) + require.NoError(t, err) require.NotNil(t, client.adminXPriv) t.Run("AdminGetContacts", func(t *testing.T) { diff --git a/client_options.go b/client_options.go index fcee338..9e2df8b 100644 --- a/client_options.go +++ b/client_options.go @@ -7,13 +7,11 @@ import ( bip32 "github.com/bitcoin-sv/go-sdk/compat/bip32" ec "github.com/bitcoin-sv/go-sdk/primitives/ec" - - "github.com/pkg/errors" ) // configurator is the interface for configuring WalletClient type configurator interface { - Configure(c *WalletClient) + Configure(c *WalletClient) error } // xPrivConf sets the xPrivString field of a WalletClient @@ -21,11 +19,13 @@ type xPrivConf struct { XPrivString string } -func (w *xPrivConf) Configure(c *WalletClient) { +func (w *xPrivConf) Configure(c *WalletClient) error { var err error if c.xPriv, err = bip32.GenerateHDKeyFromString(w.XPrivString); err != nil { c.xPriv = nil + return ErrInvalidXpriv.Wrap(err) } + return nil } // xPubConf sets the xPubString on the client @@ -33,12 +33,13 @@ type xPubConf struct { XPubString string } -func (w *xPubConf) Configure(c *WalletClient) { +func (w *xPubConf) Configure(c *WalletClient) error { var err error if c.xPub, err = bip32.GetHDKeyFromExtendedPublicKey(w.XPubString); err != nil { c.xPub = nil + return ErrInvalidXpub.Wrap(err) } - + return nil } // accessKeyConf sets the accessKeyString on the client @@ -46,11 +47,26 @@ type accessKeyConf struct { AccessKeyString string } -func (w *accessKeyConf) Configure(c *WalletClient) { +func (w *accessKeyConf) Configure(c *WalletClient) error { var err error if c.accessKey, err = w.initializeAccessKey(); err != nil { c.accessKey = nil + return err + } + return nil +} + +func (w *accessKeyConf) initializeAccessKey() (*ec.PrivateKey, error) { + var errPriv, errPub error + privateKey, errPriv := ec.PrivateKeyFromWif(w.AccessKeyString) + if errPriv != nil { + privateKey, errPub = ec.PrivateKeyFromHex(w.AccessKeyString) + if privateKey == nil { + return nil, ErrInvalidAccessKey.Wrap(errPriv).Wrap(errPub) + } } + + return privateKey, nil } // adminKeyConf sets the admin key for creating new xpubs @@ -58,12 +74,14 @@ type adminKeyConf struct { AdminKeyString string } -func (w *adminKeyConf) Configure(c *WalletClient) { +func (w *adminKeyConf) Configure(c *WalletClient) error { var err error c.adminXPriv, err = bip32.GenerateHDKeyFromString(w.AdminKeyString) if err != nil { c.adminXPriv = nil + return ErrInvalidAdminKey.Wrap(err) } + return nil } // httpConf sets the URL and httpConf client of a WalletClient @@ -72,13 +90,11 @@ type httpConf struct { HTTPClient *http.Client } -func (w *httpConf) Configure(c *WalletClient) { +func (w *httpConf) Configure(c *WalletClient) error { // Ensure the ServerURL ends with a clean base URL baseURL, err := validateAndCleanURL(w.ServerURL) if err != nil { - // Handle the error appropriately - fmt.Println("Invalid URL provided:", err) - return + return ErrInvalidServerURL.Wrap(err) } const basePath = "/v1" @@ -90,6 +106,7 @@ func (w *httpConf) Configure(c *WalletClient) { } else { c.httpClient = http.DefaultClient } + return nil } // signRequest configures whether to sign HTTP requests @@ -97,20 +114,9 @@ type signRequest struct { Sign bool } -func (w *signRequest) Configure(c *WalletClient) { +func (w *signRequest) Configure(c *WalletClient) error { c.signRequest = w.Sign -} - -func (w *accessKeyConf) initializeAccessKey() (*ec.PrivateKey, error) { - privateKey, err := ec.PrivateKeyFromWif(w.AccessKeyString) - if err != nil { - privateKey, _ = ec.PrivateKeyFromHex(w.AccessKeyString) - if privateKey == nil { - return nil, errors.New("failed to decode access key") - } - } - - return privateKey, nil + return nil } // validateAndCleanURL ensures that the provided URL is valid, and strips it down to just the base URL. diff --git a/contacts_test.go b/contacts_test.go index dc105f6..5781eda 100644 --- a/contacts_test.go +++ b/contacts_test.go @@ -44,7 +44,8 @@ func TestContactActionsRouting(t *testing.T) { })) defer server.Close() - client := NewWithAccessKey(server.URL, fixtures.AccessKeyString) + client, err := NewWithAccessKey(server.URL, fixtures.AccessKeyString) + require.NoError(t, err) require.NotNil(t, client.accessKey) t.Run("RejectContact", func(t *testing.T) { diff --git a/destinations_test.go b/destinations_test.go index 40b294c..f117bf2 100644 --- a/destinations_test.go +++ b/destinations_test.go @@ -42,7 +42,8 @@ func TestDestinations(t *testing.T) { } })) defer server.Close() - client := NewWithAccessKey(server.URL, fixtures.AccessKeyString) + client, err := NewWithAccessKey(server.URL, fixtures.AccessKeyString) + require.NoError(t, err) require.NotNil(t, client.accessKey) t.Run("GetDestinationByID", func(t *testing.T) { diff --git a/errors.go b/errors.go index e7051fe..a3766d8 100644 --- a/errors.go +++ b/errors.go @@ -12,6 +12,24 @@ var ErrAdminKey = models.SPVError{Message: "an admin key must be set to be able // ErrMissingXpriv is when xpriv is missing var ErrMissingXpriv = models.SPVError{Message: "xpriv is missing", StatusCode: 401, Code: "error-unauthorized-xpriv-missing"} +// ErrInvalidXpriv is when xpriv is invalid +var ErrInvalidXpriv = models.SPVError{Message: "xpriv is invalid", StatusCode: 401, Code: "error-unauthorized-xpriv-invalid"} + +// ErrInvalidXpub is when xpub is invalid +var ErrInvalidXpub = models.SPVError{Message: "xpub is invalid", StatusCode: 401, Code: "error-unauthorized-xpub-invalid"} + +// ErrInvalidAccessKey is when access key is invalid +var ErrInvalidAccessKey = models.SPVError{Message: "access key is invalid", StatusCode: 401, Code: "error-unauthorized-access-key-invalid"} + +// ErrInvalidAdminKey is when admin key is invalid +var ErrInvalidAdminKey = models.SPVError{Message: "admin key is invalid", StatusCode: 401, Code: "error-unauthorized-admin-key-invalid"} + +// ErrInvalidServerURL is when server url is invalid +var ErrInvalidServerURL = models.SPVError{Message: "server url is invalid", StatusCode: 401, Code: "error-unauthorized-server-url-invalid"} + +// ErrCreateClient is when client creation fails +var ErrCreateClient = models.SPVError{Message: "failed to create client", StatusCode: 500, Code: "error-create-client-failed"} + // ErrMissingKey is when neither xPriv nor adminXPriv is provided var ErrMissingKey = models.SPVError{Message: "neither xPriv nor adminXPriv is provided", StatusCode: 404, Code: "error-shared-config-key-missing"} diff --git a/examples/admin_add_user/admin_add_user.go b/examples/admin_add_user/admin_add_user.go index 440f393..d064513 100644 --- a/examples/admin_add_user/admin_add_user.go +++ b/examples/admin_add_user/admin_add_user.go @@ -19,12 +19,16 @@ func main() { server := "http://localhost:3003/v1" - adminClient := walletclient.NewWithAdminKey(server, examples.ExampleAdminKey) + adminClient, err := walletclient.NewWithAdminKey(server, examples.ExampleAdminKey) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } ctx := context.Background() metadata := map[string]any{"some_metadata": "example"} - err := adminClient.AdminNewXpub(ctx, examples.ExampleXPub, metadata) + err = adminClient.AdminNewXpub(ctx, examples.ExampleXPub, metadata) if err != nil { examples.GetFullErrorMessage(err) os.Exit(1) diff --git a/examples/admin_remove_user/admin_remove_user.go b/examples/admin_remove_user/admin_remove_user.go index db01d5f..95b7949 100644 --- a/examples/admin_remove_user/admin_remove_user.go +++ b/examples/admin_remove_user/admin_remove_user.go @@ -18,10 +18,14 @@ func main() { const server = "http://localhost:3003/v1" - adminClient := walletclient.NewWithAdminKey(server, examples.ExampleAdminKey) + adminClient, err := walletclient.NewWithAdminKey(server, examples.ExampleAdminKey) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } ctx := context.Background() - err := adminClient.AdminDeletePaymail(ctx, examples.ExamplePaymail) + err = adminClient.AdminDeletePaymail(ctx, examples.ExamplePaymail) if err != nil { examples.GetFullErrorMessage(err) os.Exit(1) diff --git a/examples/create_transaction/create_transaction.go b/examples/create_transaction/create_transaction.go index 5aa918b..d3d6a64 100644 --- a/examples/create_transaction/create_transaction.go +++ b/examples/create_transaction/create_transaction.go @@ -19,7 +19,11 @@ func main() { const server = "http://localhost:3003/v1" - client := walletclient.NewWithXPriv(server, examples.ExampleXPriv) + client, err := walletclient.NewWithXPriv(server, examples.ExampleXPriv) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } ctx := context.Background() recipient := walletclient.Recipients{To: "alice@example.com", Satoshis: 1} diff --git a/examples/generate_totp/generate_totp.go b/examples/generate_totp/generate_totp.go index 48f747e..7a06fb8 100644 --- a/examples/generate_totp/generate_totp.go +++ b/examples/generate_totp/generate_totp.go @@ -21,7 +21,11 @@ func main() { const digits = 4 const period = 1200 // 20 minutes - client := walletclient.NewWithXPriv(server, aliceXPriv) + client, err := walletclient.NewWithXPriv(server, aliceXPriv) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } mockContact := &models.Contact{ PubKey: bobPKI, @@ -30,14 +34,14 @@ func main() { totpCode, err := client.GenerateTotpForContact(mockContact, period, digits) if err != nil { - fmt.Println(err) + examples.GetFullErrorMessage(err) os.Exit(1) } fmt.Println("TOTP code from Alice to Bob: ", totpCode) valid, err := client.ValidateTotpForContact(mockContact, totpCode, mockContact.Paymail, period, digits) if err != nil { - fmt.Println(err) + examples.GetFullErrorMessage(err) os.Exit(1) } fmt.Println("Is TOTP code valid: ", valid) diff --git a/examples/get_balance/get_balance.go b/examples/get_balance/get_balance.go index c26bf79..2d0ee6a 100644 --- a/examples/get_balance/get_balance.go +++ b/examples/get_balance/get_balance.go @@ -19,7 +19,11 @@ func main() { const server = "http://localhost:3003/v1" - client := walletclient.NewWithXPriv(server, examples.ExampleXPriv) + client, err := walletclient.NewWithXPriv(server, examples.ExampleXPriv) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } ctx := context.Background() xpubInfo, err := client.GetXPub(ctx) diff --git a/examples/go.mod b/examples/go.mod index b41b537..433c994 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -6,7 +6,7 @@ replace github.com/bitcoin-sv/spv-wallet-go-client => ../ require ( github.com/bitcoin-sv/spv-wallet-go-client v0.0.0-00010101000000-000000000000 - github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.25 + github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.28 ) require ( diff --git a/examples/go.sum b/examples/go.sum index f0bf6d3..33d9aab 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,7 +1,7 @@ github.com/bitcoin-sv/go-sdk v1.1.8 h1:f/dDo+qBtjDvNmMagEiNihRS10ca93eNnZOoTG0HXk8= github.com/bitcoin-sv/go-sdk v1.1.8/go.mod h1:NOAkJLbjqKOLuxJmb9ABG86ExTZp4HS8+iygiDIUps4= -github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.25 h1:OygyRn44GhQJQGhvp2vqTjXQRABXK+EA5ZvL6WE84nU= -github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.25/go.mod h1:PEJdH9ZWKOiKHyOZkzYsRbKuZjzlRaEJy3GsM75Icdo= +github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.28 h1:Oo+taJSCcWM1FuEBOSO7x27WDXG55jl43MtjQui4h/A= +github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.28/go.mod h1:PEJdH9ZWKOiKHyOZkzYsRbKuZjzlRaEJy3GsM75Icdo= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4= github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= diff --git a/examples/handle_exceptions/handle_exceptions.go b/examples/handle_exceptions/handle_exceptions.go index 39558fe..ed55de4 100644 --- a/examples/handle_exceptions/handle_exceptions.go +++ b/examples/handle_exceptions/handle_exceptions.go @@ -23,14 +23,17 @@ func main() { const server = "http://localhost:3003/v1" - client := walletclient.NewWithXPub(server, examples.ExampleAdminKey) + client, err := walletclient.NewWithXPub(server, examples.ExampleAdminKey) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } ctx := context.Background() fmt.Println("Client created") status, err := client.AdminGetStatus(ctx) if err != nil { - fmt.Println("Error: ", err) examples.GetFullErrorMessage(err) os.Exit(1) } diff --git a/examples/list_transactions/list_transactions.go b/examples/list_transactions/list_transactions.go index 086e1e9..670c2f2 100644 --- a/examples/list_transactions/list_transactions.go +++ b/examples/list_transactions/list_transactions.go @@ -20,7 +20,11 @@ func main() { const server = "http://localhost:3003/v1" - client := walletclient.NewWithXPriv(server, examples.ExampleXPriv) + client, err := walletclient.NewWithXPriv(server, examples.ExampleXPriv) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } ctx := context.Background() metadata := map[string]any{} diff --git a/examples/send_op_return/send_op_return.go b/examples/send_op_return/send_op_return.go index 3a3fc14..04aae50 100644 --- a/examples/send_op_return/send_op_return.go +++ b/examples/send_op_return/send_op_return.go @@ -20,7 +20,11 @@ func main() { const server = "http://localhost:3003/v1" - client := walletclient.NewWithXPriv(server, examples.ExampleXPriv) + client, err := walletclient.NewWithXPriv(server, examples.ExampleXPriv) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } ctx := context.Background() metadata := map[string]any{} diff --git a/examples/webhooks/webhooks.go b/examples/webhooks/webhooks.go index e1cb70d..3dc1b64 100644 --- a/examples/webhooks/webhooks.go +++ b/examples/webhooks/webhooks.go @@ -23,16 +23,21 @@ func main() { examples.CheckIfAdminKeyExists() - client := walletclient.NewWithAdminKey("http://localhost:3003/v1", examples.ExampleAdminKey) + client, err := walletclient.NewWithAdminKey("http://localhost:3003/v1", examples.ExampleAdminKey) + if err != nil { + examples.GetFullErrorMessage(err) + os.Exit(1) + } wh := notifications.NewWebhook( client, "http://localhost:5005/notification", notifications.WithToken("Authorization", "this-is-the-token"), notifications.WithProcessors(3), ) - err := wh.Subscribe(context.Background()) + err = wh.Subscribe(context.Background()) if err != nil { - panic(err) + examples.GetFullErrorMessage(err) + os.Exit(1) } http.Handle("/notification", wh.HTTPHandler()) @@ -40,7 +45,8 @@ func main() { // show all subscribed webhooks (including the current one) allWebhooks, err := client.AdminGetWebhooks(context.Background()) if err != nil { - panic(err) + examples.GetFullErrorMessage(err) + os.Exit(1) } fmt.Println("Subscribed webhooks list") for _, item := range allWebhooks { @@ -51,14 +57,16 @@ func main() { time.Sleep(50 * time.Millisecond) // simulate processing time fmt.Printf("Processing event-string: %s\n", gpe.Value) }); err != nil { - panic(err) + examples.GetFullErrorMessage(err) + os.Exit(1) } if err = notifications.RegisterHandler(wh, func(gpe *models.TransactionEvent) { time.Sleep(50 * time.Millisecond) // simulate processing time fmt.Printf("Processing event-transaction: XPubID: %s, TxID: %s, Status: %s\n", gpe.XPubID, gpe.TransactionID, gpe.Status) }); err != nil { - panic(err) + examples.GetFullErrorMessage(err) + os.Exit(1) } server := http.Server{ @@ -77,11 +85,13 @@ func main() { fmt.Printf("Unsubscribing...\n") if err = wh.Unsubscribe(context.Background()); err != nil { - panic(err) + examples.GetFullErrorMessage(err) + os.Exit(1) } fmt.Printf("Shutting down...\n") if err = server.Shutdown(context.Background()); err != nil { - panic(err) + examples.GetFullErrorMessage(err) + os.Exit(1) } } diff --git a/go.mod b/go.mod index d14c5e5..e80d731 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,7 @@ go 1.22.5 require ( github.com/bitcoin-sv/go-sdk v1.1.8 - github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.25 - github.com/pkg/errors v0.9.1 + github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.28 github.com/pquerna/otp v1.4.0 github.com/stretchr/testify v1.9.0 ) @@ -14,6 +13,7 @@ require ( github.com/boombuler/barcode v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect golang.org/x/crypto v0.26.0 // indirect diff --git a/go.sum b/go.sum index 3d1762b..5784316 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/bitcoin-sv/go-sdk v1.1.8 h1:f/dDo+qBtjDvNmMagEiNihRS10ca93eNnZOoTG0HXk8= github.com/bitcoin-sv/go-sdk v1.1.8/go.mod h1:NOAkJLbjqKOLuxJmb9ABG86ExTZp4HS8+iygiDIUps4= -github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.25 h1:OygyRn44GhQJQGhvp2vqTjXQRABXK+EA5ZvL6WE84nU= -github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.25/go.mod h1:PEJdH9ZWKOiKHyOZkzYsRbKuZjzlRaEJy3GsM75Icdo= +github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.28 h1:Oo+taJSCcWM1FuEBOSO7x27WDXG55jl43MtjQui4h/A= +github.com/bitcoin-sv/spv-wallet/models v1.0.0-beta.28/go.mod h1:PEJdH9ZWKOiKHyOZkzYsRbKuZjzlRaEJy3GsM75Icdo= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4= github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= diff --git a/regression_tests/utils.go b/regression_tests/utils.go index 94a0519..3ce1158 100644 --- a/regression_tests/utils.go +++ b/regression_tests/utils.go @@ -67,7 +67,10 @@ func getEnvVariables() (*regressionTestConfig, error) { // getPaymailDomain retrieves the shared configuration from the SPV Wallet. func getPaymailDomain(ctx context.Context, xpriv string, clientUrl string) (string, error) { - wc := walletclient.NewWithXPriv(clientUrl, xpriv) + wc, err := walletclient.NewWithXPriv(clientUrl, xpriv) + if err != nil { + return "", err + } sharedConfig, err := wc.GetSharedConfig(ctx) if err != nil { return "", err @@ -91,7 +94,10 @@ func createUser(ctx context.Context, paymail string, paymailDomain string, insta Paymail: preparePaymail(paymail, paymailDomain), } - adminClient := walletclient.NewWithAdminKey(instanceUrl, adminXPriv) + adminClient, err := walletclient.NewWithAdminKey(instanceUrl, adminXPriv) + if err != nil { + return nil, err + } if err := adminClient.AdminNewXpub(ctx, user.XPub, map[string]any{"some_metadata": "remove"}); err != nil { return nil, err @@ -107,8 +113,11 @@ func createUser(ctx context.Context, paymail string, paymailDomain string, insta // removeRegisteredPaymail soft deletes paymail from the SPV Wallet. func removeRegisteredPaymail(ctx context.Context, paymail string, instanceURL string, adminXPriv string) error { - adminClient := walletclient.NewWithAdminKey(instanceURL, adminXPriv) - err := adminClient.AdminDeletePaymail(ctx, paymail) + adminClient, err := walletclient.NewWithAdminKey(instanceURL, adminXPriv) + if err != nil { + return err + } + err = adminClient.AdminDeletePaymail(ctx, paymail) if err != nil { return err } @@ -117,8 +126,10 @@ func removeRegisteredPaymail(ctx context.Context, paymail string, instanceURL st // getBalance retrieves the balance from the SPV Wallet. func getBalance(ctx context.Context, fromInstance string, fromXPriv string) (int, error) { - client := walletclient.NewWithXPriv(fromInstance, fromXPriv) - + client, err := walletclient.NewWithXPriv(fromInstance, fromXPriv) + if err != nil { + return -1, err + } xpubInfo, err := client.GetXPub(ctx) if err != nil { return -1, err @@ -128,7 +139,10 @@ func getBalance(ctx context.Context, fromInstance string, fromXPriv string) (int // getTransactions retrieves the transactions from the SPV Wallet. func getTransactions(ctx context.Context, fromInstance string, fromXPriv string) ([]*models.Transaction, error) { - client := walletclient.NewWithXPriv(fromInstance, fromXPriv) + client, err := walletclient.NewWithXPriv(fromInstance, fromXPriv) + if err != nil { + return nil, err + } metadata := map[string]any{} conditions := filter.TransactionFilter{} @@ -143,7 +157,10 @@ func getTransactions(ctx context.Context, fromInstance string, fromXPriv string) // sendFunds sends funds from one paymail to another. func sendFunds(ctx context.Context, fromInstance string, fromXPriv string, toPaymail string, howMuch int) (*models.Transaction, error) { - client := walletclient.NewWithXPriv(fromInstance, fromXPriv) + client, err := walletclient.NewWithXPriv(fromInstance, fromXPriv) + if err != nil { + return nil, err + } balance, err := getBalance(ctx, fromInstance, fromXPriv) if err != nil { diff --git a/totp_test.go b/totp_test.go index a35de43..d2c3e24 100644 --- a/totp_test.go +++ b/totp_test.go @@ -17,7 +17,8 @@ import ( func TestGenerateTotpForContact(t *testing.T) { t.Run("success", func(t *testing.T) { // given - sut := NewWithXPriv("localhost:3001", fixtures.XPrivString) + sut, err := NewWithXPriv("localhost:3001", fixtures.XPrivString) + require.NoError(t, err) require.NotNil(t, sut.xPriv) contact := models.Contact{PubKey: fixtures.PubKey} @@ -31,10 +32,11 @@ func TestGenerateTotpForContact(t *testing.T) { t.Run("WalletClient without xPriv - returns error", func(t *testing.T) { // given - sut := NewWithXPub("localhost:3001", fixtures.XPubString) + sut, err := NewWithXPub("localhost:3001", fixtures.XPubString) + require.NoError(t, err) require.NotNil(t, sut.xPub) // when - _, err := sut.GenerateTotpForContact(nil, 30, 2) + _, err = sut.GenerateTotpForContact(nil, 30, 2) // then require.ErrorIs(t, err, ErrMissingXpriv) @@ -42,12 +44,13 @@ func TestGenerateTotpForContact(t *testing.T) { t.Run("contact has invalid PubKey - returns error", func(t *testing.T) { // given - sut := NewWithXPriv("localhost:3001", fixtures.XPrivString) + sut, err := NewWithXPriv("localhost:3001", fixtures.XPrivString) + require.NoError(t, err) require.NotNil(t, sut.xPriv) contact := models.Contact{PubKey: "invalid-pk-format"} // when - _, err := sut.GenerateTotpForContact(&contact, 30, 2) + _, err = sut.GenerateTotpForContact(&contact, 30, 2) // then require.ErrorIs(t, err, ErrContactPubKeyInvalid) @@ -71,9 +74,11 @@ func TestValidateTotpForContact(t *testing.T) { require.NoError(t, err) // Set up the WalletClient for Alice and Bob - clientAlice := NewWithXPriv(serverURL, aliceKeys.XPriv()) + clientAlice, err := NewWithXPriv(serverURL, aliceKeys.XPriv()) + require.NoError(t, err) require.NotNil(t, clientAlice.xPriv) - clientBob := NewWithXPriv(serverURL, bobKeys.XPriv()) + clientBob, err := NewWithXPriv(serverURL, bobKeys.XPriv()) + require.NoError(t, err) require.NotNil(t, clientBob.xPriv) aliceContact := &models.Contact{ @@ -94,20 +99,16 @@ func TestValidateTotpForContact(t *testing.T) { require.True(t, result) }) - t.Run("WalletClient without xPriv - returns error", func(t *testing.T) { - client := NewWithXPub(serverURL, "invalid_xpub") - require.Nil(t, client.xPub) - }) - t.Run("contact has invalid PubKey - returns error", func(t *testing.T) { - sut := NewWithXPriv(serverURL, fixtures.XPrivString) + sut, err := NewWithXPriv(serverURL, fixtures.XPrivString) + require.NoError(t, err) invalidContact := &models.Contact{ PubKey: "invalid_pub_key_format", Paymail: "invalid@example.com", } - _, err := sut.ValidateTotpForContact(invalidContact, "123456", "someone@example.com", 3600, 6) + _, err = sut.ValidateTotpForContact(invalidContact, "123456", "someone@example.com", 3600, 6) require.Error(t, err) require.Contains(t, err.Error(), "contact's PubKey is invalid") }) diff --git a/transactions_test.go b/transactions_test.go index f359cce..7bc6175 100644 --- a/transactions_test.go +++ b/transactions_test.go @@ -35,7 +35,8 @@ func TestTransactions(t *testing.T) { })) defer server.Close() - client := NewWithXPriv(server.URL, fixtures.XPrivString) + client, err := NewWithXPriv(server.URL, fixtures.XPrivString) + require.NoError(t, err) require.NotNil(t, client.xPriv) t.Run("GetTransaction", func(t *testing.T) { diff --git a/walletclient.go b/walletclient.go index f6bff95..412f7d5 100644 --- a/walletclient.go +++ b/walletclient.go @@ -22,7 +22,7 @@ type WalletClient struct { // It configures the client with a specific server URL and a flag indicating whether requests should be signed. // - `xPriv`: The extended private key used for cryptographic operations. // - `serverURL`: The URL of the server the client will interact with. ex. https://hostname:3003 -func NewWithXPriv(serverURL, xPriv string) *WalletClient { +func NewWithXPriv(serverURL, xPriv string) (*WalletClient, error) { return makeClient( &xPrivConf{XPrivString: xPriv}, &httpConf{ServerURL: serverURL}, @@ -34,7 +34,7 @@ func NewWithXPriv(serverURL, xPriv string) *WalletClient { // This client is configured for operations that require a public key, such as verifying signatures or receiving transactions. // - `xPub`: The extended public key used for cryptographic verification and other public operations. // - `serverURL`: The URL of the server the client will interact with. ex. https://hostname:3003 -func NewWithXPub(serverURL, xPub string) *WalletClient { +func NewWithXPub(serverURL, xPub string) (*WalletClient, error) { return makeClient( &xPubConf{XPubString: xPub}, &httpConf{ServerURL: serverURL}, @@ -46,7 +46,7 @@ func NewWithXPub(serverURL, xPub string) *WalletClient { // This configuration is typically used for administrative tasks such as managing sub-wallets or configuring system-wide settings. // - `adminKey`: The extended private key used for administrative operations. // - `serverURL`: The URL of the server the client will interact with. ex. https://hostname:3003 -func NewWithAdminKey(serverURL, adminKey string) *WalletClient { +func NewWithAdminKey(serverURL, adminKey string) (*WalletClient, error) { return makeClient( &adminKeyConf{AdminKeyString: adminKey}, &httpConf{ServerURL: serverURL}, @@ -58,7 +58,7 @@ func NewWithAdminKey(serverURL, adminKey string) *WalletClient { // This method is useful for scenarios where the client needs to authenticate using a less sensitive key than an xPriv. // - `accessKey`: The access key used for API authentication. // - `serverURL`: The URL of the server the client will interact with. ex. https://hostname:3003 -func NewWithAccessKey(serverURL, accessKey string) *WalletClient { +func NewWithAccessKey(serverURL, accessKey string) (*WalletClient, error) { return makeClient( &accessKeyConf{AccessKeyString: accessKey}, &httpConf{ServerURL: serverURL}, @@ -67,14 +67,18 @@ func NewWithAccessKey(serverURL, accessKey string) *WalletClient { } // makeClient creates a new WalletClient using the provided configuration options. -func makeClient(configurators ...configurator) *WalletClient { +func makeClient(configurators ...configurator) (*WalletClient, error) { client := &WalletClient{} + var err error for _, configurator := range configurators { - configurator.Configure(client) + err = configurator.Configure(client) + if err != nil { + return nil, ErrCreateClient.Wrap(err) + } } - return client + return client, nil } // addSignature will add the signature to the request @@ -83,7 +87,7 @@ func addSignature(header *http.Header, xPriv *bip32.ExtendedKey, bodyString stri } // SetAdminKeyByString will set aminXPriv key -func (wc *WalletClient) SetAdminKeyByString(adminKey string) { +func (wc *WalletClient) SetAdminKeyByString(adminKey string) error { keyConf := accessKeyConf{AccessKeyString: adminKey} - keyConf.Configure(wc) + return keyConf.Configure(wc) } diff --git a/walletclient_test.go b/walletclient_test.go index 0c7fc07..399a9ff 100644 --- a/walletclient_test.go +++ b/walletclient_test.go @@ -26,7 +26,8 @@ func TestNewWalletClient(t *testing.T) { t.Run("NewWalletClientWithXPrivate success", func(t *testing.T) { keys, err := xpriv.Generate() require.NoError(t, err) - client := NewWithXPriv(serverURL, keys.XPriv()) + client, err := NewWithXPriv(serverURL, keys.XPriv()) + require.NoError(t, err) require.NotNil(t, client.xPriv) require.Equal(t, keys.XPriv(), client.xPriv.String()) require.NotNil(t, client.httpClient) @@ -53,14 +54,16 @@ func TestNewWalletClient(t *testing.T) { t.Run("NewWalletClientWithXPrivate fail", func(t *testing.T) { xPriv := "invalid_key" - client := NewWithXPriv(xPriv, "http://example.com") - require.Nil(t, client.xPriv) + client, err := NewWithXPriv(xPriv, "http://example.com") + require.ErrorIs(t, err, ErrInvalidXpriv) + require.Nil(t, client) }) t.Run("NewWalletClientWithXPublic success", func(t *testing.T) { keys, err := xpriv.Generate() require.NoError(t, err) - client := NewWithXPub(serverURL, keys.XPub().String()) + client, err := NewWithXPub(serverURL, keys.XPub().String()) + require.NoError(t, err) require.NotNil(t, client.xPub) require.Equal(t, keys.XPub().String(), client.xPub.String()) require.NotNil(t, client.httpClient) @@ -84,12 +87,14 @@ func TestNewWalletClient(t *testing.T) { }) t.Run("NewWalletClientWithXPublic fail", func(t *testing.T) { - client := NewWithXPub(serverURL, "invalid_key") - require.Nil(t, client.xPub) + client, err := NewWithXPub(serverURL, "invalid_key") + require.ErrorIs(t, err, ErrInvalidXpub) + require.Nil(t, client) }) t.Run("NewWalletClientWithAdminKey success", func(t *testing.T) { - client := NewWithAdminKey(server.URL, fixtures.XPrivString) + client, err := NewWithAdminKey(server.URL, fixtures.XPrivString) + require.NoError(t, err) require.NotNil(t, client.adminXPriv) require.Nil(t, client.xPriv) require.Equal(t, fixtures.XPrivString, client.adminXPriv.String()) @@ -117,13 +122,15 @@ func TestNewWalletClient(t *testing.T) { }) t.Run("NewWalletClientWithAdminKey fail", func(t *testing.T) { - client := NewWithAdminKey(serverURL, "invalid_key") - require.Nil(t, client.adminXPriv) + client, err := NewWithAdminKey(serverURL, "invalid_key") + require.ErrorIs(t, err, ErrInvalidAdminKey) + require.Nil(t, client) }) t.Run("NewWalletClientWithAccessKey success", func(t *testing.T) { // Attempt to create a new WalletClient with an access key - client := NewWithAccessKey(server.URL, fixtures.AccessKeyString) + client, err := NewWithAccessKey(server.URL, fixtures.AccessKeyString) + require.NoError(t, err) require.NotNil(t, client.accessKey) require.Equal(t, serverURL, client.server) @@ -150,7 +157,8 @@ func TestNewWalletClient(t *testing.T) { }) t.Run("NewWalletClientWithAccessKey fail", func(t *testing.T) { - client := NewWithAccessKey(serverURL, "invalid_key") - require.Nil(t, client.accessKey) + client, err := NewWithAccessKey(serverURL, "invalid_key") + require.ErrorIs(t, err, ErrInvalidAccessKey) + require.Nil(t, client) }) } diff --git a/xpubs_test.go b/xpubs_test.go index 8a87d81..dfddea9 100644 --- a/xpubs_test.go +++ b/xpubs_test.go @@ -41,7 +41,8 @@ func TestXpub(t *testing.T) { keys, err := xpriv.Generate() require.NoError(t, err) - client := NewWithXPriv(server.URL, keys.XPriv()) + client, err := NewWithXPriv(server.URL, keys.XPriv()) + require.NoError(t, err) require.NotNil(t, client.xPriv) t.Run("GetXPub", func(t *testing.T) {