diff --git a/contacts.go b/contacts.go new file mode 100644 index 0000000..0c11107 --- /dev/null +++ b/contacts.go @@ -0,0 +1,38 @@ +package walletclient + +import ( + "context" + + "github.com/bitcoin-sv/spv-wallet-go-client/transports" + "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) +} + +// 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 new file mode 100644 index 0000000..ab468bb --- /dev/null +++ b/contacts_test.go @@ -0,0 +1,87 @@ +package walletclient + +import ( + "context" + "testing" + + "github.com/bitcoin-sv/spv-wallet-go-client/fixtures" + "github.com/stretchr/testify/assert" +) + +// TestRejectContact will test the RejectContact method +func TestContactActionsRouting(t *testing.T) { + tcs := []struct { + name string + route string + responsePayload string + f func(c *WalletClient) error + }{ + { + name: "RejectContact", + route: "/contact/rejected/", + responsePayload: "{}", + f: func(c *WalletClient) error { return c.RejectContact(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/", + responsePayload: "{}", + f: func(c *WalletClient) error { return c.ConfirmContact(context.Background(), fixtures.PaymailAddress) }, + }, + { + 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 + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + // given + tmq := testTransportHandler{ + Type: fixtures.RequestType, + Path: tc.route, + Result: tc.responsePayload, + ClientURL: fixtures.ServerURL, + Client: WithHTTPClient, + } + + client := getTestWalletClient(tmq, true) + + // when + err := tc.f(client) + + // then + assert.NoError(t, err) + }) + } + +} diff --git a/transports/http.go b/transports/http.go index c827953..b124a74 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) { @@ -639,3 +639,83 @@ 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 the contact associated with the paymail +func (h *TransportHTTP) AcceptContact(ctx context.Context, paymail string) ResponseError { + if err := h.doHTTPRequest( + ctx, http.MethodPatch, "/contact/accepted/"+paymail, nil, h.xPriv, h.signRequest, nil, + ); err != nil { + return err + } + + return nil +} + +// 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/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 +} + +// 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 dcec8ee..3ae040b 100644 --- a/transports/interface.go +++ b/transports/interface.go @@ -49,6 +49,15 @@ 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 + 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 type AdminService interface { AdminGetStatus(ctx context.Context) (bool, ResponseError) @@ -79,6 +88,7 @@ type AdminService interface { type TransportService interface { AccessKeyService AdminService + ContactService DestinationService TransactionService XpubService