Skip to content

Commit

Permalink
feat:_ Unfurl transaction deep link
Browse files Browse the repository at this point in the history
  • Loading branch information
Cuteivist committed Oct 17, 2024
1 parent 4c88939 commit 98f79c0
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 14 deletions.
72 changes: 60 additions & 12 deletions protocol/common/message_linkpreview.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ type StatusCommunityLinkPreview struct {
Banner LinkPreviewThumbnail `json:"banner,omitempty"`
}

type StatusTransactionLinkPreview struct {
TxType int `json:"txType"`
Amount string `json:"amount"`
Asset string `json:"asset"`
ToAsset string `json:"toAsset"`
Address string `json:"address"`
ChainID int `json:"chainId"`
}

type StatusCommunityChannelLinkPreview struct {
ChannelUUID string `json:"channelUuid"`
Emoji string `json:"emoji"`
Expand All @@ -64,10 +73,11 @@ type StatusCommunityChannelLinkPreview struct {
}

type StatusLinkPreview struct {
URL string `json:"url,omitempty"`
Contact *StatusContactLinkPreview `json:"contact,omitempty"`
Community *StatusCommunityLinkPreview `json:"community,omitempty"`
Channel *StatusCommunityChannelLinkPreview `json:"channel,omitempty"`
URL string `json:"url,omitempty"`
Contact *StatusContactLinkPreview `json:"contact,omitempty"`
Community *StatusCommunityLinkPreview `json:"community,omitempty"`
Channel *StatusCommunityChannelLinkPreview `json:"channel,omitempty"`
Transaction *StatusTransactionLinkPreview `json:"transaction,omitempty"`
}

func (thumbnail *LinkPreviewThumbnail) IsEmpty() bool {
Expand Down Expand Up @@ -161,17 +171,23 @@ func (preview *StatusLinkPreview) validateForProto() error {
}

// At least and only one of Contact/Community/Channel should be present in the preview
if preview.Contact != nil && preview.Community != nil {
return fmt.Errorf("both contact and community are set at the same time")
var linkTypes []string
if preview.Contact != nil {
linkTypes = append(linkTypes, "Contact")
}
if preview.Community != nil && preview.Channel != nil {
return fmt.Errorf("both community and channel are set at the same time")
if preview.Community != nil {
linkTypes = append(linkTypes, "Community")
}
if preview.Channel != nil && preview.Contact != nil {
return fmt.Errorf("both contact and channel are set at the same time")
if preview.Channel != nil {
linkTypes = append(linkTypes, "Channel")
}
if preview.Contact == nil && preview.Community == nil && preview.Channel == nil {
return fmt.Errorf("none of contact/community/channel are set")
if preview.Transaction != nil {
linkTypes = append(linkTypes, "Transaction")
}
if len(linkTypes) > 1 {
return fmt.Errorf("multiple components set at the same time: %v", linkTypes)
} else if len(linkTypes) == 0 {
return fmt.Errorf("none of contact/community/channel/transaction are set")
}

if preview.Contact != nil {
Expand Down Expand Up @@ -200,6 +216,14 @@ func (preview *StatusLinkPreview) validateForProto() error {
}
return nil
}

if preview.Transaction != nil {
if preview.Transaction.Asset == "" && preview.Transaction.Amount == "" && preview.Transaction.Address == "" && preview.Transaction.ToAsset == "" {
return fmt.Errorf("transaction fields are empty")
}
return nil
}

return nil
}

Expand Down Expand Up @@ -444,6 +468,19 @@ func (m *Message) ConvertStatusLinkPreviewsToProto() (*protobuf.UnfurledStatusLi

}

if preview.Transaction != nil {
ul.Payload = &protobuf.UnfurledStatusLink_Transaction{
Transaction: &protobuf.UnfurledStatusTransactionLink{
TxType: uint32(preview.Transaction.TxType),
Amount: preview.Transaction.Amount,
Asset: preview.Transaction.Asset,
ToAsset: preview.Transaction.ToAsset,
Address: preview.Transaction.Address,
ChainId: uint32(preview.Transaction.ChainID),
},
}
}

unfurledLinks = append(unfurledLinks, ul)
}

Expand Down Expand Up @@ -508,6 +545,17 @@ func (m *Message) ConvertFromProtoToStatusLinkPreviews(makeMediaServerURL func(m
}
}

if c := link.GetTransaction(); c != nil {
lp.Transaction = &StatusTransactionLinkPreview{
TxType: int(c.TxType),
Amount: c.Amount,
Asset: c.Asset,
ToAsset: c.ToAsset,
Address: c.Address,
ChainID: int(c.ChainId),
}
}

previews = append(previews, lp)
}

Expand Down
69 changes: 67 additions & 2 deletions protocol/common/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,15 @@ func TestConvertStatusLinkPreviewsToProto(t *testing.T) {
},
}

transaction := &StatusTransactionLinkPreview{
TxType: 2,
Amount: "Amount_22",
Asset: "Asset_23",
ToAsset: "ToAsset_24",
Address: "Address_25",
ChainID: 26,
}

message := Message{
StatusLinkPreviews: []StatusLinkPreview{
{
Expand All @@ -352,6 +361,10 @@ func TestConvertStatusLinkPreviewsToProto(t *testing.T) {
URL: "https://status.app/cc/",
Channel: channel,
},
{
URL: "https://status.app/tx/",
Transaction: transaction,
},
},
}

Expand All @@ -360,7 +373,7 @@ func TestConvertStatusLinkPreviewsToProto(t *testing.T) {

unfurledLinks, err := message.ConvertStatusLinkPreviewsToProto()
require.NoError(t, err)
require.Len(t, unfurledLinks.UnfurledStatusLinks, 3)
require.Len(t, unfurledLinks.UnfurledStatusLinks, 4)

// Contact link

Expand All @@ -369,6 +382,7 @@ func TestConvertStatusLinkPreviewsToProto(t *testing.T) {
require.NotNil(t, l1.GetContact())
require.Nil(t, l1.GetCommunity())
require.Nil(t, l1.GetChannel())
require.Nil(t, l1.GetTransaction())
c1 := l1.GetContact()
require.Equal(t, compressedContactPublicKey, c1.PublicKey)
require.Equal(t, contact.DisplayName, c1.DisplayName)
Expand All @@ -385,6 +399,7 @@ func TestConvertStatusLinkPreviewsToProto(t *testing.T) {
require.NotNil(t, l2.GetCommunity())
require.Nil(t, l2.GetContact())
require.Nil(t, l2.GetChannel())
require.Nil(t, l2.GetTransaction())
c2 := l2.GetCommunity()
require.Equal(t, compressedCommunityPublicKey, c2.CommunityId)
require.Equal(t, community.DisplayName, c2.DisplayName)
Expand All @@ -407,6 +422,7 @@ func TestConvertStatusLinkPreviewsToProto(t *testing.T) {
require.NotNil(t, l3.GetChannel())
require.Nil(t, l3.GetContact())
require.Nil(t, l3.GetCommunity())
require.Nil(t, l3.GetTransaction())

c3 := l3.GetChannel()
require.Equal(t, channel.ChannelUUID, c3.ChannelUuid)
Expand All @@ -430,6 +446,23 @@ func TestConvertStatusLinkPreviewsToProto(t *testing.T) {
require.Equal(t, uint32(channel.Community.Banner.Height), c3.Community.Banner.Height)
require.Equal(t, expectedThumbnailPayload, c3.Community.Banner.Payload)

// Transaction link

l4 := unfurledLinks.UnfurledStatusLinks[3]
require.Equal(t, "https://status.app/tx/", l4.Url)
require.NotNil(t, l4.GetTransaction())
require.Nil(t, l4.GetContact())
require.Nil(t, l4.GetCommunity())
require.Nil(t, l4.GetChannel())

t4 := l4.GetTransaction()
require.Equal(t, uint32(transaction.TxType), t4.TxType)
require.Equal(t, transaction.Amount, t4.Amount)
require.Equal(t, transaction.Asset, t4.Asset)
require.Equal(t, transaction.ToAsset, t4.ToAsset)
require.Equal(t, transaction.Address, t4.Address)
require.Equal(t, uint32(transaction.ChainID), t4.ChainId)

// Test any invalid link preview causes an early return.
invalidContactPreview := contact
invalidContactPreview.PublicKey = ""
Expand Down Expand Up @@ -516,6 +549,15 @@ func TestConvertFromProtoToStatusLinkPreviews(t *testing.T) {
},
}

transaction := &protobuf.UnfurledStatusTransactionLink{
TxType: 1,
Amount: "100",
Asset: "ETH",
ToAsset: "DAI",
Address: "0x1234567890",
ChainId: 11,
}

msg := Message{
ID: "42",
ChatMessage: &protobuf.ChatMessage{
Expand All @@ -539,6 +581,12 @@ func TestConvertFromProtoToStatusLinkPreviews(t *testing.T) {
Channel: channel,
},
},
{
Url: "https://status.app/tx/",
Payload: &protobuf.UnfurledStatusLink_Transaction{
Transaction: transaction,
},
},
},
},
},
Expand All @@ -549,7 +597,7 @@ func TestConvertFromProtoToStatusLinkPreviews(t *testing.T) {
}

previews := msg.ConvertFromProtoToStatusLinkPreviews(urlMaker)
require.Len(t, previews, 3)
require.Len(t, previews, 4)

// Contact preview

Expand All @@ -558,6 +606,7 @@ func TestConvertFromProtoToStatusLinkPreviews(t *testing.T) {
require.NotNil(t, p1.Contact)
require.Nil(t, p1.Community)
require.Nil(t, p1.Channel)
require.Nil(t, p1.Transaction)

c1 := p1.Contact
require.NotNil(t, c1)
Expand All @@ -577,6 +626,7 @@ func TestConvertFromProtoToStatusLinkPreviews(t *testing.T) {
require.NotNil(t, p2.Community)
require.Nil(t, p2.Contact)
require.Nil(t, p2.Channel)
require.Nil(t, p2.Transaction)

c2 := p2.Community
require.Equal(t, communityID, c2.CommunityID)
Expand All @@ -602,6 +652,7 @@ func TestConvertFromProtoToStatusLinkPreviews(t *testing.T) {
require.NotNil(t, p3.Channel)
require.Nil(t, p3.Contact)
require.Nil(t, p3.Community)
require.Nil(t, p3.Transaction)

c3 := previews[2].Channel
require.Equal(t, channel.ChannelUuid, c3.ChannelUUID)
Expand All @@ -627,6 +678,20 @@ func TestConvertFromProtoToStatusLinkPreviews(t *testing.T) {
require.Equal(t, "", c3.Community.Banner.DataURI)
require.Equal(t, "https://localhost:6666/42-https://status.app/cc/-community-channel-banner", c3.Community.Banner.URL)

p4 := previews[3]
require.Equal(t, "https://status.app/tx/", p4.URL)
require.NotNil(t, p4.Transaction)
require.Nil(t, p4.Contact)
require.Nil(t, p4.Community)
require.Nil(t, p4.Channel)

t1 := p4.Transaction
require.Equal(t, transaction.TxType, uint32(t1.TxType))
require.Equal(t, transaction.Amount, t1.Amount)
require.Equal(t, transaction.Asset, t1.Asset)
require.Equal(t, transaction.ToAsset, t1.ToAsset)
require.Equal(t, transaction.Address, t1.Address)
require.Equal(t, transaction.ChainId, uint32(t1.ChainID))
}

func assertMarshalAndUnmarshalJSON[T any](t *testing.T, obj *T, msgAndArgs ...any) {
Expand Down
19 changes: 19 additions & 0 deletions protocol/linkpreview_unfurler_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ func (u *StatusUnfurler) buildContactData(publicKey string) (*common.StatusConta
return c, nil
}

func (u *StatusUnfurler) buildTransactionData(urlData *TransactionURLData) (*common.StatusTransactionLinkPreview, error) {
return &common.StatusTransactionLinkPreview{
TxType: urlData.TxType,
Asset: urlData.Asset,
Amount: urlData.Amount,
Address: urlData.Address,
ChainID: urlData.ChainID,
ToAsset: urlData.ToAsset,
}, nil
}

func (u *StatusUnfurler) buildCommunityData(communityID string, shard *shard.Shard) (*communities.Community, *common.StatusCommunityLinkPreview, error) {
// This automatically checks the database
community, err := u.m.FetchCommunity(&FetchCommunityRequest{
Expand Down Expand Up @@ -172,5 +183,13 @@ func (u *StatusUnfurler) Unfurl() (*common.StatusLinkPreview, error) {
return preview, nil
}

if resp.Transaction != nil {
preview.Transaction, err = u.buildTransactionData(resp.Transaction)
if err != nil {
return nil, fmt.Errorf("error when building transaction data: %w", err)
}
return preview, nil
}

return nil, fmt.Errorf("shared url does not contain contact, community or channel data")
}
10 changes: 10 additions & 0 deletions protocol/protobuf/chat_message.proto
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,22 @@ message UnfurledStatusChannelLink {
UnfurledStatusCommunityLink community = 6;
}

message UnfurledStatusTransactionLink {
uint32 txType = 1;
string address = 2;
string amount = 3;
string asset = 4;
uint32 chainId = 5;
string toAsset = 6;
}

message UnfurledStatusLink {
string url = 1;
oneof payload {
UnfurledStatusContactLink contact = 2;
UnfurledStatusCommunityLink community = 3;
UnfurledStatusChannelLink channel = 4;
UnfurledStatusTransactionLink transaction = 5;
}
}

Expand Down
Loading

0 comments on commit 98f79c0

Please sign in to comment.