feat_: share and parse transaction deep link

This commit is contained in:
Emil Sawicki 2024-10-07 06:08:48 +02:00
parent 86cd41d04e
commit 4c889399eb
5 changed files with 189 additions and 7 deletions

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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)
}