diff --git a/protocol/messenger_share_urls.go b/protocol/messenger_share_urls.go index 238f06dbd..3e29455de 100644 --- a/protocol/messenger_share_urls.go +++ b/protocol/messenger_share_urls.go @@ -45,11 +45,21 @@ type ContactURLData struct { PublicKey string `json:"publicKey"` } +type TransactionURLData struct { + TxType int `json:"txType"` + Address string `json:"address"` + Amount string `json:"amount"` + Asset string `json:"asset"` + ChainID int `json:"chainId"` + ToAsset string `json:"toAsset"` +} + type URLDataResponse struct { - Community *CommunityURLData `json:"community"` - Channel *CommunityChannelURLData `json:"channel"` - Contact *ContactURLData `json:"contact"` - Shard *shard.Shard `json:"shard,omitempty"` + Community *CommunityURLData `json:"community"` + Channel *CommunityChannelURLData `json:"channel"` + Contact *ContactURLData `json:"contact"` + Transaction *TransactionURLData `json:"tx"` + Shard *shard.Shard `json:"shard,omitempty"` } const baseShareURL = "https://status.app" @@ -58,12 +68,14 @@ const userWithDataPath = "u/" const communityPath = "c#" const communityWithDataPath = "c/" const channelPath = "cc/" +const transactionPath = "tx/" const sharedURLUserPrefix = baseShareURL + "/" + userPath const sharedURLUserPrefixWithData = baseShareURL + "/" + userWithDataPath const sharedURLCommunityPrefix = baseShareURL + "/" + communityPath const sharedURLCommunityPrefixWithData = baseShareURL + "/" + communityWithDataPath const sharedURLChannelPrefixWithData = baseShareURL + "/" + channelPath +const sharedURLTransaction = baseShareURL + "/" + transactionPath const channelUUIDRegExp = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$" @@ -302,6 +314,77 @@ func (m *Messenger) prepareEncodedCommunityChannelData(community *communities.Co return encodedData, shortKey, nil } +func (m *Messenger) ShareTransactionURL(request *requests.TransactionShareURL) (string, error) { + encodedData, err := m.prepareTransactionUrl(request) + if err != nil { + return "", err + } + return fmt.Sprintf("%s%s", sharedURLTransaction, encodedData), nil +} + +func (m *Messenger) prepareTransactionUrl(request *requests.TransactionShareURL) (string, error) { + if err := request.Validate(); err != nil { + return "", err + } + + txProto := &protobuf.Transaction{ + TxType: uint32(request.TxType), + Address: request.Address, + Amount: request.Amount, + Asset: request.Asset, + ChainId: uint32(request.ChainID), + ToAsset: request.ToAsset, + } + txData, err := proto.Marshal(txProto) + if err != nil { + return "", err + } + urlDataProto := &protobuf.URLData{ + Content: txData, + } + + urlData, err := proto.Marshal(urlDataProto) + if err != nil { + return "", err + } + + encodedData, err := encodeDataURL(urlData) + if err != nil { + return "", err + } + return encodedData, nil +} + +func parseTransactionURL(data string) (*URLDataResponse, error) { + urlData, err := decodeDataURL(data) + if err != nil { + return nil, err + } + + var urlDataProto protobuf.URLData + err = proto.Unmarshal(urlData, &urlDataProto) + if err != nil { + return nil, err + } + + var txProto protobuf.Transaction + err = proto.Unmarshal(urlDataProto.Content, &txProto) + if err != nil { + return nil, err + } + + return &URLDataResponse{ + Transaction: &TransactionURLData{ + TxType: int(txProto.TxType), + Address: txProto.Address, + Amount: txProto.Amount, + Asset: txProto.Asset, + ChainID: int(txProto.ChainId), + ToAsset: txProto.ToAsset, + }, + }, nil +} + func (m *Messenger) ShareCommunityChannelURLWithData(request *requests.CommunityChannelShareURL) (string, error) { if err := request.Validate(); err != nil { return "", err @@ -518,7 +601,8 @@ func IsStatusSharedURL(url string) bool { strings.HasPrefix(url, sharedURLUserPrefixWithData) || strings.HasPrefix(url, sharedURLCommunityPrefix) || strings.HasPrefix(url, sharedURLCommunityPrefixWithData) || - strings.HasPrefix(url, sharedURLChannelPrefixWithData) + strings.HasPrefix(url, sharedURLChannelPrefixWithData) || + strings.HasPrefix(url, sharedURLTransaction) } func splitSharedURLData(data string) (string, string, error) { @@ -576,6 +660,11 @@ func ParseSharedURL(url string) (*URLDataResponse, error) { return parseCommunityChannelURLWithData(encodedData, chatKey) } + if strings.HasPrefix(url, sharedURLTransaction) { + trimmedURL := strings.TrimPrefix(url, sharedURLTransaction) + return parseTransactionURL(trimmedURL) + } + return nil, fmt.Errorf("not a status shared url") } diff --git a/protocol/messenger_share_urls_test.go b/protocol/messenger_share_urls_test.go index 9436c661c..168149b62 100644 --- a/protocol/messenger_share_urls_test.go +++ b/protocol/messenger_share_urls_test.go @@ -497,3 +497,57 @@ func (s *MessengerShareUrlsSuite) TestShareAndParseUserURLWithData() { s.Require().Equal(contact.DisplayName, urlData.Contact.DisplayName) s.Require().Equal(shortKey, urlData.Contact.PublicKey) } + +func (s *MessengerShareUrlsSuite) TestShareTransactionURL() { + request := &requests.TransactionShareURL{ + TxType: 0, + Address: "0x1234567890abcdef", + Amount: "0.123", + Asset: "ETH", + ChainID: 123, + ToAsset: "SNT", + } + url, err := s.m.ShareTransactionURL(request) + s.Require().NoError(err) + s.Require().NotEmpty(url) + + data, err := s.m.prepareTransactionUrl(request) + s.Require().NoError(err) + + expectedURL := fmt.Sprintf("%s/tx/%s", baseShareURL, data) + s.Require().Equal(expectedURL, url) +} + +func (s *MessengerShareUrlsSuite) TestShareTransactionURLInvalid() { + request := &requests.TransactionShareURL{ + TxType: -1, + } + _, err := s.m.ShareTransactionURL(request) + s.Require().Error(err) + _, err = s.m.prepareTransactionUrl(request) + s.Require().Error(err) +} + +func (s *MessengerShareUrlsSuite) TestShareAndParseTransactionURL() { + request := &requests.TransactionShareURL{ + TxType: 0, + Address: "0x1234567890abcdef", + Amount: "0.123", + Asset: "ETH", + ChainID: 123, + ToAsset: "SNT", + } + url, err := s.m.ShareTransactionURL(request) + s.Require().NoError(err) + + urlData, err := ParseSharedURL(url) + s.Require().NoError(err) + s.Require().NotNil(urlData) + + s.Require().Equal(request.TxType, urlData.Transaction.TxType) + s.Require().Equal(request.Address, urlData.Transaction.Address) + s.Require().Equal(request.Amount, urlData.Transaction.Amount) + s.Require().Equal(request.Asset, urlData.Transaction.Asset) + s.Require().Equal(request.ChainID, urlData.Transaction.ChainID) + s.Require().Equal(request.ToAsset, urlData.Transaction.ToAsset) +} diff --git a/protocol/protobuf/url_data.proto b/protocol/protobuf/url_data.proto index bc3a501ae..ea57dcad0 100644 --- a/protocol/protobuf/url_data.proto +++ b/protocol/protobuf/url_data.proto @@ -28,8 +28,17 @@ message User { string color = 3; } +message Transaction { + uint32 txType = 1; + string address = 2; + string amount = 3; + string asset = 4; + uint32 chainId = 5; + string toAsset = 6; +} + message URLData { - // Community, Channel, or User + // Community, Channel, User or Transaction bytes content = 1; Shard shard = 2; -} +} \ No newline at end of file diff --git a/protocol/requests/transaction_share_url.go b/protocol/requests/transaction_share_url.go new file mode 100644 index 000000000..412ad57fe --- /dev/null +++ b/protocol/requests/transaction_share_url.go @@ -0,0 +1,26 @@ +package requests + +import ( + "errors" +) + +var ( + ErrInvalidTransactionType = errors.New("transaction-share-url: invalid transaction type") +) + +type TransactionShareURL struct { + TxType int `json:"txType"` + Asset string `json:"asset"` + Amount string `json:"amount"` + Address string `json:"address"` + ChainID int `json:"chainId"` + ToAsset string `json:"toAsset"` +} + +func (r *TransactionShareURL) Validate() error { + if r.TxType < 0 { + return ErrInvalidTransactionType + } + + return nil +} diff --git a/services/ext/api.go b/services/ext/api.go index d7918fb4d..457263d43 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -1687,6 +1687,10 @@ func (api *PublicAPI) ShareUserURLWithData(pubKey string) (string, error) { return api.service.messenger.ShareUserURLWithData(pubKey) } +func (api *PublicAPI) ShareTransactionURL(txData *requests.TransactionShareURL) (string, error) { + return api.service.messenger.ShareTransactionURL(txData) +} + func (api *PublicAPI) ParseSharedURL(url string) (*protocol.URLDataResponse, error) { return protocol.ParseSharedURL(url) }