feat: Community token received notification (#4682)
This commit is contained in:
parent
54d0cf28c7
commit
a866b8025e
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/protocol/common"
|
"github.com/status-im/status-go/protocol/common"
|
||||||
"github.com/status-im/status-go/protocol/verification"
|
"github.com/status-im/status-go/protocol/verification"
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The activity center is a place where we store incoming notifications before
|
// The activity center is a place where we store incoming notifications before
|
||||||
|
@ -36,6 +37,7 @@ const (
|
||||||
ActivityCenterNotificationTypeSetSignerDeclined
|
ActivityCenterNotificationTypeSetSignerDeclined
|
||||||
ActivityCenterNotificationTypeShareAccounts
|
ActivityCenterNotificationTypeShareAccounts
|
||||||
ActivityCenterNotificationTypeCommunityTokenReceived
|
ActivityCenterNotificationTypeCommunityTokenReceived
|
||||||
|
ActivityCenterNotificationTypeFirstCommunityTokenReceived
|
||||||
)
|
)
|
||||||
|
|
||||||
type ActivityCenterMembershipStatus int
|
type ActivityCenterMembershipStatus int
|
||||||
|
@ -60,6 +62,22 @@ const (
|
||||||
|
|
||||||
var ErrInvalidActivityCenterNotification = errors.New("invalid activity center notification")
|
var ErrInvalidActivityCenterNotification = errors.New("invalid activity center notification")
|
||||||
|
|
||||||
|
type ActivityTokenData struct {
|
||||||
|
ChainID uint64 `json:"chainId,omitempty"`
|
||||||
|
CollectibleID thirdparty.CollectibleUniqueID `json:"collectibleId,omitempty"`
|
||||||
|
TxHash string `json:"txHash,omitempty"`
|
||||||
|
WalletAddress string `json:"walletAddress,omitempty"`
|
||||||
|
IsFirst bool `json:"isFirst,omitempty"`
|
||||||
|
// Community data
|
||||||
|
CommunityID string `json:"communityId,omitempty"`
|
||||||
|
// Token data
|
||||||
|
Amount string `json:"amount,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Symbol string `json:"symbol,omitempty"`
|
||||||
|
ImageURL string `json:"imageUrl,omitempty"`
|
||||||
|
TokenType int `json:"tokenType,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ActivityCenterNotification struct {
|
type ActivityCenterNotification struct {
|
||||||
ID types.HexBytes `json:"id"`
|
ID types.HexBytes `json:"id"`
|
||||||
ChatID string `json:"chatId"`
|
ChatID string `json:"chatId"`
|
||||||
|
@ -77,6 +95,7 @@ type ActivityCenterNotification struct {
|
||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
Accepted bool `json:"accepted"`
|
Accepted bool `json:"accepted"`
|
||||||
ContactVerificationStatus verification.RequestStatus `json:"contactVerificationStatus"`
|
ContactVerificationStatus verification.RequestStatus `json:"contactVerificationStatus"`
|
||||||
|
TokenData *ActivityTokenData `json:"tokenData"`
|
||||||
//Used for synchronization. Each update should increment the UpdatedAt.
|
//Used for synchronization. Each update should increment the UpdatedAt.
|
||||||
//The value should represent the time when the update occurred.
|
//The value should represent the time when the update occurred.
|
||||||
UpdatedAt uint64 `json:"updatedAt"`
|
UpdatedAt uint64 `json:"updatedAt"`
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const allFieldsForTableActivityCenterNotification = `id, timestamp, notification_type, chat_id, read, dismissed, accepted, message, author,
|
const allFieldsForTableActivityCenterNotification = `id, timestamp, notification_type, chat_id, read, dismissed, accepted, message, author,
|
||||||
reply_message, community_id, membership_status, contact_verification_status, deleted, updated_at`
|
reply_message, community_id, membership_status, contact_verification_status, token_data, deleted, updated_at`
|
||||||
|
|
||||||
var emptyNotifications = make([]*ActivityCenterNotification, 0)
|
var emptyNotifications = make([]*ActivityCenterNotification, 0)
|
||||||
|
|
||||||
|
@ -125,6 +125,15 @@ func (db sqlitePersistence) SaveActivityCenterNotification(notification *Activit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encode token data
|
||||||
|
var encodedTokenData []byte
|
||||||
|
if notification.TokenData != nil {
|
||||||
|
encodedTokenData, err = json.Marshal(notification.TokenData)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result, err := tx.Exec(`
|
result, err := tx.Exec(`
|
||||||
INSERT OR REPLACE
|
INSERT OR REPLACE
|
||||||
INTO activity_center_notifications (
|
INTO activity_center_notifications (
|
||||||
|
@ -141,10 +150,11 @@ func (db sqlitePersistence) SaveActivityCenterNotification(notification *Activit
|
||||||
read,
|
read,
|
||||||
accepted,
|
accepted,
|
||||||
dismissed,
|
dismissed,
|
||||||
|
token_data,
|
||||||
deleted,
|
deleted,
|
||||||
updated_at
|
updated_at
|
||||||
)
|
)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
`,
|
`,
|
||||||
notification.ID,
|
notification.ID,
|
||||||
notification.Timestamp,
|
notification.Timestamp,
|
||||||
|
@ -159,9 +169,9 @@ func (db sqlitePersistence) SaveActivityCenterNotification(notification *Activit
|
||||||
notification.Read,
|
notification.Read,
|
||||||
notification.Accepted,
|
notification.Accepted,
|
||||||
notification.Dismissed,
|
notification.Dismissed,
|
||||||
|
encodedTokenData,
|
||||||
notification.Deleted,
|
notification.Deleted,
|
||||||
notification.UpdatedAt,
|
notification.UpdatedAt,
|
||||||
notification.ID,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -187,6 +197,7 @@ func (db sqlitePersistence) parseRowFromTableActivityCenterNotification(rows *sq
|
||||||
var communityID sql.NullString
|
var communityID sql.NullString
|
||||||
var messageBytes []byte
|
var messageBytes []byte
|
||||||
var replyMessageBytes []byte
|
var replyMessageBytes []byte
|
||||||
|
var tokenDataBytes []byte
|
||||||
var author sql.NullString
|
var author sql.NullString
|
||||||
notification := &ActivityCenterNotification{}
|
notification := &ActivityCenterNotification{}
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
|
@ -203,6 +214,7 @@ func (db sqlitePersistence) parseRowFromTableActivityCenterNotification(rows *sq
|
||||||
&communityID,
|
&communityID,
|
||||||
¬ification.MembershipStatus,
|
¬ification.MembershipStatus,
|
||||||
¬ification.ContactVerificationStatus,
|
¬ification.ContactVerificationStatus,
|
||||||
|
&tokenDataBytes,
|
||||||
¬ification.Deleted,
|
¬ification.Deleted,
|
||||||
¬ification.UpdatedAt,
|
¬ification.UpdatedAt,
|
||||||
)
|
)
|
||||||
|
@ -222,6 +234,13 @@ func (db sqlitePersistence) parseRowFromTableActivityCenterNotification(rows *sq
|
||||||
notification.Author = author.String
|
notification.Author = author.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(tokenDataBytes) > 0 {
|
||||||
|
err = json.Unmarshal(tokenDataBytes, ¬ification.TokenData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(messageBytes) > 0 {
|
if len(messageBytes) > 0 {
|
||||||
err = json.Unmarshal(messageBytes, ¬ification.Message)
|
err = json.Unmarshal(messageBytes, ¬ification.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -251,6 +270,7 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRow(row *sql.Row)
|
||||||
var lastMessageBytes []byte
|
var lastMessageBytes []byte
|
||||||
var messageBytes []byte
|
var messageBytes []byte
|
||||||
var replyMessageBytes []byte
|
var replyMessageBytes []byte
|
||||||
|
var tokenDataBytes []byte
|
||||||
var name sql.NullString
|
var name sql.NullString
|
||||||
var author sql.NullString
|
var author sql.NullString
|
||||||
notification := &ActivityCenterNotification{}
|
notification := &ActivityCenterNotification{}
|
||||||
|
@ -271,6 +291,7 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRow(row *sql.Row)
|
||||||
¬ification.ContactVerificationStatus,
|
¬ification.ContactVerificationStatus,
|
||||||
&name,
|
&name,
|
||||||
&author,
|
&author,
|
||||||
|
&tokenDataBytes,
|
||||||
¬ification.UpdatedAt)
|
¬ification.UpdatedAt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -293,6 +314,13 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRow(row *sql.Row)
|
||||||
notification.Author = author.String
|
notification.Author = author.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(tokenDataBytes) > 0 {
|
||||||
|
err = json.Unmarshal(tokenDataBytes, ¬ification.TokenData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Restore last message
|
// Restore last message
|
||||||
if lastMessageBytes != nil {
|
if lastMessageBytes != nil {
|
||||||
lastMessage := common.NewMessage()
|
lastMessage := common.NewMessage()
|
||||||
|
@ -332,6 +360,7 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRows(rows *sql.Ro
|
||||||
var lastMessageBytes []byte
|
var lastMessageBytes []byte
|
||||||
var messageBytes []byte
|
var messageBytes []byte
|
||||||
var replyMessageBytes []byte
|
var replyMessageBytes []byte
|
||||||
|
var tokenDataBytes []byte
|
||||||
var name sql.NullString
|
var name sql.NullString
|
||||||
var author sql.NullString
|
var author sql.NullString
|
||||||
notification := &ActivityCenterNotification{}
|
notification := &ActivityCenterNotification{}
|
||||||
|
@ -351,6 +380,7 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRows(rows *sql.Ro
|
||||||
¬ification.ContactVerificationStatus,
|
¬ification.ContactVerificationStatus,
|
||||||
&name,
|
&name,
|
||||||
&author,
|
&author,
|
||||||
|
&tokenDataBytes,
|
||||||
&latestCursor,
|
&latestCursor,
|
||||||
¬ification.UpdatedAt)
|
¬ification.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -373,6 +403,14 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRows(rows *sql.Ro
|
||||||
notification.Author = author.String
|
notification.Author = author.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(tokenDataBytes) > 0 {
|
||||||
|
tokenData := &ActivityTokenData{}
|
||||||
|
if err = json.Unmarshal(tokenDataBytes, &tokenData); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
notification.TokenData = tokenData
|
||||||
|
}
|
||||||
|
|
||||||
// Restore last message
|
// Restore last message
|
||||||
if lastMessageBytes != nil {
|
if lastMessageBytes != nil {
|
||||||
lastMessage := common.NewMessage()
|
lastMessage := common.NewMessage()
|
||||||
|
@ -503,6 +541,7 @@ func (db sqlitePersistence) buildActivityCenterQuery(tx *sql.Tx, params activity
|
||||||
a.contact_verification_status,
|
a.contact_verification_status,
|
||||||
c.name,
|
c.name,
|
||||||
a.author,
|
a.author,
|
||||||
|
a.token_data,
|
||||||
substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor,
|
substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor,
|
||||||
a.updated_at
|
a.updated_at
|
||||||
FROM activity_center_notifications a
|
FROM activity_center_notifications a
|
||||||
|
@ -623,6 +662,7 @@ func (db sqlitePersistence) GetActivityCenterNotificationsByID(ids []types.HexBy
|
||||||
a.contact_verification_status,
|
a.contact_verification_status,
|
||||||
c.name,
|
c.name,
|
||||||
a.author,
|
a.author,
|
||||||
|
a.token_data,
|
||||||
substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor,
|
substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor,
|
||||||
a.updated_at
|
a.updated_at
|
||||||
FROM activity_center_notifications a
|
FROM activity_center_notifications a
|
||||||
|
@ -663,6 +703,7 @@ func (db sqlitePersistence) GetActivityCenterNotificationByID(id types.HexBytes)
|
||||||
a.contact_verification_status,
|
a.contact_verification_status,
|
||||||
c.name,
|
c.name,
|
||||||
a.author,
|
a.author,
|
||||||
|
a.token_data,
|
||||||
a.updated_at
|
a.updated_at
|
||||||
FROM activity_center_notifications a
|
FROM activity_center_notifications a
|
||||||
LEFT JOIN chats c
|
LEFT JOIN chats c
|
||||||
|
@ -1295,6 +1336,7 @@ func (db sqlitePersistence) ActiveContactRequestNotification(contactID string) (
|
||||||
a.contact_verification_status,
|
a.contact_verification_status,
|
||||||
c.name,
|
c.name,
|
||||||
a.author,
|
a.author,
|
||||||
|
a.token_data,
|
||||||
a.updated_at
|
a.updated_at
|
||||||
FROM activity_center_notifications a
|
FROM activity_center_notifications a
|
||||||
LEFT JOIN chats c ON c.id = a.chat_id
|
LEFT JOIN chats c ON c.id = a.chat_id
|
||||||
|
|
|
@ -4189,6 +4189,15 @@ func extractQuotedImages(messages []*common.Message, s *server.MediaServer) []st
|
||||||
return quotedImages
|
return quotedImages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) prepareTokenData(tokenData *ActivityTokenData, s *server.MediaServer) error {
|
||||||
|
if tokenData.TokenType == int(protobuf.CommunityTokenType_ERC721) {
|
||||||
|
tokenData.ImageURL = s.MakeWalletCollectibleImagesURL(tokenData.CollectibleID)
|
||||||
|
} else if tokenData.TokenType == int(protobuf.CommunityTokenType_ERC20) {
|
||||||
|
tokenData.ImageURL = s.MakeCommunityTokenImagesURL(tokenData.CommunityID, tokenData.ChainID, tokenData.Symbol)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Messenger) prepareMessage(msg *common.Message, s *server.MediaServer) error {
|
func (m *Messenger) prepareMessage(msg *common.Message, s *server.MediaServer) error {
|
||||||
if msg.QuotedMessage != nil && msg.QuotedMessage.ContentType == int64(protobuf.ChatMessage_IMAGE) {
|
if msg.QuotedMessage != nil && msg.QuotedMessage.ContentType == int64(protobuf.ChatMessage_IMAGE) {
|
||||||
msg.QuotedMessage.ImageLocalURL = s.MakeImageURL(msg.QuotedMessage.ID)
|
msg.QuotedMessage.ImageLocalURL = s.MakeImageURL(msg.QuotedMessage.ID)
|
||||||
|
|
|
@ -70,6 +70,14 @@ func (m *Messenger) ActivityCenterNotifications(request ActivityCenterNotificati
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if notification.TokenData != nil {
|
||||||
|
if notification.Type == ActivityCenterNotificationTypeCommunityTokenReceived || notification.Type == ActivityCenterNotificationTypeFirstCommunityTokenReceived {
|
||||||
|
err = m.prepareTokenData(notification.TokenData, m.httpServer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4241,7 +4241,13 @@ func (m *Messenger) PromoteSelfToControlNode(communityID types.HexBytes) (*Messe
|
||||||
return &response, nil
|
return &response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messenger) CreateResponseWithACNotification(communityID string, acType ActivityCenterType, isRead bool) (*MessengerResponse, error) {
|
func (m *Messenger) CreateResponseWithACNotification(communityID string, acType ActivityCenterType, isRead bool, tokenDataJSON string) (*MessengerResponse, error) {
|
||||||
|
tokenData := ActivityTokenData{}
|
||||||
|
err := json.Unmarshal([]byte(tokenDataJSON), &tokenData)
|
||||||
|
if len(tokenDataJSON) > 0 && err != nil {
|
||||||
|
// Only return error when activityDataString is not empty
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// Activity center notification
|
// Activity center notification
|
||||||
notification := &ActivityCenterNotification{
|
notification := &ActivityCenterNotification{
|
||||||
ID: types.FromHex(uuid.New().String()),
|
ID: types.FromHex(uuid.New().String()),
|
||||||
|
@ -4251,11 +4257,17 @@ func (m *Messenger) CreateResponseWithACNotification(communityID string, acType
|
||||||
Read: isRead,
|
Read: isRead,
|
||||||
Deleted: false,
|
Deleted: false,
|
||||||
UpdatedAt: m.GetCurrentTimeInMillis(),
|
UpdatedAt: m.GetCurrentTimeInMillis(),
|
||||||
|
TokenData: &tokenData,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.prepareTokenData(notification.TokenData, m.httpServer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &MessengerResponse{}
|
response := &MessengerResponse{}
|
||||||
|
|
||||||
err := m.addActivityCenterNotification(response, notification, nil)
|
err = m.addActivityCenterNotification(response, notification, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Error("failed to save notification", zap.Error(err))
|
m.logger.Error("failed to save notification", zap.Error(err))
|
||||||
return response, err
|
return response, err
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE activity_center_notifications ADD COLUMN token_data BLOB DEFAULT NULL;
|
|
@ -1685,32 +1685,36 @@ func (api *PublicAPI) GetProfileShowcaseAccountsByAddress(address string) ([]*id
|
||||||
|
|
||||||
// Returns response with AC notification when owner token is received
|
// Returns response with AC notification when owner token is received
|
||||||
func (api *PublicAPI) RegisterOwnerTokenReceivedNotification(communityID string) (*protocol.MessengerResponse, error) {
|
func (api *PublicAPI) RegisterOwnerTokenReceivedNotification(communityID string) (*protocol.MessengerResponse, error) {
|
||||||
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeOwnerTokenReceived, false)
|
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeOwnerTokenReceived, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns response with AC notification when setting signer is successful
|
// Returns response with AC notification when setting signer is successful
|
||||||
func (api *PublicAPI) RegisterReceivedOwnershipNotification(communityID string) (*protocol.MessengerResponse, error) {
|
func (api *PublicAPI) RegisterReceivedOwnershipNotification(communityID string) (*protocol.MessengerResponse, error) {
|
||||||
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeOwnershipReceived, false)
|
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeOwnershipReceived, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns response with AC notification when community token is received
|
// Returns response with AC notification when community token is received
|
||||||
func (api *PublicAPI) RegisterReceivedCommunityTokenNotification(communityID string) (*protocol.MessengerResponse, error) {
|
func (api *PublicAPI) RegisterReceivedCommunityTokenNotification(communityID string, isFirst bool, tokenData string) (*protocol.MessengerResponse, error) {
|
||||||
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeCommunityTokenReceived, false)
|
activityType := protocol.ActivityCenterNotificationTypeCommunityTokenReceived
|
||||||
|
if isFirst {
|
||||||
|
activityType = protocol.ActivityCenterNotificationTypeFirstCommunityTokenReceived
|
||||||
|
}
|
||||||
|
return api.service.messenger.CreateResponseWithACNotification(communityID, activityType, false, tokenData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns response with AC notification when setting signer is failed
|
// Returns response with AC notification when setting signer is failed
|
||||||
func (api *PublicAPI) RegisterSetSignerFailedNotification(communityID string) (*protocol.MessengerResponse, error) {
|
func (api *PublicAPI) RegisterSetSignerFailedNotification(communityID string) (*protocol.MessengerResponse, error) {
|
||||||
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeSetSignerFailed, false)
|
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeSetSignerFailed, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns response with AC notification when setting signer is declined
|
// Returns response with AC notification when setting signer is declined
|
||||||
func (api *PublicAPI) RegisterSetSignerDeclinedNotification(communityID string) (*protocol.MessengerResponse, error) {
|
func (api *PublicAPI) RegisterSetSignerDeclinedNotification(communityID string) (*protocol.MessengerResponse, error) {
|
||||||
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeSetSignerDeclined, true)
|
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeSetSignerDeclined, true, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns response with AC notification when ownership is lost
|
// Returns response with AC notification when ownership is lost
|
||||||
func (api *PublicAPI) RegisterLostOwnershipNotification(communityID string) (*protocol.MessengerResponse, error) {
|
func (api *PublicAPI) RegisterLostOwnershipNotification(communityID string) (*protocol.MessengerResponse, error) {
|
||||||
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeOwnershipLost, false)
|
return api.service.messenger.CreateResponseWithACNotification(communityID, protocol.ActivityCenterNotificationTypeOwnershipLost, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *PublicAPI) PromoteSelfToControlMode(communityID string) error {
|
func (api *PublicAPI) PromoteSelfToControlMode(communityID string) error {
|
||||||
|
|
|
@ -300,7 +300,6 @@ func updateAddressOwnershipTimestamp(creator sqlite.StatementCreator, ownerAddre
|
||||||
// Returns the list of added/removed IDs when comparing the given list of IDs with the ones in the DB.
|
// Returns the list of added/removed IDs when comparing the given list of IDs with the ones in the DB.
|
||||||
// Call before Update for the result to be useful.
|
// Call before Update for the result to be useful.
|
||||||
func (o *OwnershipDB) GetIDsNotInDB(
|
func (o *OwnershipDB) GetIDsNotInDB(
|
||||||
chainID w_common.ChainID,
|
|
||||||
ownerAddress common.Address,
|
ownerAddress common.Address,
|
||||||
newIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleUniqueID, error) {
|
newIDs []thirdparty.CollectibleUniqueID) ([]thirdparty.CollectibleUniqueID, error) {
|
||||||
ret := make([]thirdparty.CollectibleUniqueID, 0, len(newIDs))
|
ret := make([]thirdparty.CollectibleUniqueID, 0, len(newIDs))
|
||||||
|
@ -333,6 +332,31 @@ func (o *OwnershipDB) GetIDsNotInDB(
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *OwnershipDB) GetIsFirstOfCollection(onwerAddress common.Address, newIDs []thirdparty.CollectibleUniqueID) (map[thirdparty.CollectibleUniqueID]bool, error) {
|
||||||
|
ret := make(map[thirdparty.CollectibleUniqueID]bool)
|
||||||
|
|
||||||
|
exists, err := o.db.Prepare(`SELECT count(*) FROM collectibles_ownership_cache
|
||||||
|
WHERE chain_id=? AND contract_address=? AND owner_address=?`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range newIDs {
|
||||||
|
row := exists.QueryRow(
|
||||||
|
id.ContractID.ChainID,
|
||||||
|
id.ContractID.Address,
|
||||||
|
onwerAddress,
|
||||||
|
)
|
||||||
|
var count int
|
||||||
|
err = row.Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret[id] = count <= 1
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (o *OwnershipDB) Update(chainID w_common.ChainID, ownerAddress common.Address, balances thirdparty.TokenBalancesPerContractAddress, timestamp int64) (removedIDs, updatedIDs, insertedIDs []thirdparty.CollectibleUniqueID, err error) {
|
func (o *OwnershipDB) Update(chainID w_common.ChainID, ownerAddress common.Address, balances thirdparty.TokenBalancesPerContractAddress, timestamp int64) (removedIDs, updatedIDs, insertedIDs []thirdparty.CollectibleUniqueID, err error) {
|
||||||
err = insertTmpOwnership(o.db, chainID, ownerAddress, balances)
|
err = insertTmpOwnership(o.db, chainID, ownerAddress, balances)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -415,8 +415,8 @@ func (s *Service) onOwnedCollectiblesChange(ownedCollectiblesChange OwnedCollect
|
||||||
switch ownedCollectiblesChange.changeType {
|
switch ownedCollectiblesChange.changeType {
|
||||||
case OwnedCollectiblesChangeTypeAdded, OwnedCollectiblesChangeTypeUpdated:
|
case OwnedCollectiblesChangeTypeAdded, OwnedCollectiblesChangeTypeUpdated:
|
||||||
// For recently added/updated collectibles, try to find a matching transfer
|
// For recently added/updated collectibles, try to find a matching transfer
|
||||||
s.lookupTransferForCollectibles(ownedCollectiblesChange.ownedCollectibles)
|
hashMap := s.lookupTransferForCollectibles(ownedCollectiblesChange.ownedCollectibles)
|
||||||
s.notifyCommunityCollectiblesReceived(ownedCollectiblesChange.ownedCollectibles)
|
s.notifyCommunityCollectiblesReceived(ownedCollectiblesChange.ownedCollectibles, hashMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,7 +437,7 @@ func (s *Service) onCollectiblesTransfer(account common.Address, chainID walletC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) lookupTransferForCollectibles(ownedCollectibles OwnedCollectibles) {
|
func (s *Service) lookupTransferForCollectibles(ownedCollectibles OwnedCollectibles) map[thirdparty.CollectibleUniqueID]common.Hash {
|
||||||
// There are some limitations to this approach:
|
// There are some limitations to this approach:
|
||||||
// - Collectibles ownership and transfers are not in sync and might represent the state at different moments.
|
// - Collectibles ownership and transfers are not in sync and might represent the state at different moments.
|
||||||
// - We have no way of knowing if the latest collectible transfer we've detected is actually the latest one, so the timestamp we
|
// - We have no way of knowing if the latest collectible transfer we've detected is actually the latest one, so the timestamp we
|
||||||
|
@ -445,6 +445,9 @@ func (s *Service) lookupTransferForCollectibles(ownedCollectibles OwnedCollectib
|
||||||
// - There might be detected transfers that are temporarily not reflected in the collectibles ownership.
|
// - There might be detected transfers that are temporarily not reflected in the collectibles ownership.
|
||||||
// - For ERC721 tokens we should only look for incoming transfers. For ERC1155 tokens we should look for both incoming and outgoing transfers.
|
// - For ERC721 tokens we should only look for incoming transfers. For ERC1155 tokens we should look for both incoming and outgoing transfers.
|
||||||
// We need to get the contract standard for each collectible to know which approach to take.
|
// We need to get the contract standard for each collectible to know which approach to take.
|
||||||
|
|
||||||
|
result := make(map[thirdparty.CollectibleUniqueID]common.Hash)
|
||||||
|
|
||||||
for _, id := range ownedCollectibles.ids {
|
for _, id := range ownedCollectibles.ids {
|
||||||
transfer, err := s.transferDB.GetLatestCollectibleTransfer(ownedCollectibles.account, id)
|
transfer, err := s.transferDB.GetLatestCollectibleTransfer(ownedCollectibles.account, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -452,17 +455,24 @@ func (s *Service) lookupTransferForCollectibles(ownedCollectibles OwnedCollectib
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if transfer != nil {
|
if transfer != nil {
|
||||||
|
result[id] = transfer.Transaction.Hash()
|
||||||
err = s.manager.SetCollectibleTransferID(ownedCollectibles.account, id, transfer.ID, false)
|
err = s.manager.SetCollectibleTransferID(ownedCollectibles.account, id, transfer.ID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error setting transfer ID for collectible", "error", err)
|
log.Error("Error setting transfer ID for collectible", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) notifyCommunityCollectiblesReceived(ownedCollectibles OwnedCollectibles) {
|
func (s *Service) notifyCommunityCollectiblesReceived(ownedCollectibles OwnedCollectibles, hashMap map[thirdparty.CollectibleUniqueID]common.Hash) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
firstCollectibles, err := s.ownershipDB.GetIsFirstOfCollection(ownedCollectibles.account, ownedCollectibles.ids)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
collectiblesData, err := s.manager.FetchAssetsByCollectibleUniqueID(ctx, ownedCollectibles.ids, false)
|
collectiblesData, err := s.manager.FetchAssetsByCollectibleUniqueID(ctx, ownedCollectibles.ids, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error fetching collectibles data", "error", err)
|
log.Error("Error fetching collectibles data", "error", err)
|
||||||
|
@ -475,7 +485,45 @@ func (s *Service) notifyCommunityCollectiblesReceived(ownedCollectibles OwnedCol
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedMessage, err := json.Marshal(communityCollectibles)
|
type CollectibleGroup struct {
|
||||||
|
contractID thirdparty.ContractID
|
||||||
|
txHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := make(map[CollectibleGroup]Collectible)
|
||||||
|
for i, collectible := range communityCollectibles {
|
||||||
|
for key, value := range hashMap {
|
||||||
|
if key.Same(&collectible.ID) {
|
||||||
|
communityCollectibles[i].LatestTxHash = value.Hex()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, value := range firstCollectibles {
|
||||||
|
if value && id.Same(&collectible.ID) {
|
||||||
|
communityCollectibles[i].IsFirst = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group := CollectibleGroup{
|
||||||
|
contractID: collectible.ID.ContractID,
|
||||||
|
txHash: collectible.LatestTxHash,
|
||||||
|
}
|
||||||
|
_, ok := groups[group]
|
||||||
|
if !ok {
|
||||||
|
collectible.ReceivedAmount = float64(0)
|
||||||
|
}
|
||||||
|
collectible.ReceivedAmount = collectible.ReceivedAmount + 1
|
||||||
|
groups[group] = collectible
|
||||||
|
}
|
||||||
|
|
||||||
|
groupedCommunityCollectibles := make([]Collectible, 0, len(groups))
|
||||||
|
for _, collectible := range groups {
|
||||||
|
groupedCommunityCollectibles = append(groupedCommunityCollectibles, collectible)
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedMessage, err := json.Marshal(groupedCommunityCollectibles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@ type Collectible struct {
|
||||||
CollectionData *CollectionData `json:"collection_data,omitempty"`
|
CollectionData *CollectionData `json:"collection_data,omitempty"`
|
||||||
CommunityData *CommunityData `json:"community_data,omitempty"`
|
CommunityData *CommunityData `json:"community_data,omitempty"`
|
||||||
Ownership []thirdparty.AccountBalance `json:"ownership,omitempty"`
|
Ownership []thirdparty.AccountBalance `json:"ownership,omitempty"`
|
||||||
|
IsFirst bool `json:"is_first,omitempty"`
|
||||||
|
LatestTxHash string `json:"latest_tx_hash,omitempty"`
|
||||||
|
ReceivedAmount float64 `json:"received_amount,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollectibleData struct {
|
type CollectibleData struct {
|
||||||
|
@ -167,10 +170,12 @@ func fullCollectiblesDataToCommunityHeader(data []thirdparty.FullCollectibleData
|
||||||
ID: collectibleID,
|
ID: collectibleID,
|
||||||
ContractType: getContractType(c),
|
ContractType: getContractType(c),
|
||||||
CollectibleData: &CollectibleData{
|
CollectibleData: &CollectibleData{
|
||||||
Name: c.CollectibleData.Name,
|
Name: c.CollectibleData.Name,
|
||||||
|
ImageURL: &c.CollectibleData.ImageURL,
|
||||||
},
|
},
|
||||||
CommunityData: &communityData,
|
CommunityData: &communityData,
|
||||||
Ownership: c.Ownership,
|
Ownership: c.Ownership,
|
||||||
|
IsFirst: c.CollectibleData.IsFirst,
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, header)
|
res = append(res, header)
|
||||||
|
|
|
@ -138,6 +138,7 @@ type CollectibleData struct {
|
||||||
Traits []CollectibleTrait `json:"traits"`
|
Traits []CollectibleTrait `json:"traits"`
|
||||||
BackgroundColor string `json:"background_color"`
|
BackgroundColor string `json:"background_color"`
|
||||||
TokenURI string `json:"token_uri"`
|
TokenURI string `json:"token_uri"`
|
||||||
|
IsFirst bool `json:"is_first"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Community-related collectible info. Present only for collectibles minted in a community.
|
// Community-related collectible info. Present only for collectibles minted in a community.
|
||||||
|
|
|
@ -68,8 +68,9 @@ type ReceivedToken struct {
|
||||||
Image string `json:"image,omitempty"`
|
Image string `json:"image,omitempty"`
|
||||||
ChainID uint64 `json:"chainId"`
|
ChainID uint64 `json:"chainId"`
|
||||||
CommunityData *community.Data `json:"community_data,omitempty"`
|
CommunityData *community.Data `json:"community_data,omitempty"`
|
||||||
Balance *big.Int `json:"balance"`
|
Amount float64 `json:"amount"`
|
||||||
TxHash common.Hash `json:"txHash"`
|
TxHash common.Hash `json:"txHash"`
|
||||||
|
IsFirst bool `json:"isFirst"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Token) IsNative() bool {
|
func (t *Token) IsNative() bool {
|
||||||
|
@ -316,20 +317,20 @@ func (tm *Manager) FindOrCreateTokenByAddress(ctx context.Context, chainID uint6
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tm *Manager) MarkAsPreviouslyOwnedToken(token *Token, owner common.Address) error {
|
func (tm *Manager) MarkAsPreviouslyOwnedToken(token *Token, owner common.Address) (bool, error) {
|
||||||
if token == nil {
|
if token == nil {
|
||||||
return errors.New("token is nil")
|
return false, errors.New("token is nil")
|
||||||
}
|
}
|
||||||
if (owner == common.Address{}) {
|
if (owner == common.Address{}) {
|
||||||
return errors.New("owner is nil")
|
return false, errors.New("owner is nil")
|
||||||
}
|
}
|
||||||
count := 0
|
count := 0
|
||||||
err := tm.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM token_balances WHERE user_address = ? AND token_address = ? AND chain_id = ?)`, owner.Hex(), token.Address.Hex(), token.ChainID).Scan(&count)
|
err := tm.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM token_balances WHERE user_address = ? AND token_address = ? AND chain_id = ?)`, owner.Hex(), token.Address.Hex(), token.ChainID).Scan(&count)
|
||||||
if err != nil || count > 0 {
|
if err != nil || count > 0 {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
_, err = tm.db.Exec(`INSERT INTO token_balances(user_address,token_name,token_symbol,token_address,token_decimals,chain_id,token_decimals,raw_balance,balance) VALUES (?,?,?,?,?,?,?,?,?)`, owner.Hex(), token.Name, token.Symbol, token.Address.Hex(), token.Decimals, token.ChainID, 0, "0", "0")
|
_, err = tm.db.Exec(`INSERT INTO token_balances(user_address,token_name,token_symbol,token_address,token_decimals,chain_id,token_decimals,raw_balance,balance) VALUES (?,?,?,?,?,?,?,?,?)`, owner.Hex(), token.Name, token.Symbol, token.Address.Hex(), token.Decimals, token.ChainID, 0, "0", "0")
|
||||||
return err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tm *Manager) discoverTokenCommunityID(ctx context.Context, token *Token, address common.Address) {
|
func (tm *Manager) discoverTokenCommunityID(ctx context.Context, token *Token, address common.Address) {
|
||||||
|
@ -809,7 +810,7 @@ func (tm *Manager) GetBalancesAtByChain(parent context.Context, clients map[uint
|
||||||
return response, group.Error()
|
return response, group.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tm *Manager) SignalCommunityTokenReceived(address common.Address, txHash common.Hash, value *big.Int, t *Token) {
|
func (tm *Manager) SignalCommunityTokenReceived(address common.Address, txHash common.Hash, value *big.Int, t *Token, isFirst bool) {
|
||||||
if tm.walletFeed == nil || t == nil || t.CommunityData == nil {
|
if tm.walletFeed == nil || t == nil || t.CommunityData == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -826,6 +827,8 @@ func (tm *Manager) SignalCommunityTokenReceived(address common.Address, txHash c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
floatAmount, _ := new(big.Float).Quo(new(big.Float).SetInt(value), new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(t.Decimals)), nil))).Float64()
|
||||||
|
|
||||||
receivedToken := ReceivedToken{
|
receivedToken := ReceivedToken{
|
||||||
Address: t.Address,
|
Address: t.Address,
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
|
@ -833,8 +836,9 @@ func (tm *Manager) SignalCommunityTokenReceived(address common.Address, txHash c
|
||||||
Image: t.Image,
|
Image: t.Image,
|
||||||
ChainID: t.ChainID,
|
ChainID: t.ChainID,
|
||||||
CommunityData: t.CommunityData,
|
CommunityData: t.CommunityData,
|
||||||
Balance: value,
|
Amount: floatAmount,
|
||||||
TxHash: txHash,
|
TxHash: txHash,
|
||||||
|
IsFirst: isFirst,
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedMessage, err := json.Marshal(receivedToken)
|
encodedMessage, err := json.Marshal(receivedToken)
|
||||||
|
|
|
@ -208,14 +208,17 @@ func TestMarkAsPreviouslyOwnedToken(t *testing.T) {
|
||||||
ChainID: 1,
|
ChainID: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := manager.MarkAsPreviouslyOwnedToken(nil, owner)
|
isFirst, err := manager.MarkAsPreviouslyOwnedToken(nil, owner)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
require.False(t, isFirst)
|
||||||
|
|
||||||
err = manager.MarkAsPreviouslyOwnedToken(token, common.Address{})
|
isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, common.Address{})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
require.False(t, isFirst)
|
||||||
|
|
||||||
err = manager.MarkAsPreviouslyOwnedToken(token, owner)
|
isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.True(t, isFirst)
|
||||||
|
|
||||||
// Verify that the token balance was inserted correctly
|
// Verify that the token balance was inserted correctly
|
||||||
var count int
|
var count int
|
||||||
|
@ -225,8 +228,9 @@ func TestMarkAsPreviouslyOwnedToken(t *testing.T) {
|
||||||
|
|
||||||
token.Name = "123"
|
token.Name = "123"
|
||||||
|
|
||||||
err = manager.MarkAsPreviouslyOwnedToken(token, owner)
|
isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.False(t, isFirst)
|
||||||
|
|
||||||
// Not updated because already exists
|
// Not updated because already exists
|
||||||
err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
|
err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
|
||||||
|
@ -235,11 +239,12 @@ func TestMarkAsPreviouslyOwnedToken(t *testing.T) {
|
||||||
|
|
||||||
token.ChainID = 2
|
token.ChainID = 2
|
||||||
|
|
||||||
err = manager.MarkAsPreviouslyOwnedToken(token, owner)
|
isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Same token on different chains counts as different token
|
// Same token on different chains counts as different token
|
||||||
err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
|
err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, count)
|
require.Equal(t, 2, count)
|
||||||
|
require.True(t, isFirst)
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,11 +378,11 @@ func (c *transfersCommand) markMultiTxTokensAsPreviouslyOwned(ctx context.Contex
|
||||||
}
|
}
|
||||||
if len(multiTransaction.ToAsset) > 0 && multiTransaction.ToNetworkID > 0 {
|
if len(multiTransaction.ToAsset) > 0 && multiTransaction.ToNetworkID > 0 {
|
||||||
token := c.tokenManager.GetToken(multiTransaction.ToNetworkID, multiTransaction.ToAsset)
|
token := c.tokenManager.GetToken(multiTransaction.ToNetworkID, multiTransaction.ToAsset)
|
||||||
_ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, ownerAddress)
|
_, _ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, ownerAddress)
|
||||||
}
|
}
|
||||||
if len(multiTransaction.FromAsset) > 0 && multiTransaction.FromNetworkID > 0 {
|
if len(multiTransaction.FromAsset) > 0 && multiTransaction.FromNetworkID > 0 {
|
||||||
token := c.tokenManager.GetToken(multiTransaction.FromNetworkID, multiTransaction.FromAsset)
|
token := c.tokenManager.GetToken(multiTransaction.FromNetworkID, multiTransaction.FromAsset)
|
||||||
_ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, ownerAddress)
|
_, _ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, ownerAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,11 +439,12 @@ func (c *transfersCommand) processUnknownErc20CommunityTransactions(ctx context.
|
||||||
// Find token in db or if this is a community token, find its metadata
|
// Find token in db or if this is a community token, find its metadata
|
||||||
token := c.tokenManager.FindOrCreateTokenByAddress(ctx, tx.NetworkID, *tx.Transaction.To())
|
token := c.tokenManager.FindOrCreateTokenByAddress(ctx, tx.NetworkID, *tx.Transaction.To())
|
||||||
if token != nil {
|
if token != nil {
|
||||||
|
isFirst := false
|
||||||
if token.Verified || token.CommunityData != nil {
|
if token.Verified || token.CommunityData != nil {
|
||||||
_ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, tx.Address)
|
isFirst, _ = c.tokenManager.MarkAsPreviouslyOwnedToken(token, tx.Address)
|
||||||
}
|
}
|
||||||
if token.CommunityData != nil {
|
if token.CommunityData != nil {
|
||||||
go c.tokenManager.SignalCommunityTokenReceived(tx.Address, tx.ID, tx.Transaction.Value(), token)
|
go c.tokenManager.SignalCommunityTokenReceived(tx.Address, tx.ID, tx.TokenValue, token, isFirst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue