Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat_: share and parse transaction deep link #5959

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 94 additions & 5 deletions protocol/messenger_share_urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,21 @@
PublicKey string `json:"publicKey"`
}

type TransactionURLData struct {
TxType int `json:"txType"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think tx is redundant here, it's all about the transaction?

Suggested change
TxType int `json:"txType"`
type int `json:"type"`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I 100% agree with you. The reason why I set it to txType to prevent any issues of type keyword that might happen on any layer.
While it might be redundant, I wanted to clearly point it is about transaction not url type or something like that.

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"
Expand All @@ -58,12 +68,14 @@
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}$"

Expand Down Expand Up @@ -302,6 +314,77 @@
return encodedData, shortKey, nil
}

func (m *Messenger) ShareTransactionURL(request *requests.TransactionShareURL) (string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to build the transaction URL, I need to know all details about it?
But most probably all of the details about the transaction are already somewhere in our database, can we just pass the transaction hash here and status-go will collect all required data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature creates link from the modal, not existing transaction. It is to request the payment by setting proper fields.

Please note that not all the fields are required.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is to request the payment

wait, so there's no transaction yet? Should we call this transactionRequest then?
I can simply imagine a URL to the actual transaction 🤔

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

Check warning on line 340 in protocol/messenger_share_urls.go

View check run for this annotation

Codecov / codecov/patch

protocol/messenger_share_urls.go#L340

Added line #L340 was not covered by tests
}
urlDataProto := &protobuf.URLData{
Content: txData,
}

urlData, err := proto.Marshal(urlDataProto)
if err != nil {
return "", err

Check warning on line 348 in protocol/messenger_share_urls.go

View check run for this annotation

Codecov / codecov/patch

protocol/messenger_share_urls.go#L348

Added line #L348 was not covered by tests
}

encodedData, err := encodeDataURL(urlData)
if err != nil {
return "", err

Check warning on line 353 in protocol/messenger_share_urls.go

View check run for this annotation

Codecov / codecov/patch

protocol/messenger_share_urls.go#L353

Added line #L353 was not covered by tests
}
return encodedData, nil
}

func parseTransactionURL(data string) (*URLDataResponse, error) {
urlData, err := decodeDataURL(data)
if err != nil {
return nil, err

Check warning on line 361 in protocol/messenger_share_urls.go

View check run for this annotation

Codecov / codecov/patch

protocol/messenger_share_urls.go#L361

Added line #L361 was not covered by tests
}

var urlDataProto protobuf.URLData
err = proto.Unmarshal(urlData, &urlDataProto)
if err != nil {
return nil, err

Check warning on line 367 in protocol/messenger_share_urls.go

View check run for this annotation

Codecov / codecov/patch

protocol/messenger_share_urls.go#L367

Added line #L367 was not covered by tests
}

var txProto protobuf.Transaction
err = proto.Unmarshal(urlDataProto.Content, &txProto)
if err != nil {
return nil, err

Check warning on line 373 in protocol/messenger_share_urls.go

View check run for this annotation

Codecov / codecov/patch

protocol/messenger_share_urls.go#L373

Added line #L373 was not covered by tests
}

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
Expand Down Expand Up @@ -518,7 +601,8 @@
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) {
Expand Down Expand Up @@ -576,6 +660,11 @@
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")
}

Expand Down
54 changes: 54 additions & 0 deletions protocol/messenger_share_urls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
13 changes: 11 additions & 2 deletions protocol/protobuf/url_data.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
26 changes: 26 additions & 0 deletions protocol/requests/transaction_share_url.go
Original file line number Diff line number Diff line change
@@ -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
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also validate that the address is not empty for example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. It might be changed by design team, but right now, deep link can be created without recipient. For example Swap doesn't have recipient.

I don't see the reason why I couldn't share tx setup for specific token, it is much easier than copy pasting name.

return nil
}
4 changes: 4 additions & 0 deletions services/ext/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,10 @@
return api.service.messenger.ShareUserURLWithData(pubKey)
}

func (api *PublicAPI) ShareTransactionURL(txData *requests.TransactionShareURL) (string, error) {
return api.service.messenger.ShareTransactionURL(txData)

Check warning on line 1691 in services/ext/api.go

View check run for this annotation

Codecov / codecov/patch

services/ext/api.go#L1690-L1691

Added lines #L1690 - L1691 were not covered by tests
}

func (api *PublicAPI) ParseSharedURL(url string) (*protocol.URLDataResponse, error) {
return protocol.ParseSharedURL(url)
}
Expand Down