From d3bf225d222c500a42b0bbe675aa0f9cddf91ad4 Mon Sep 17 00:00:00 2001 From: jakubmkowalski Date: Mon, 18 Mar 2024 17:39:35 +0100 Subject: [PATCH 1/3] feat: adds accept and reject contact functions --- contacts.go | 17 ++++++++++++++ contacts_test.go | 52 +++++++++++++++++++++++++++++++++++++++++ transports/http.go | 22 +++++++++++++++++ transports/interface.go | 7 ++++++ 4 files changed, 98 insertions(+) create mode 100644 contacts.go create mode 100644 contacts_test.go diff --git a/contacts.go b/contacts.go new file mode 100644 index 0000000..c0944c6 --- /dev/null +++ b/contacts.go @@ -0,0 +1,17 @@ +package walletclient + +import ( + "context" + + "github.com/bitcoin-sv/spv-wallet-go-client/transports" +) + +// AcceptContact accepts a contact by paymail +func (b *WalletClient) AcceptContact(ctx context.Context, paymail string) transports.ResponseError { + return b.transport.AcceptContact(ctx, paymail) +} + +// RejectContact rejects a contact by paymail +func (b *WalletClient) RejectContact(ctx context.Context, paymail string) transports.ResponseError { + return b.transport.RejectContact(ctx, paymail) +} diff --git a/contacts_test.go b/contacts_test.go new file mode 100644 index 0000000..596d83a --- /dev/null +++ b/contacts_test.go @@ -0,0 +1,52 @@ +package walletclient + +import ( + "context" + "testing" + + "github.com/bitcoin-sv/spv-wallet-go-client/fixtures" + "github.com/stretchr/testify/assert" +) + +// TestAcceptContact will test the AcceptContact method +func TestAcceptContact(t *testing.T) { + transportHandler := testTransportHandler{ + Type: fixtures.RequestType, + Path: "/contact/accept/", + ClientURL: fixtures.ServerURL, + Client: WithHTTPClient, + } + + t.Run("AcceptContact", func(t *testing.T) { + // given + client := getTestWalletClient(transportHandler, true) + + // when + err := client.AcceptContact(context.Background(), fixtures.PaymailAddress) + + // then + assert.NoError(t, err) + }) +} + +// TestRejectContact will test the RejectContact method +func TestRejectContact(t *testing.T) { + transportHandler := testTransportHandler{ + Type: fixtures.RequestType, + Path: "/contact/reject/", + Result: fixtures.MarshallForTestHandler(""), + ClientURL: fixtures.ServerURL, + Client: WithHTTPClient, + } + + t.Run("RejectContact", func(t *testing.T) { + // given + client := getTestWalletClient(transportHandler, true) + + // when + err := client.RejectContact(context.Background(), fixtures.PaymailAddress) + + // then + assert.NoError(t, err) + }) +} diff --git a/transports/http.go b/transports/http.go index c827953..5b25c7d 100644 --- a/transports/http.go +++ b/transports/http.go @@ -639,3 +639,25 @@ func (h *TransportHTTP) authenticateWithXpriv(sign bool, req *http.Request, xPri func (h *TransportHTTP) authenticateWithAccessKey(req *http.Request, rawJSON []byte) ResponseError { return SetSignatureFromAccessKey(&req.Header, hex.EncodeToString(h.accessKey.Serialise()), string(rawJSON)) } + +// AcceptContact will accept a contact by paymail +func (h *TransportHTTP) AcceptContact(ctx context.Context, paymail string) ResponseError { + if err := h.doHTTPRequest( + ctx, http.MethodPatch, "/contact/accept/"+paymail, nil, h.xPriv, h.signRequest, nil, + ); err != nil { + return err + } + + return nil +} + +// RejectContact will accept a contact by paymail +func (h *TransportHTTP) RejectContact(ctx context.Context, paymail string) ResponseError { + if err := h.doHTTPRequest( + ctx, http.MethodPatch, "/contact/reject/"+paymail, nil, h.xPriv, h.signRequest, nil, + ); err != nil { + return err + } + + return nil +} diff --git a/transports/interface.go b/transports/interface.go index dcec8ee..f98664e 100644 --- a/transports/interface.go +++ b/transports/interface.go @@ -49,6 +49,12 @@ type TransactionService interface { GetUtxosCount(ctx context.Context, conditions map[string]interface{}, metadata *models.Metadata) (int64, ResponseError) } +// ContactService is the contact related requests +type ContactService interface { + AcceptContact(ctx context.Context, paymail string) ResponseError + RejectContact(ctx context.Context, paymail string) ResponseError +} + // AdminService is the admin related requests type AdminService interface { AdminGetStatus(ctx context.Context) (bool, ResponseError) @@ -79,6 +85,7 @@ type AdminService interface { type TransportService interface { AccessKeyService AdminService + ContactService DestinationService TransactionService XpubService From bbe0b0fc39dd2d0a1d18244a17e9f2a1f608bab6 Mon Sep 17 00:00:00 2001 From: arkadiuszos4chain Date: Tue, 2 Apr 2024 14:10:21 +0200 Subject: [PATCH 2/3] feat(BUX-639): adjust accept/reject contact routings; add confirm/search contacts functions --- contacts.go | 15 +++++++- contacts_test.go | 83 +++++++++++++++++++++++------------------ transports/http.go | 42 ++++++++++++++++++--- transports/interface.go | 2 + 4 files changed, 98 insertions(+), 44 deletions(-) diff --git a/contacts.go b/contacts.go index c0944c6..02c392a 100644 --- a/contacts.go +++ b/contacts.go @@ -4,14 +4,25 @@ import ( "context" "github.com/bitcoin-sv/spv-wallet-go-client/transports" + "github.com/bitcoin-sv/spv-wallet/models" ) -// AcceptContact accepts a contact by paymail +// AcceptContact will accept the contact associated with the paymail func (b *WalletClient) AcceptContact(ctx context.Context, paymail string) transports.ResponseError { return b.transport.AcceptContact(ctx, paymail) } -// RejectContact rejects a contact by paymail +// RejectContact will reject the contact associated with the paymail func (b *WalletClient) RejectContact(ctx context.Context, paymail string) transports.ResponseError { return b.transport.RejectContact(ctx, paymail) } + +// ConfirmContact will confirm the contact associated with the paymail +func (b *WalletClient) ConfirmContact(ctx context.Context, paymail string) transports.ResponseError { + return b.transport.ConfirmContact(ctx, paymail) +} + +// GetContacts will get contacts by conditions +func (b *WalletClient) GetContacts(ctx context.Context, conditions map[string]interface{}, metadata *models.Metadata, queryParams *transports.QueryParams) ([]*models.Contact, transports.ResponseError) { + return b.transport.GetContacts(ctx, conditions, metadata, queryParams) +} diff --git a/contacts_test.go b/contacts_test.go index 596d83a..0f3740b 100644 --- a/contacts_test.go +++ b/contacts_test.go @@ -8,45 +8,54 @@ import ( "github.com/stretchr/testify/assert" ) -// TestAcceptContact will test the AcceptContact method -func TestAcceptContact(t *testing.T) { - transportHandler := testTransportHandler{ - Type: fixtures.RequestType, - Path: "/contact/accept/", - ClientURL: fixtures.ServerURL, - Client: WithHTTPClient, - } - - t.Run("AcceptContact", func(t *testing.T) { - // given - client := getTestWalletClient(transportHandler, true) - - // when - err := client.AcceptContact(context.Background(), fixtures.PaymailAddress) - - // then - assert.NoError(t, err) - }) -} - // TestRejectContact will test the RejectContact method -func TestRejectContact(t *testing.T) { - transportHandler := testTransportHandler{ - Type: fixtures.RequestType, - Path: "/contact/reject/", - Result: fixtures.MarshallForTestHandler(""), - ClientURL: fixtures.ServerURL, - Client: WithHTTPClient, +func TestContactActionsRouting(t *testing.T) { + tcs := []struct { + name string + route string + f func(c *WalletClient) error + }{ + { + name: "RejectContact", + route: "/contact/rejected/", + f: func(c *WalletClient) error { return c.RejectContact(context.Background(), fixtures.PaymailAddress) }, + }, + { + name: "AcceptContact", + route: "/contact/accepted/", + f: func(c *WalletClient) error { return c.AcceptContact(context.Background(), fixtures.PaymailAddress) }, + }, + { + name: "ConfirmContact", + route: "/contact/confirmed/", + f: func(c *WalletClient) error { return c.ConfirmContact(context.Background(), fixtures.PaymailAddress) }, + }, + { + name: "GetContacts", + route: "/contact/search/", + f: func(c *WalletClient) error { _, err := c.GetContacts(context.Background(), nil, nil, nil); return err }, + }, } - t.Run("RejectContact", func(t *testing.T) { - // given - client := getTestWalletClient(transportHandler, true) - - // when - err := client.RejectContact(context.Background(), fixtures.PaymailAddress) + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + // given + tmq := testTransportHandler{ + Type: fixtures.RequestType, + Path: tc.route, + Result: "[]", + ClientURL: fixtures.ServerURL, + Client: WithHTTPClient, + } + + client := getTestWalletClient(tmq, true) + + // when + err := tc.f(client) + + // then + assert.NoError(t, err) + }) + } - // then - assert.NoError(t, err) - }) } diff --git a/transports/http.go b/transports/http.go index 5b25c7d..04a52dd 100644 --- a/transports/http.go +++ b/transports/http.go @@ -329,7 +329,7 @@ func (h *TransportHTTP) GetTransaction(ctx context.Context, txID string) (*model return &transaction, nil } -// GetTransactions will get a transactions by conditions +// GetTransactions will get transactions by conditions func (h *TransportHTTP) GetTransactions(ctx context.Context, conditions map[string]interface{}, metadataConditions *models.Metadata, queryParams *QueryParams, ) ([]*models.Transaction, ResponseError) { @@ -640,10 +640,10 @@ func (h *TransportHTTP) authenticateWithAccessKey(req *http.Request, rawJSON []b return SetSignatureFromAccessKey(&req.Header, hex.EncodeToString(h.accessKey.Serialise()), string(rawJSON)) } -// AcceptContact will accept a contact by paymail +// AcceptContact will accept the contact associated with the paymail func (h *TransportHTTP) AcceptContact(ctx context.Context, paymail string) ResponseError { if err := h.doHTTPRequest( - ctx, http.MethodPatch, "/contact/accept/"+paymail, nil, h.xPriv, h.signRequest, nil, + ctx, http.MethodPatch, "/contact/accepted/"+paymail, nil, h.xPriv, h.signRequest, nil, ); err != nil { return err } @@ -651,13 +651,45 @@ func (h *TransportHTTP) AcceptContact(ctx context.Context, paymail string) Respo return nil } -// RejectContact will accept a contact by paymail +// RejectContact will reject the contact associated with the paymail func (h *TransportHTTP) RejectContact(ctx context.Context, paymail string) ResponseError { if err := h.doHTTPRequest( - ctx, http.MethodPatch, "/contact/reject/"+paymail, nil, h.xPriv, h.signRequest, nil, + ctx, http.MethodPatch, "/contact/rejected/"+paymail, nil, h.xPriv, h.signRequest, nil, ); err != nil { return err } return nil } + +// ConfirmContact will confirm the contact associated with the paymail +func (h *TransportHTTP) ConfirmContact(ctx context.Context, paymail string) ResponseError { + if err := h.doHTTPRequest( + ctx, http.MethodPatch, "/contact/confirmed/"+paymail, nil, h.xPriv, h.signRequest, nil, + ); err != nil { + return err + } + + return nil +} + +// GetContacts will get contacts by conditions +func (h *TransportHTTP) GetContacts(ctx context.Context, conditions map[string]interface{}, metadata *models.Metadata, queryParams *QueryParams) ([]*models.Contact, ResponseError) { + jsonStr, err := json.Marshal(map[string]interface{}{ + FieldConditions: conditions, + FieldMetadata: processMetadata(metadata), + FieldQueryParams: queryParams, + }) + if err != nil { + return nil, WrapError(err) + } + + var result []*models.Contact + if err := h.doHTTPRequest( + ctx, http.MethodPost, "/contact/search", jsonStr, h.xPriv, h.signRequest, &result, + ); err != nil { + return nil, err + } + + return result, nil +} diff --git a/transports/interface.go b/transports/interface.go index f98664e..73c83c2 100644 --- a/transports/interface.go +++ b/transports/interface.go @@ -53,6 +53,8 @@ type TransactionService interface { type ContactService interface { AcceptContact(ctx context.Context, paymail string) ResponseError RejectContact(ctx context.Context, paymail string) ResponseError + ConfirmContact(ctx context.Context, paymail string) ResponseError + GetContacts(ctx context.Context, conditions map[string]interface{}, metadata *models.Metadata, queryParams *QueryParams) ([]*models.Contact, ResponseError) } // AdminService is the admin related requests From f43bb1d61e7cd83fcb75c3389ca236754929e8f5 Mon Sep 17 00:00:00 2001 From: arkadiuszos4chain Date: Tue, 2 Apr 2024 14:48:51 +0200 Subject: [PATCH 3/3] feat(BUX-639): add upsert contact --- contacts.go | 10 +++++++ contacts_test.go | 58 +++++++++++++++++++++++++++++------------ transports/http.go | 26 ++++++++++++++++++ transports/interface.go | 1 + 4 files changed, 79 insertions(+), 16 deletions(-) diff --git a/contacts.go b/contacts.go index 02c392a..0c11107 100644 --- a/contacts.go +++ b/contacts.go @@ -7,6 +7,16 @@ import ( "github.com/bitcoin-sv/spv-wallet/models" ) +// UpsertContact add or update contact. When adding a new contact, the system utilizes Paymail's PIKE capability to dispatch an invitation request, asking the counterparty to include the current user in their contacts. +func (b *WalletClient) UpsertContact(ctx context.Context, paymail, fullName string, metadata *models.Metadata) (*models.Contact, transports.ResponseError) { + return b.transport.UpsertContact(ctx, paymail, fullName, metadata, "") +} + +// UpsertContactForPaymail add or update contact. When adding a new contact, the system utilizes Paymail's PIKE capability to dispatch an invitation request, asking the counterparty to include the current user specified paymail in their contacts. +func (b *WalletClient) UpsertContactForPaymail(ctx context.Context, paymail, fullName string, metadata *models.Metadata, requesterPaymail string) (*models.Contact, transports.ResponseError) { + return b.transport.UpsertContact(ctx, paymail, fullName, metadata, requesterPaymail) +} + // AcceptContact will accept the contact associated with the paymail func (b *WalletClient) AcceptContact(ctx context.Context, paymail string) transports.ResponseError { return b.transport.AcceptContact(ctx, paymail) diff --git a/contacts_test.go b/contacts_test.go index 0f3740b..ab468bb 100644 --- a/contacts_test.go +++ b/contacts_test.go @@ -11,29 +11,55 @@ import ( // TestRejectContact will test the RejectContact method func TestContactActionsRouting(t *testing.T) { tcs := []struct { - name string - route string - f func(c *WalletClient) error + name string + route string + responsePayload string + f func(c *WalletClient) error }{ { - name: "RejectContact", - route: "/contact/rejected/", - f: func(c *WalletClient) error { return c.RejectContact(context.Background(), fixtures.PaymailAddress) }, + name: "RejectContact", + route: "/contact/rejected/", + responsePayload: "{}", + f: func(c *WalletClient) error { return c.RejectContact(context.Background(), fixtures.PaymailAddress) }, }, { - name: "AcceptContact", - route: "/contact/accepted/", - f: func(c *WalletClient) error { return c.AcceptContact(context.Background(), fixtures.PaymailAddress) }, + name: "AcceptContact", + route: "/contact/accepted/", + responsePayload: "{}", + f: func(c *WalletClient) error { return c.AcceptContact(context.Background(), fixtures.PaymailAddress) }, }, { - name: "ConfirmContact", - route: "/contact/confirmed/", - f: func(c *WalletClient) error { return c.ConfirmContact(context.Background(), fixtures.PaymailAddress) }, + name: "ConfirmContact", + route: "/contact/confirmed/", + responsePayload: "{}", + f: func(c *WalletClient) error { return c.ConfirmContact(context.Background(), fixtures.PaymailAddress) }, }, { - name: "GetContacts", - route: "/contact/search/", - f: func(c *WalletClient) error { _, err := c.GetContacts(context.Background(), nil, nil, nil); return err }, + name: "GetContacts", + route: "/contact/search/", + responsePayload: "[]", + f: func(c *WalletClient) error { + _, err := c.GetContacts(context.Background(), nil, nil, nil) + return err + }, + }, + { + name: "UpsertContact", + route: "/contact/", + responsePayload: "{}", + f: func(c *WalletClient) error { + _, err := c.UpsertContact(context.Background(), "", "", nil) + return err + }, + }, + { + name: "UpsertContactForPaymail", + route: "/contact/", + responsePayload: "{}", + f: func(c *WalletClient) error { + _, err := c.UpsertContactForPaymail(context.Background(), "", "", nil, "") + return err + }, }, } @@ -43,7 +69,7 @@ func TestContactActionsRouting(t *testing.T) { tmq := testTransportHandler{ Type: fixtures.RequestType, Path: tc.route, - Result: "[]", + Result: tc.responsePayload, ClientURL: fixtures.ServerURL, Client: WithHTTPClient, } diff --git a/transports/http.go b/transports/http.go index 04a52dd..b124a74 100644 --- a/transports/http.go +++ b/transports/http.go @@ -693,3 +693,29 @@ func (h *TransportHTTP) GetContacts(ctx context.Context, conditions map[string]i return result, nil } + +// UpsertContact add or update contact. When adding a new contact, the system utilizes Paymail's PIKE capability to dispatch an invitation request, asking the counterparty to include the current user in their contacts. +func (h *TransportHTTP) UpsertContact(ctx context.Context, paymail, fullName string, metadata *models.Metadata, requesterPaymail string) (*models.Contact, ResponseError) { + payload := map[string]interface{}{ + "fullName": fullName, + FieldMetadata: processMetadata(metadata), + } + + if requesterPaymail != "" { + payload["requesterPaymail"] = requesterPaymail + } + + jsonStr, err := json.Marshal(payload) + if err != nil { + return nil, WrapError(err) + } + + var result models.Contact + if err := h.doHTTPRequest( + ctx, http.MethodPut, "/contact/"+paymail, jsonStr, h.xPriv, h.signRequest, &result, + ); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/transports/interface.go b/transports/interface.go index 73c83c2..3ae040b 100644 --- a/transports/interface.go +++ b/transports/interface.go @@ -55,6 +55,7 @@ type ContactService interface { RejectContact(ctx context.Context, paymail string) ResponseError ConfirmContact(ctx context.Context, paymail string) ResponseError GetContacts(ctx context.Context, conditions map[string]interface{}, metadata *models.Metadata, queryParams *QueryParams) ([]*models.Contact, ResponseError) + UpsertContact(ctx context.Context, paymail, fullName string, metadata *models.Metadata, requesterPaymail string) (*models.Contact, ResponseError) } // AdminService is the admin related requests