Repository: trustwallet/blockatlas
Pull request #1374 (2021-01-18 10:55:04)

[Vechain] Fix VTHO token transaction direction
move direction code out of `NormalizeTokenTransaction`, it's reused between block api and tokens api

codecov[bot]: # [Codecov](https://codecov.io/gh/trustwallet/blockatlas/pull/1374?src=pr&el=h1) Report
> Merging [#1374](https://codecov.io/gh/trustwallet/blockatlas/pull/1374?src=pr&el=desc) (77cc90f) into [master](https://codecov.io/gh/trustwallet/blockatlas/commit/ce3600e70611faad0dcbb07820ce984d2a7a38c4?el=desc) (ce3600e) will **decrease** coverage by `0%`.
> The diff coverage is `79%`.

[![Impacted file tree graph](https://codecov.io/gh/trustwallet/blockatlas/pull/1374/graphs/tree.svg?width=650&height=150&src=pr&token=jnrjoEQAqv)](https://codecov.io/gh/trustwallet/blockatlas/pull/1374?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           master   #1374   +/-   ##
======================================
- Coverage      45%     45%   -1%     
======================================
  Files         142     142           
  Lines        4168    4163    -5     
======================================
- Hits         1907    1902    -5     
  Misses       2090    2090           
  Partials      171     171           
```


| [Impacted Files](https://codecov.io/gh/trustwallet/blockatlas/pull/1374?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [platform/vechain/transaction.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1374/diff?src=pr&el=tree#diff-cGxhdGZvcm0vdmVjaGFpbi90cmFuc2FjdGlvbi5nbw==) | `49% <79%> (-2%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/trustwallet/blockatlas/pull/1374?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/trustwallet/blockatlas/pull/1374?src=pr&el=footer). Last update [ce3600e...77cc90f](https://codecov.io/gh/trustwallet/blockatlas/pull/1374?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/Procfile b/Procfile
index ebaa812..c06f6e4 100644
--- a/Procfile
+++ b/Procfile
@@ -1,6 +1,7 @@
 release: bin/setup -c $HOME/config.yml
 web: bin/api -c $HOME/config.yml -p $PORT
-consumer-transactions: CONSUMER_SERVICE=transactions bin/consumer -c $HOME/config.yml
-consumer-subscriptions: CONSUMER_SERVICE=subscriptions bin/consumer -c $HOME/config.yml
-consumer-tokens: CONSUMER_SERVICE=tokens bin/consumer -c $HOME/config.yml
+consumer_transactions: CONSUMER_SERVICE=transactions bin/consumer -c $HOME/config.yml
+consumer_subscriptions: CONSUMER_SERVICE=subscriptions bin/consumer -c $HOME/config.yml
+consumer_subscriptions_tokens: CONSUMER_SERVICE=subscriptions_tokens bin/consumer -c $HOME/config.yml
+consumer_tokens: CONSUMER_SERVICE=tokens bin/consumer -c $HOME/config.yml
 parser: bin/parser -c $HOME/config.yml
diff --git a/api/endpoint/token.go b/api/endpoint/token.go
index 994a0c1..dfdb975 100644
--- a/api/endpoint/token.go
+++ b/api/endpoint/token.go
@@ -19,7 +19,7 @@ import (
 // @Tags Transactions
 // @Param coin path string true "the coin name" default(ethereum)
 // @Param address path string true "the query address" default(0x5574Cd97432cEd0D7Caf58ac3c4fEDB2061C98fB)
-// @Success 200 {object} types.CollectionPage
+// @Success 200 {object} []string
 // @Failure 500 {object} ErrorResponse
 // @Router /v2/{coin}/tokens/{address} [get]
 func GetTokensByAddress(c *gin.Context, tokenAPI blockatlas.TokensAPI) {
@@ -34,7 +34,7 @@ func GetTokensByAddress(c *gin.Context, tokenAPI blockatlas.TokensAPI) {
 		c.AbortWithStatusJSON(http.StatusInternalServerError, errorResponse(err))
 		return
 	}
-	c.JSON(http.StatusOK, blockatlas.DocsResponse{Docs: &result})
+	c.JSON(http.StatusOK, result)
 }
 
 func GetTokensByAddressV3(c *gin.Context, instance tokenindexer.Instance) {
diff --git a/cmd/consumer/main.go b/cmd/consumer/main.go
index cb26f90..bba2018 100644
--- a/cmd/consumer/main.go
+++ b/cmd/consumer/main.go
@@ -31,9 +31,10 @@ var (
 	cancel   context.CancelFunc
 	database *db.Instance
 
-	transactions  = "transactions"
-	tokens        = "tokens"
-	subscriptions = "subscriptions"
+	transactions        = "transactions"
+	tokens              = "tokens"
+	subscriptions       = "subscriptions"
+	subscriptionsTokens = "subscriptions_tokens"
 )
 
 func init() {
@@ -63,17 +64,22 @@ func main() {
 	platform.Init(config.Default.Platform)
 
 	options := mq.InitDefaultConsumerOptions(config.Default.Consumer.Workers)
+	// Special case options to avoid unknown deadlock on insert
+	subscriptionsOptions := mq.InitDefaultConsumerOptions(1)
 
 	switch config.Default.Consumer.Service {
 	case transactions:
 		setupTransactionsConsumer(options, ctx)
 	case subscriptions:
-		setupSubscriptionsConsumer(options, ctx)
+		setupSubscriptionsConsumer(subscriptionsOptions, ctx)
+	case subscriptionsTokens:
+		setupSubscriptionsTokensConsumer(options, ctx)
 	case tokens:
 		setupTokensConsumer(options, ctx)
 	default:
 		setupTransactionsConsumer(options, ctx)
-		setupSubscriptionsConsumer(options, ctx)
+		setupSubscriptionsConsumer(subscriptionsOptions, ctx)
+		setupSubscriptionsTokensConsumer(options, ctx)
 		setupTokensConsumer(options, ctx)
 	}
 
@@ -85,16 +91,34 @@ func main() {
 }
 
 func setupTransactionsConsumer(options mq.ConsumerOptions, ctx context.Context) {
-	go internal.RawTransactions.RunConsumer(internal.ConsumerDatabase{Database: database, Delivery: notifier.RunNotifier}, options, ctx)
+	go internal.RawTransactions.RunConsumer(internal.ConsumerDatabase{
+		Database: database,
+		Delivery: notifier.RunNotifier,
+		Tag:      transactions,
+	}, options, ctx)
 }
 
 func setupSubscriptionsConsumer(options mq.ConsumerOptions, ctx context.Context) {
-	// Special case options to avoid unknown deadlock on insert
-	subscriptionsOptions := mq.InitDefaultConsumerOptions(1)
-	go internal.Subscriptions.RunConsumer(internal.ConsumerDatabase{Database: database, Delivery: subscriber.RunSubscriber}, subscriptionsOptions, ctx)
-	go internal.SubscriptionsTokens.RunConsumer(tokenindexer.ConsumerIndexer{Database: database, TokensAPIs: platform.TokensAPIs, Delivery: tokenindexer.RunTokenIndexerSubscribe}, options, ctx)
+	go internal.Subscriptions.RunConsumer(internal.ConsumerDatabase{
+		Database: database,
+		Delivery: subscriber.RunSubscriber,
+		Tag:      subscriptions,
+	}, options, ctx)
+}
+
+func setupSubscriptionsTokensConsumer(options mq.ConsumerOptions, ctx context.Context) {
+	go internal.SubscriptionsTokens.RunConsumer(tokenindexer.ConsumerIndexer{
+		Database:   database,
+		TokensAPIs: platform.TokensAPIs,
+		Delivery:   tokenindexer.RunTokenIndexerSubscribe,
+		Tag:        subscriptionsTokens,
+	}, options, ctx)
 }
 
 func setupTokensConsumer(options mq.ConsumerOptions, ctx context.Context) {
-	go internal.RawTokens.RunConsumer(internal.ConsumerDatabase{Database: database, Delivery: tokenindexer.RunTokenIndexer}, options, ctx)
+	go internal.RawTokens.RunConsumer(internal.ConsumerDatabase{
+		Database: database,
+		Delivery: tokenindexer.RunTokenIndexer,
+		Tag:      tokens,
+	}, options, ctx)
 }
diff --git a/config.yml b/config.yml
index 3001b68..72640d5 100644
--- a/config.yml
+++ b/config.yml
@@ -99,7 +99,7 @@ icon:
 # [TRX] Tron: https://tron.network/
 tron:
   api: https://api.trongrid.io
-  explorer: https://apilist.tronscan.org
+  grid: https://api.trongrid.io # trongrid api
 
 # [VET] VeChain: https://www.vechain.org
 vechain:
diff --git a/config/configuration.go b/config/configuration.go
index eb9fe30..3015365 100755
--- a/config/configuration.go
+++ b/config/configuration.go
@@ -99,6 +99,7 @@ type Configuration struct {
 	} `mapstructure:"icon"`
 	Tron struct {
 		API      string `mapstructure:"api"`
+		Grid     string `mapstructure:"grid"`
 		Explorer string `mapstructure:"explorer"`
 	} `mapstructure:"tron"`
 	Vechain struct {
diff --git a/db/subscription.go b/db/subscription.go
index 53cc191..4dbb720 100644
--- a/db/subscription.go
+++ b/db/subscription.go
@@ -2,11 +2,11 @@ package db
 
 import (
 	"github.com/trustwallet/blockatlas/db/models"
-	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"github.com/trustwallet/golibs/types"
 	"gorm.io/gorm/clause"
 )
 
-func (i *Instance) CreateSubscriptions(addresses []blockatlas.Subscription) error {
+func (i *Instance) CreateSubscriptions(addresses []types.Subscription) error {
 	if len(addresses) == 0 {
 		return nil
 	}
diff --git a/go.mod b/go.mod
index 80ef65d..fe6c6b9 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,7 @@ require (
 	github.com/stretchr/testify v1.7.0
 	github.com/swaggo/gin-swagger v1.3.0
 	github.com/swaggo/swag v1.7.0
-	github.com/trustwallet/golibs v0.0.32
+	github.com/trustwallet/golibs v0.0.33
 	github.com/trustwallet/golibs/network v0.0.0-20210113212158-b74318d7ab45
 	golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
 	golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
diff --git a/go.sum b/go.sum
index dbf3b64..b5904ba 100644
--- a/go.sum
+++ b/go.sum
@@ -418,8 +418,8 @@ github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+t
 github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
 github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/trustwallet/golibs v0.0.32 h1:3kBJ6bX2t2HdIEjbuKk/WsExgxHUZVH1JkNbPdofeVs=
-github.com/trustwallet/golibs v0.0.32/go.mod h1:MhWay9yOMBj3yEXTd1EepmxOwekbhVq4F1cIYAD2WRE=
+github.com/trustwallet/golibs v0.0.33 h1:nADMzgTh/MH+4UHK/TfsZ77VVmMUrFPVIIyMBhbDW0M=
+github.com/trustwallet/golibs v0.0.33/go.mod h1:VI0APImKAYxvJ8MnMD6aHSKvnArXUd3NgNEghX5P+K8=
 github.com/trustwallet/golibs/network v0.0.0-20210113212158-b74318d7ab45 h1:zaobIG3KO/CHpY1o/rGouYFHCWIzLE6uYSUwz2JCLmw=
 github.com/trustwallet/golibs/network v0.0.0-20210113212158-b74318d7ab45/go.mod h1:RMMyjp4qUcVhWidjE+XYzQgOtSXuhIKSB5pnLCA3WqY=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
diff --git a/internal/mq.go b/internal/mq.go
index 3fa3a64..e41ff9b 100644
--- a/internal/mq.go
+++ b/internal/mq.go
@@ -22,8 +22,13 @@ const (
 type ConsumerDatabase struct {
 	Database *db.Instance
 	Delivery func(*db.Instance, amqp.Delivery) error
+	Tag      string
 }
 
 func (c ConsumerDatabase) Callback(msg amqp.Delivery) error {
 	return c.Delivery(c.Database, msg)
 }
+
+func (c ConsumerDatabase) ConsumerTag() string {
+	return c.Tag
+}
diff --git a/pkg/blockatlas/observer.go b/pkg/blockatlas/observer.go
deleted file mode 100644
index bb7aa0d..0000000
--- a/pkg/blockatlas/observer.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package blockatlas
-
-import "strconv"
-
-type (
-	Subscriptions map[string][]string
-
-	SubscriptionOperation string
-
-	SubscriptionEvent struct {
-		Subscriptions Subscriptions         `json:"subscriptions"`
-		Operation     SubscriptionOperation `json:"operation"`
-	}
-
-	Subscription struct {
-		Coin    uint   `json:"coin"`
-		Address string `json:"address"`
-	}
-)
-
-func (v *Subscription) AddressID() string {
-	return GetAddressID(strconv.Itoa(int(v.Coin)), v.Address)
-}
-
-func GetAddressID(coin, address string) string {
-	return coin + "_" + address
-}
-
-func (e *SubscriptionEvent) ParseSubscriptions(s Subscriptions) []Subscription {
-	subs := make([]Subscription, 0)
-	for coinStr, perCoin := range s {
-		coin, err := strconv.Atoi(coinStr)
-		if err != nil {
-			continue
-		}
-		for _, addr := range perCoin {
-			subs = append(subs, Subscription{
-				Coin:    uint(coin),
-				Address: addr,
-			})
-		}
-	}
-	return subs
-}
diff --git a/pkg/blockatlas/platform.go b/pkg/blockatlas/platform.go
index 09020c7..5e718b5 100644
--- a/pkg/blockatlas/platform.go
+++ b/pkg/blockatlas/platform.go
@@ -39,7 +39,7 @@ type (
 	// TokensAPI provides token lookups
 	TokensAPI interface {
 		Platform
-		GetTokenListByAddress(address string) (types.TokenPage, error)
+		GetTokenListByAddress(address string) ([]string, error)
 	}
 
 	// StakingAPI provides staking information
diff --git a/platform/binance/mocks/tokens.json b/platform/binance/mocks/tokens.json
index 8f8d1bc..c165618 100644
--- a/platform/binance/mocks/tokens.json
+++ b/platform/binance/mocks/tokens.json
@@ -1,26 +1,5 @@
 [
-  {
-    "name": "Travala.com Token",
-    "symbol": "AVA",
-    "decimals": 8,
-    "token_id": "AVA-645",
-    "coin": 714,
-    "type": "BEP2"
-  },
-  {
-    "name": "Binance Chain Native Token",
-    "symbol": "BNB",
-    "decimals": 8,
-    "token_id": "BNB",
-    "coin": 714,
-    "type": "BEP2"
-  },
-  {
-    "name": "Binance USD",
-    "symbol": "BUSD",
-    "decimals": 8,
-    "token_id": "BUSD-BD1",
-    "coin": 714,
-    "type": "BEP2"
-  }
+  "c714_tAVA-645",
+  "c714_tBNB",
+  "c714_tBUSD-BD1"
 ]
diff --git a/platform/binance/model.go b/platform/binance/model.go
index fb15289..dfa2c2b 100644
--- a/platform/binance/model.go
+++ b/platform/binance/model.go
@@ -215,16 +215,16 @@ func normalizeBaseOfTransaction(t Tx) types.Tx {
 	}
 }
 
-func normalizeTokens(srcBalance []TokenBalance, tokens Tokens) []types.Token {
-	tokensList := make([]types.Token, 0, len(srcBalance))
+func normalizeTokens(srcBalance []TokenBalance, tokens Tokens) []string {
+	assetIds := make([]string, 0)
 	for _, srcToken := range srcBalance {
 		token, ok := normalizeToken(srcToken, tokens)
 		if !ok {
 			continue
 		}
-		tokensList = append(tokensList, token)
+		assetIds = append(assetIds, token.AssetId())
 	}
-	return tokensList
+	return assetIds
 }
 
 func normalizeToken(srcToken TokenBalance, tokens Tokens) (types.Token, bool) {
diff --git a/platform/binance/token.go b/platform/binance/token.go
index 8448cb2..2989314 100644
--- a/platform/binance/token.go
+++ b/platform/binance/token.go
@@ -1,8 +1,6 @@
 package binance
 
-import "github.com/trustwallet/golibs/types"
-
-func (p *Platform) GetTokenListByAddress(address string) (types.TokenPage, error) {
+func (p *Platform) GetTokenListByAddress(address string) ([]string, error) {
 	account, err := p.client.FetchAccountMeta(address)
 	if err != nil || len(account.Balances) == 0 {
 		return nil, nil
diff --git a/platform/bitcoin/blockbook/token.go b/platform/bitcoin/blockbook/token.go
index 09d4ac4..429faa9 100644
--- a/platform/bitcoin/blockbook/token.go
+++ b/platform/bitcoin/blockbook/token.go
@@ -1,10 +1,10 @@
 package blockbook
 
 import (
-	"github.com/trustwallet/golibs/types"
+	"github.com/trustwallet/golibs/asset"
 )
 
-func (c *Client) GetTokenList(address string, coinIndex uint) (types.TokenPage, error) {
+func (c *Client) GetTokenList(address string, coinIndex uint) ([]string, error) {
 	tokens, err := c.GetTokens(address)
 	if err != nil {
 		return nil, err
@@ -12,25 +12,13 @@ func (c *Client) GetTokenList(address string, coinIndex uint) (types.TokenPage,
 	return NormalizeTokens(tokens, coinIndex), nil
 }
 
-func NormalizeTokens(srcTokens []Token, coinIndex uint) []types.Token {
-	tokenPage := make([]types.Token, 0, len(srcTokens))
+func NormalizeTokens(srcTokens []Token, coinIndex uint) []string {
+	tokenPage := make([]string, 0)
 	for _, srcToken := range srcTokens {
 		if srcToken.Balance == "0" || srcToken.Balance == "" {
 			continue
 		}
-		token := NormalizeToken(&srcToken, coinIndex)
-		tokenPage = append(tokenPage, token)
+		tokenPage = append(tokenPage, asset.BuildID(coinIndex, srcToken.Contract))
 	}
 	return tokenPage
 }
-
-func NormalizeToken(srcToken *Token, coinIndex uint) types.Token {
-	return types.Token{
-		Name:     srcToken.Name,
-		Symbol:   srcToken.Symbol,
-		TokenID:  srcToken.Contract,
-		Coin:     coinIndex,
-		Decimals: srcToken.Decimals,
-		Type:     types.GetEthereumTokenTypeByIndex(coinIndex),
-	}
-}
diff --git a/platform/elrond/mocks/scr_negative_value.json b/platform/elrond/mocks/scr_negative_value.json
new file mode 100644
index 0000000..a5cd936
--- /dev/null
+++ b/platform/elrond/mocks/scr_negative_value.json
@@ -0,0 +1,9 @@
+{
+  "hash": "a96ed4816f5312d00361cc16037e19e0cffa4765a79a808d54d89db58fd76aa6",
+  "value": "-2500000000000000000000",
+  "receiver": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l",
+  "sender": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l",
+  "gasPrice": 1000000000,
+  "signature": "",
+  "status": "success"
+}
diff --git a/platform/elrond/model.go b/platform/elrond/model.go
index 422cd49..705f8ea 100644
--- a/platform/elrond/model.go
+++ b/platform/elrond/model.go
@@ -3,6 +3,7 @@ package elrond
 import (
 	"encoding/json"
 	"math/big"
+	"strings"
 	"time"
 
 	"github.com/trustwallet/golibs/types"
@@ -93,6 +94,10 @@ func (tx *Transaction) Direction(address string) types.Direction {
 	}
 }
 
+func (tx *Transaction) HasNegativeValue() bool {
+	return strings.HasPrefix(tx.Value, "-")
+}
+
 func (tx *Transaction) TxTimestamp(blockRound uint64) time.Duration {
 	if int64(tx.Timestamp) != 0 {
 		return tx.Timestamp
diff --git a/platform/elrond/transaction.go b/platform/elrond/transaction.go
index cf130b8..ebfdedf 100644
--- a/platform/elrond/transaction.go
+++ b/platform/elrond/transaction.go
@@ -25,6 +25,10 @@ func NormalizeTxs(srcTxs []Transaction, address string, block Block) (txs []type
 
 // NormalizeTx converts an Elrond transaction into the generic model
 func NormalizeTx(srcTx Transaction, address string, block Block) (tx types.Tx, ok bool) {
+	if srcTx.HasNegativeValue() {
+		return types.Tx{}, false
+	}
+
 	tx = types.Tx{
 		ID:       srcTx.Hash,
 		Coin:     coin.Elrond().ID,
diff --git a/platform/ethereum/client.go b/platform/ethereum/client.go
index 6229271..219765a 100644
--- a/platform/ethereum/client.go
+++ b/platform/ethereum/client.go
@@ -5,12 +5,12 @@ import "github.com/trustwallet/golibs/types"
 type EthereumClient interface {
 	GetTransactions(address string, coinIndex uint) (types.TxPage, error)
 	GetTokenTxs(address, token string, coinIndex uint) (types.TxPage, error)
-	GetTokenList(address string, coinIndex uint) (types.TokenPage, error)
+	GetTokenList(address string, coinIndex uint) ([]string, error)
 	GetCurrentBlockNumber() (int64, error)
 	GetBlockByNumber(num int64, coinIndex uint) (*types.Block, error)
 }
 
-func (p *Platform) GetTokenListByAddress(address string) (types.TokenPage, error) {
+func (p *Platform) GetTokenListByAddress(address string) ([]string, error) {
 	return p.client.GetTokenList(address, p.CoinIndex)
 }
 
diff --git a/platform/ethereum/trustray/token.go b/platform/ethereum/trustray/token.go
index ef38466..78b07d5 100644
--- a/platform/ethereum/trustray/token.go
+++ b/platform/ethereum/trustray/token.go
@@ -4,7 +4,7 @@ import (
 	"github.com/trustwallet/golibs/types"
 )
 
-func (c *Client) GetTokenList(address string, coinIndex uint) (types.TokenPage, error) {
+func (c *Client) GetTokenList(address string, coinIndex uint) ([]string, error) {
 	account, err := c.GetTokens(address)
 	if err != nil {
 		return nil, err
@@ -27,11 +27,11 @@ func NormalizeToken(srcToken *Contract, coinIndex uint) types.Token {
 }
 
 // NormalizeTxs converts multiple Ethereum tokens
-func NormalizeTokens(srcTokens []Contract, coinIndex uint) []types.Token {
-	tokenPage := make([]types.Token, 0)
+func NormalizeTokens(srcTokens []Contract, coinIndex uint) []string {
+	assetIds := make([]string, 0)
 	for _, srcToken := range srcTokens {
 		token := NormalizeToken(&srcToken, coinIndex)
-		tokenPage = append(tokenPage, token)
+		assetIds = append(assetIds, token.AssetId())
 	}
-	return tokenPage
+	return assetIds
 }
diff --git a/platform/platform.go b/platform/platform.go
index 5721ca6..1f59a60 100644
--- a/platform/platform.go
+++ b/platform/platform.go
@@ -50,7 +50,7 @@ func getAllHandlers() blockatlas.Platforms {
 		coin.Fio().Handle:          fio.Init(config.Default.Fio.API),
 		coin.Aion().Handle:         aion.Init(config.Default.Aion.API),
 		coin.Icon().Handle:         icon.Init(config.Default.Icon.API),
-		coin.Tron().Handle:         tron.Init(config.Default.Tron.API, config.Default.Tron.Explorer),
+		coin.Tron().Handle:         tron.Init(config.Default.Tron.API, config.Default.Tron.Grid),
 		coin.Nano().Handle:         nano.Init(config.Default.Nano.API),
 		coin.Nimiq().Handle:        nimiq.Init(config.Default.Nimiq.API),
 		coin.Iotex().Handle:        iotex.Init(config.Default.Iotex.API),
diff --git a/platform/tezos/block.go b/platform/tezos/block.go
index c5983cc..eda5979 100644
--- a/platform/tezos/block.go
+++ b/platform/tezos/block.go
@@ -2,7 +2,7 @@ package tezos
 
 import (
 	"encoding/json"
-	"errors"
+	"fmt"
 
 	"github.com/trustwallet/golibs/coin"
 	"github.com/trustwallet/golibs/types"
@@ -19,20 +19,36 @@ func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) {
 	if err != nil {
 		return nil, err
 	}
-	return NormalizeRpcBlock(block)
+
+	return ProcessRpcBlock(block, &p.rpcClient)
 }
 
-func NormalizeRpcBlock(block RpcBlock) (*types.Block, error) {
+func ProcessRpcBlock(block RpcBlock, rpcClient IRpcClient) (*types.Block, error) {
 	txs := []types.Tx{}
 
 	for _, ops := range block.Operations {
 		for _, op := range ops {
 			for _, content := range op.Contents {
 				if tx, err := mapTransaction(content); err == nil {
-					if normalized, err := NormalizeRpcTransaction(tx, block.Header); err == nil {
-						normalized.ID = op.Hash
-						txs = append(txs, normalized)
+					if !(tx.Kind == TxTypeDelegation || tx.Kind == TxTypeTransaction) {
+						continue
+					}
+
+					balance := tx.Amount
+					if len(balance) == 0 {
+						account, err := rpcClient.GetAccountBalanceAtBlock(tx.Source, block.Header.Level)
+						if err != nil {
+							return nil, err
+						}
+						balance = account.Balance
 					}
+
+					normalized, err := NormalizeRpcTransaction(tx, block.Header, balance)
+					if err != nil {
+						return nil, err
+					}
+					normalized.ID = op.Hash
+					txs = append(txs, normalized)
 				}
 			}
 		}
@@ -41,40 +57,19 @@ func NormalizeRpcBlock(block RpcBlock) (*types.Block, error) {
 	return &types.Block{Number: block.Header.Level, Txs: txs}, nil
 }
 
-func NormalizeRpcTransaction(tx RpcTransaction, header RpcBlockHeader) (types.Tx, error) {
-	coin := coin.Tezos()
+func NormalizeRpcTransaction(tx RpcTransaction, header RpcBlockHeader, balance string) (types.Tx, error) {
 
-	var metadata interface{}
 	var to string
 	var txType types.TransactionType
 	switch tx.Kind {
 	case TxTypeTransaction:
 		to = tx.Destination
 		txType = types.TxTransfer
-		metadata = types.Transfer{
-			Value:    types.Amount(tx.Amount),
-			Symbol:   coin.Symbol,
-			Decimals: coin.Decimals,
-		}
 	case TxTypeDelegation:
-		var title types.KeyTitle
-		if len(tx.Delegate) == 0 {
-			title = types.AnyActionUndelegation
-		} else {
-			title = types.AnyActionDelegation
-			to = tx.Delegate
-		}
+		to = tx.Delegate
 		txType = types.TxAnyAction
-		metadata = types.AnyAction{
-			Coin:     coin.ID,
-			Title:    title,
-			Key:      types.KeyStakeDelegate,
-			Name:     coin.Name,
-			Symbol:   coin.Symbol,
-			Decimals: coin.Decimals,
-		}
 	default:
-		return types.Tx{}, errors.New("not supported operation kind: " + tx.Kind)
+		return types.Tx{}, fmt.Errorf("not supported operation kind: %s", tx.Kind)
 	}
 
 	date, err := timefmt.Parse(header.Timestamp, "%Y-%m-%dT%H:%M:%SZ")
@@ -82,15 +77,18 @@ func NormalizeRpcTransaction(tx RpcTransaction, header RpcBlockHeader) (types.Tx
 		return types.Tx{}, err
 	}
 
-	var status types.Status
-	if tx.Metadata.OperationResult.Status == TxStatusApplied {
-		status = types.StatusCompleted
-	} else {
+	metadata, err := mapMetadat(tx, balance)
+	if err != nil {
+		return types.Tx{}, err
+	}
+
+	status := types.StatusCompleted
+	if tx.Metadata.OperationResult.Status != TxStatusApplied {
 		status = types.StatusError
 	}
 
 	return types.Tx{
-		Coin:   coin.ID,
+		Coin:   coin.Tezos().ID,
 		From:   tx.Source,
 		To:     to,
 		Fee:    types.Amount(tx.Fee),
@@ -102,6 +100,38 @@ func NormalizeRpcTransaction(tx RpcTransaction, header RpcBlockHeader) (types.Tx
 	}, nil
 }
 
+func mapMetadat(tx RpcTransaction, balance string) (interface{}, error) {
+	coin := coin.Tezos()
+
+	switch tx.Kind {
+	case TxTypeTransaction:
+		return types.Transfer{
+			Value:    types.Amount(balance),
+			Symbol:   coin.Symbol,
+			Decimals: coin.Decimals,
+		}, nil
+	case TxTypeDelegation:
+		var title types.KeyTitle
+		if len(tx.Delegate) == 0 {
+			title = types.AnyActionUndelegation
+		} else {
+			title = types.AnyActionDelegation
+		}
+
+		return types.AnyAction{
+			Coin:     coin.ID,
+			Title:    title,
+			Key:      types.KeyStakeDelegate,
+			Name:     coin.Name,
+			Symbol:   coin.Symbol,
+			Decimals: coin.Decimals,
+			Value:    types.Amount(balance),
+		}, nil
+	default:
+		return nil, fmt.Errorf("not supported metadata for kind: %s", tx.Kind)
+	}
+}
+
 func mapTransaction(content interface{}) (RpcTransaction, error) {
 	bytes, err := json.Marshal(content)
 	if err != nil {
diff --git a/platform/tezos/model.go b/platform/tezos/model.go
index d90c0b7..6181eb3 100644
--- a/platform/tezos/model.go
+++ b/platform/tezos/model.go
@@ -20,6 +20,10 @@ type (
 		Delegate string `json:"delegate"`
 	}
 
+	AccountBalance struct {
+		Balance string `json:"balance"`
+	}
+
 	ExplorerAccount struct {
 		Transactions []Transaction `json:"ops"`
 	}
diff --git a/platform/tezos/rpc.go b/platform/tezos/rpc.go
index 089a9fb..b0f271f 100644
--- a/platform/tezos/rpc.go
+++ b/platform/tezos/rpc.go
@@ -2,11 +2,16 @@ package tezos
 
 import (
 	"fmt"
+	"strconv"
 	"time"
 
 	"github.com/trustwallet/golibs/client"
 )
 
+type IRpcClient interface {
+	GetAccountBalanceAtBlock(address string, block int64) (account AccountBalance, err error)
+}
+
 type RpcClient struct {
 	client.Request
 }
@@ -46,6 +51,18 @@ func (c *RpcClient) GetAccount(address string) (account Account, err error) {
 	return
 }
 
+func (c *RpcClient) GetAccountBalanceAtBlock(address string, block int64) (account AccountBalance, err error) {
+	var head string
+	if block == 0 {
+		head = "head"
+	} else {
+		head = strconv.FormatInt(block, 10)
+	}
+	path := fmt.Sprintf("chains/main/blocks/%s/context/contracts/%s", head, address)
+	err = c.Get(&account, path, nil)
+	return
+}
+
 func (c *RpcClient) fetchValidatorActivityInfo(id string) (ActivityValidatorInfo, error) {
 	var info ActivityValidatorInfo
 	path := fmt.Sprintf("/chains/main/blocks/head/context/delegates/%s", id)
diff --git a/platform/tezos/rpc_mock.go b/platform/tezos/rpc_mock.go
new file mode 100644
index 0000000..9847ca8
--- /dev/null
+++ b/platform/tezos/rpc_mock.go
@@ -0,0 +1,9 @@
+package tezos
+
+type RpcClientMock struct {
+	Balance string
+}
+
+func (r *RpcClientMock) GetAccountBalanceAtBlock(address string, block int64) (account AccountBalance, err error) {
+	return AccountBalance{Balance: r.Balance}, nil
+}
diff --git a/platform/tron/base.go b/platform/tron/base.go
index fa514ca..e6cb9b5 100644
--- a/platform/tron/base.go
+++ b/platform/tron/base.go
@@ -6,14 +6,14 @@ import (
 )
 
 type Platform struct {
-	client         Client
-	explorerClient ExplorerClient
+	client     Client
+	gridClient GridClient
 }
 
-func Init(api, explorerApi string) *Platform {
+func Init(api, gridApi string) *Platform {
 	return &Platform{
-		client:         Client{internal.InitClient(api)},
-		explorerClient: ExplorerClient{internal.InitClient(explorerApi)},
+		client:     Client{internal.InitClient(api)},
+		gridClient: GridClient{internal.InitClient(api)},
 	}
 }
 
diff --git a/platform/tron/block.go b/platform/tron/block.go
index 80995e2..3c6d289 100644
--- a/platform/tron/block.go
+++ b/platform/tron/block.go
@@ -57,7 +57,7 @@ func (p *Platform) NormalizeBlockChannel(srcTx Tx, txChan chan types.Tx) {
 	if len(transfer.AssetName) > 0 {
 		assetName, err := hex.DecodeString(transfer.AssetName[:])
 		if err == nil {
-			info, err := p.client.fetchTokenInfo(string(assetName))
+			info, err := p.gridClient.fetchTokenInfo(string(assetName))
 			if err == nil && len(info.Data) > 0 {
 				addTokenMeta(tx, srcTx, info.Data[0])
 			}
diff --git a/platform/tron/client.go b/platform/tron/client.go
index 89f6264..8667613 100644
--- a/platform/tron/client.go
+++ b/platform/tron/client.go
@@ -9,11 +9,10 @@ import (
 )
 
 type (
-	Client struct {
+	GridClient struct {
 		client.Request
 	}
-
-	ExplorerClient struct {
+	Client struct {
 		client.Request
 	}
 )
@@ -33,7 +32,7 @@ func (c *Client) fetchBlockByNumber(num int64) (Block, error) {
 	return blocks.Blocks[0], nil
 }
 
-func (c *Client) fetchTxsOfAddress(address, token string) ([]Tx, error) {
+func (c *GridClient) fetchTxsOfAddress(address, token string) ([]Tx, error) {
 	path := fmt.Sprintf("v1/accounts/%s/transactions", url.PathEscape(address))
 
 	var txs Page
@@ -46,9 +45,9 @@ func (c *Client) fetchTxsOfAddress(address, token string) ([]Tx, error) {
 	return txs.Txs, err
 }
 
-func (c *Client) fetchAccount(address string) (accounts *Account, err error) {
+func (c *GridClient) fetchAccount(address string) (accounts *Account, err error) {
 	path := fmt.Sprintf("v1/accounts/%s", address)
-	err = c.GetWithCache(&accounts, path, nil, time.Minute*1)
+	err = c.GetWithCache(&accounts, path, nil, time.Second*1)
 	return
 }
 
@@ -57,7 +56,7 @@ func (c *Client) fetchAccountVotes(address string) (account *AccountData, err er
 	return
 }
 
-func (c *Client) fetchTokenInfo(id string) (asset Asset, err error) {
+func (c *GridClient) fetchTokenInfo(id string) (asset Asset, err error) {
 	path := fmt.Sprintf("v1/assets/%s", id)
 	err = c.GetWithCache(&asset, path, nil, time.Hour*24)
 	return
@@ -81,16 +80,3 @@ func (c *Client) fetchTRC20Transactions(address string) (TRC20Transactions, erro
 	}
 	return result, nil
 }
-
-func (c *ExplorerClient) fetchAllTRC20Tokens(address string) ([]ExplorerTrc20Tokens, error) {
-	var result ExplorerResponse
-	path := "api/account"
-	err := c.GetWithCache(&result, path, url.Values{"address": {address}}, time.Minute*5)
-	if err != nil {
-		return nil, err
-	}
-	if result.ExplorerTrc20Tokens != nil {
-		return result.ExplorerTrc20Tokens, nil
-	}
-	return nil, nil
-}
diff --git a/platform/tron/mocks/tokens/tokens_response.json b/platform/tron/mocks/tokens/tokens_response.json
index 41de9ab..36caf40 100644
--- a/platform/tron/mocks/tokens/tokens_response.json
+++ b/platform/tron/mocks/tokens/tokens_response.json
@@ -1,98 +1,13 @@
 [
-  {
-    "name": "FomoThreeD",
-    "symbol": "FOM",
-    "decimals": 0,
-    "token_id": "1000542",
-    "coin": 195,
-    "type": "TRC10"
-  },
-  {
-    "name": "OtonamiS",
-    "symbol": "OS",
-    "decimals": 0,
-    "token_id": "1000567",
-    "coin": 195,
-    "type": "TRC10"
-  },
-  {
-    "name": "JUST GOV",
-    "symbol": "JST",
-    "decimals": 18,
-    "token_id": "TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "Enme Token",
-    "symbol": "EME",
-    "decimals": 6,
-    "token_id": "TCRhVHPv6efvXgogNMhiunAMXFKcMmv2pF",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "BeeHive",
-    "symbol": "BEE",
-    "decimals": 8,
-    "token_id": "TG7Z1ptC7nRkaniDVRhHSyLycaroaS5PdK",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "Mono Token",
-    "symbol": "MONO",
-    "decimals": 18,
-    "token_id": "TLKyLt4MXuvvdFvUUc3Zma4rZyj2t87Mak",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "WINK",
-    "symbol": "WIN",
-    "decimals": 6,
-    "token_id": "TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "PYRO Network",
-    "symbol": "PYRO",
-    "decimals": 6,
-    "token_id": "TMCMPzmosnQ8UAYW1zcBwjLTxDq8ce4Y5e",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "NoleCoin",
-    "symbol": "NOLE",
-    "decimals": 6,
-    "token_id": "TPt8DTDBZYfJ9fuyRjdWJr4PP68tRfptLG",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "Tether USD",
-    "symbol": "USDT",
-    "decimals": 6,
-    "token_id": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "Wuhan Fried Bats",
-    "symbol": "WUHAN",
-    "decimals": 4,
-    "token_id": "TSzjFRf8bQ46kRndWaHYKSq1P5HzRJfPvr",
-    "coin": 195,
-    "type": "TRC20"
-  },
-  {
-    "name": "HelGro",
-    "symbol": "HGRO",
-    "decimals": 6,
-    "token_id": "TTvVC9jv5AfDdHuGeCMcQg6QftmNnfQiVm",
-    "coin": 195,
-    "type": "TRC20"
-  }
+  "c195_t1000567",
+  "c195_tTCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9",
+  "c195_tTCRhVHPv6efvXgogNMhiunAMXFKcMmv2pF",
+  "c195_tTG7Z1ptC7nRkaniDVRhHSyLycaroaS5PdK",
+  "c195_tTJSF4iVkzkRkYwEVNkqJkeGDaZpnFbGy9x",
+  "c195_tTLKyLt4MXuvvdFvUUc3Zma4rZyj2t87Mak",
+  "c195_tTLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7",
+  "c195_tTMCMPzmosnQ8UAYW1zcBwjLTxDq8ce4Y5e",
+  "c195_tTPt8DTDBZYfJ9fuyRjdWJr4PP68tRfptLG",
+  "c195_tTR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
+  "c195_tTTvVC9jv5AfDdHuGeCMcQg6QftmNnfQiVm"
 ]
diff --git a/platform/tron/stake.go b/platform/tron/stake.go
index 54e276d..35dff0d 100644
--- a/platform/tron/stake.go
+++ b/platform/tron/stake.go
@@ -70,7 +70,7 @@ func (p *Platform) GetDelegations(address string) (blockatlas.DelegationsPage, e
 }
 
 func (p *Platform) UndelegatedBalance(address string) (string, error) {
-	account, err := p.client.fetchAccount(address)
+	account, err := p.gridClient.fetchAccount(address)
 	if err != nil {
 		return "0", err
 	}
diff --git a/platform/tron/token.go b/platform/tron/token.go
index 8d6bd60..9c19291 100644
--- a/platform/tron/token.go
+++ b/platform/tron/token.go
@@ -1,86 +1,32 @@
 package tron
 
 import (
-	"strconv"
-	"strings"
-	"sync"
-
-	log "github.com/sirupsen/logrus"
-	"github.com/trustwallet/golibs/coin"
-	"github.com/trustwallet/golibs/types"
+	"github.com/trustwallet/golibs/asset"
 )
 
-func (p *Platform) GetTokenListByAddress(address string) (types.TokenPage, error) {
-	tokens, err := p.client.fetchAccount(address)
+func (p *Platform) GetTokenListByAddress(address string) ([]string, error) {
+	assetIds := make([]string, 0)
+	tokens, err := p.gridClient.fetchAccount(address)
 	if err != nil {
-		return nil, err
+		return assetIds, err
 	}
-	tokenPage := make(types.TokenPage, 0)
 	if len(tokens.Data) == 0 {
-		return tokenPage, nil
+		return assetIds, nil
 	}
+	data := tokens.Data[0]
 
 	var tokenIds []string
-	for _, v := range tokens.Data[0].AssetsV2 {
-		tokenIds = append(tokenIds, v.Key)
-	}
-
-	tokensChan := p.getTokens(tokenIds)
-	for info := range tokensChan {
-		tokenPage = append(tokenPage, info)
+	for _, v := range data.AssetsV2 {
+		tokenIds = append(assetIds, v.Key)
 	}
-
-	trc20Tokens, err := p.explorerClient.fetchAllTRC20Tokens(address)
-	if err != nil {
-		log.Error("Explorer error" + err.Error())
+	for _, trc20Tokens := range data.Trc20 {
+		for key := range trc20Tokens {
+			tokenIds = append(tokenIds, key)
+		}
 	}
-
-	for _, t := range trc20Tokens {
-		tokenPage = append(tokenPage, types.Token{
-			Name:     t.Name,
-			Symbol:   strings.ToUpper(t.Symbol),
-			Decimals: uint(t.Decimals),
-			TokenID:  t.ContractAddress,
-			Coin:     coin.Tron().ID,
-			Type:     types.TRC20,
-		})
+	for _, tokenId := range tokenIds {
+		assetIds = append(assetIds, asset.BuildID(p.Coin().ID, tokenId))
 	}
 
-	return tokenPage, nil
-}
-
-func (p *Platform) getTokens(ids []string) chan types.Token {
-	tkChan := make(chan types.Token, len(ids))
-	var wg sync.WaitGroup
-	for _, id := range ids {
-		wg.Add(1)
-		go func(i string, c chan types.Token) {
-			defer wg.Done()
-			_ = p.getTokensChannel(i, c)
-		}(id, tkChan)
-	}
-	wg.Wait()
-	close(tkChan)
-	return tkChan
-}
-
-func (p *Platform) getTokensChannel(id string, tkChan chan types.Token) error {
-	info, err := p.client.fetchTokenInfo(id)
-	if err != nil || len(info.Data) == 0 {
-		return err
-	}
-	asset := NormalizeToken(info.Data[0])
-	tkChan <- asset
-	return nil
-}
-
-func NormalizeToken(info AssetInfo) types.Token {
-	return types.Token{
-		Name:     info.Name,
-		Symbol:   strings.ToUpper(info.Symbol),
-		TokenID:  strconv.Itoa(int(info.ID)),
-		Coin:     coin.TRX,
-		Decimals: info.Decimals,
-		Type:     types.TRC10,
-	}
+	return assetIds, nil
 }
diff --git a/platform/tron/transaction.go b/platform/tron/transaction.go
index af8eb10..8ee82b7 100644
--- a/platform/tron/transaction.go
+++ b/platform/tron/transaction.go
@@ -11,7 +11,7 @@ import (
 )
 
 func (p *Platform) GetTxsByAddress(address string) (types.TxPage, error) {
-	Txs, err := p.client.fetchTxsOfAddress(address, "")
+	Txs, err := p.gridClient.fetchTxsOfAddress(address, "")
 	if err != nil && len(Txs) == 0 {
 		return nil, err
 	}
@@ -80,12 +80,12 @@ func addTokenMeta(tx *types.Tx, srcTx Tx, tokenInfo AssetInfo) {
 func (p *Platform) fetchTransactionsForTRC10Tokens(address, token string) (types.TxPage, error) {
 	txs := make(types.TxPage, 0)
 
-	tokenTxs, err := p.client.fetchTxsOfAddress(address, token)
+	tokenTxs, err := p.gridClient.fetchTxsOfAddress(address, token)
 	if err != nil {
 		return nil, err
 	}
 
-	info, err := p.client.fetchTokenInfo(token)
+	info, err := p.gridClient.fetchTokenInfo(token)
 	if err != nil {
 		return nil, err
 	}
diff --git a/platform/vechain/mocks/transfer_receipt.json b/platform/vechain/mocks/incoming_vtho_receipt.json
similarity index 54%
rename from platform/vechain/mocks/transfer_receipt.json
rename to platform/vechain/mocks/incoming_vtho_receipt.json
index a134f13..6e18c6b 100644
--- a/platform/vechain/mocks/transfer_receipt.json
+++ b/platform/vechain/mocks/incoming_vtho_receipt.json
@@ -1,15 +1,15 @@
 {
   "gasUsed": 36582,
-  "gasPayer": "0x2c7a8d5cce0d5e6a8a31233b7dc3dae9aae4b405",
+  "gasPayer": "0xb5e883349e68ab59307d1604555ac890fac47128",
   "paid": "0x1fbad5f2e25570000",
   "reward": "0x984d9c8dd8008000",
   "reverted": false,
   "meta": {
-    "blockID": "0x0042e02cebd1bec003d31526dba338c1b9eeeefdef722fb147e9d31690fbff1e",
-    "blockNumber": 4382764,
-    "blockTimestamp": 1574278180,
-    "txID": "0x42f5eba46ddcc458243c753545a3faa849502d078efbc5b74baddea9e6ea5b04",
-    "txOrigin": "0x2c7a8d5cce0d5e6a8a31233b7dc3dae9aae4b405"
+    "blockID": "0x007ac4b1a18c0fc3a11f44285d6a42eff591a99312d303c105a4f4de7fa10575",
+    "blockNumber": 8045745,
+    "blockTimestamp": 1610958460,
+    "txID": "0xb356fa7b3a371f1518a5f9bc51e951d0dac2ef04d58b532c7ca50a52aa5cddb4",
+    "txOrigin": "0xb5e883349e68ab59307d1604555ac890fac47128"
   },
   "outputs": [
     {
@@ -19,10 +19,10 @@
           "address": "0x0000000000000000000000000000456e65726779",
           "topics": [
             "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
-            "0x0000000000000000000000002c7a8d5cce0d5e6a8a31233b7dc3dae9aae4b405",
-            "0x000000000000000000000000b5e883349e68ab59307d1604555ac890fac47128"
+            "0x000000000000000000000000b5e883349e68ab59307d1604555ac890fac47128",
+            "0x000000000000000000000000e99399dd211ef54c301a5d1aa813471d92122ea8"
           ],
-          "data": "0x000000000000000000000000000000000000000000000003afb087b876900000"
+          "data": "0x00000000000000000000000000000000000000000000003635c9adc5dea00000"
         }
       ],
       "transfers": []
diff --git a/platform/vechain/mocks/incoming_vtho_tx.json b/platform/vechain/mocks/incoming_vtho_tx.json
new file mode 100644
index 0000000..93c7737
--- /dev/null
+++ b/platform/vechain/mocks/incoming_vtho_tx.json
@@ -0,0 +1,25 @@
+{
+  "id": "0xb356fa7b3a371f1518a5f9bc51e951d0dac2ef04d58b532c7ca50a52aa5cddb4",
+  "chainTag": 74,
+  "blockRef": "0x007ac4b0b0c47a00",
+  "expiration": 720,
+  "clauses": [
+    {
+      "to": "0x0000000000000000000000000000456e65726779",
+      "value": "0x0",
+      "data": "0xa9059cbb000000000000000000000000e99399dd211ef54c301a5d1aa813471d92122ea800000000000000000000000000000000000000000000003635c9adc5dea00000"
+    }
+  ],
+  "gasPriceCoef": 0,
+  "gas": 43245,
+  "origin": "0xb5e883349e68ab59307d1604555ac890fac47128",
+  "delegator": null,
+  "nonce": "0x2a2e2da5",
+  "dependsOn": null,
+  "size": 188,
+  "meta": {
+    "blockID": "0x007ac4b1a18c0fc3a11f44285d6a42eff591a99312d303c105a4f4de7fa10575",
+    "blockNumber": 8045745,
+    "blockTimestamp": 1610958460
+  }
+}
diff --git a/platform/vechain/mocks/outgoing_vtho_receipt.json b/platform/vechain/mocks/outgoing_vtho_receipt.json
new file mode 100644
index 0000000..72b8627
--- /dev/null
+++ b/platform/vechain/mocks/outgoing_vtho_receipt.json
@@ -0,0 +1,31 @@
+{
+  "gasUsed": 36518,
+  "gasPayer": "0xe99399dd211ef54c301a5d1aa813471d92122ea8",
+  "paid": "0x1fac9ff84f3b70000",
+  "reward": "0x980966417c508000",
+  "reverted": false,
+  "meta": {
+    "blockID": "0x007ac4bc8090b67c3e3c395e5e986226eab3a8619250807cf32b0c247f6d513c",
+    "blockNumber": 8045756,
+    "blockTimestamp": 1610958570,
+    "txID": "0x0677f91de4787d295087acec0a7ba317b0019fbf296fed630fdb5afbfca97a58",
+    "txOrigin": "0xe99399dd211ef54c301a5d1aa813471d92122ea8"
+  },
+  "outputs": [
+    {
+      "contractAddress": null,
+      "events": [
+        {
+          "address": "0x0000000000000000000000000000456e65726779",
+          "topics": [
+            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
+            "0x000000000000000000000000e99399dd211ef54c301a5d1aa813471d92122ea8",
+            "0x000000000000000000000000b5e883349e68ab59307d1604555ac890fac47128"
+          ],
+          "data": "0x0000000000000000000000000000000000000000000000006124fee993bc0000"
+        }
+      ],
+      "transfers": []
+    }
+  ]
+}
diff --git a/platform/vechain/mocks/outgoing_vtho_tx.json b/platform/vechain/mocks/outgoing_vtho_tx.json
new file mode 100644
index 0000000..93a05cb
--- /dev/null
+++ b/platform/vechain/mocks/outgoing_vtho_tx.json
@@ -0,0 +1,25 @@
+{
+  "id": "0x0677f91de4787d295087acec0a7ba317b0019fbf296fed630fdb5afbfca97a58",
+  "chainTag": 74,
+  "blockRef": "0x007ac4babac47a00",
+  "expiration": 720,
+  "clauses": [
+    {
+      "to": "0x0000000000000000000000000000456e65726779",
+      "value": "0x0",
+      "data": "0xa9059cbb000000000000000000000000b5e883349e68ab59307d1604555ac890fac471280000000000000000000000000000000000000000000000006124fee993bc0000"
+    }
+  ],
+  "gasPriceCoef": 0,
+  "gas": 43181,
+  "origin": "0xe99399dd211ef54c301a5d1aa813471d92122ea8",
+  "delegator": null,
+  "nonce": "0x2bb08067",
+  "dependsOn": null,
+  "size": 188,
+  "meta": {
+    "blockID": "0x007ac4bc8090b67c3e3c395e5e986226eab3a8619250807cf32b0c247f6d513c",
+    "blockNumber": 8045756,
+    "blockTimestamp": 1610958570
+  }
+}
diff --git a/platform/vechain/mocks/transfer_log.json b/platform/vechain/mocks/transfer_log.json
deleted file mode 100644
index e66c5e7..0000000
--- a/platform/vechain/mocks/transfer_log.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-  "id": "0x42f5eba46ddcc458243c753545a3faa849502d078efbc5b74baddea9e6ea5b04",
-  "chainTag": 74,
-  "blockRef": "0x0042e02a2ae04200",
-  "expiration": 720,
-  "clauses": [
-    {
-      "to": "0x0000000000000000000000000000456e65726779",
-      "value": "0x0",
-      "data": "0xa9059cbb000000000000000000000000b5e883349e68ab59307d1604555ac890fac47128000000000000000000000000000000000000000000000003afb087b876900000"
-    }
-  ],
-  "gasPriceCoef": 0,
-  "gas": 80000,
-  "origin": "0x2c7a8d5cce0d5e6a8a31233b7dc3dae9aae4b405",
-  "delegator": null,
-  "nonce": "0x4a8569d",
-  "dependsOn": null,
-  "size": 189,
-  "meta": {
-    "blockID": "0x0042e02cebd1bec003d31526dba338c1b9eeeefdef722fb147e9d31690fbff1e",
-    "blockNumber": 4382764,
-    "blockTimestamp": 1574278180
-  }
-}
diff --git a/platform/vechain/transaction.go b/platform/vechain/transaction.go
index 5e7e9a0..2272bac 100644
--- a/platform/vechain/transaction.go
+++ b/platform/vechain/transaction.go
@@ -34,6 +34,12 @@ func (p *Platform) GetTokenTxsByAddress(address, token string) (types.TxPage, er
 	for t := range cTxs {
 		txs = append(txs, t...)
 	}
+
+	// NormalizeTokenTransaction won't set tx direction anymore, set it here
+	for _, tx := range txs {
+		updateTransactionDirection(&tx, address)
+	}
+
 	return txs, nil
 }
 
@@ -130,31 +136,22 @@ func (p *Platform) NormalizeTokenTransaction(srcTx Tx, receipt TxReceipt) (types
 		if err != nil {
 			continue
 		}
-		topicsFrom, err := address.EIP55Checksum(getRecipientAddress(event.Topics[1]))
-		if err != nil {
-			continue
-		}
-		topicsTo, err := address.EIP55Checksum(getRecipientAddress(event.Topics[2]))
-		if err != nil {
-			continue
-		}
 
-		direction, err := getTokenTransactionDirectory(originSender, topicsFrom, topicsTo)
+		topicsTo, err := address.EIP55Checksum(getRecipientAddress(event.Topics[2]))
 		if err != nil {
 			continue
 		}
 
 		txs = append(txs, types.Tx{
-			ID:        srcTx.Id,
-			Coin:      p.Coin().ID,
-			From:      originSender,
-			To:        originReceiver,
-			Fee:       types.Amount(fee),
-			Date:      srcTx.Meta.BlockTimestamp,
-			Type:      types.TxTokenTransfer,
-			Block:     srcTx.Meta.BlockNumber,
-			Status:    types.StatusCompleted,
-			Direction: direction,
+			ID:     srcTx.Id,
+			Coin:   p.Coin().ID,
+			From:   originSender,
+			To:     originReceiver,
+			Fee:    types.Amount(fee),
+			Date:   srcTx.Meta.BlockTimestamp,
+			Type:   types.TxTokenTransfer,
+			Block:  srcTx.Meta.BlockNumber,
+			Status: types.StatusCompleted,
 			Meta: types.TokenTransfer{
 				// the only supported Token on VeChain is its Gas token
 				Name:     gasTokenName,
@@ -215,7 +212,7 @@ func (p *Platform) NormalizeTransaction(srcTx LogTransfer, trxId Tx, addr string
 		return
 	}
 
-	directory, err := getTransferDirectory(sender, recipient, addrChecksum)
+	direction, err := getTransactionDirection(sender, recipient, addrChecksum)
 	if err != nil {
 		return types.Tx{}, err
 	}
@@ -229,7 +226,7 @@ func (p *Platform) NormalizeTransaction(srcTx LogTransfer, trxId Tx, addr string
 		Date:      srcTx.Meta.BlockTimestamp,
 		Type:      types.TxTransfer,
 		Block:     srcTx.Meta.BlockNumber,
-		Direction: directory,
+		Direction: direction,
 		Status:    types.StatusCompleted,
 		Meta: types.Transfer{
 			Value:    types.Amount(value),
@@ -253,28 +250,28 @@ func getRecipientAddress(hex string) string {
 	return "0x" + hex[len(hex)-40:]
 }
 
-func getTokenTransactionDirectory(originSender, topicsFrom, topicsTo string) (dir types.Direction, err error) {
-	if originSender == topicsFrom && originSender == topicsTo {
-		return types.DirectionSelf, nil
-	}
-	if originSender == topicsFrom && originSender != topicsTo {
-		return types.DirectionIncoming, nil
+func updateTransactionDirection(tx *types.Tx, addr string) {
+	meta, ok := tx.Meta.(types.TokenTransfer)
+	if !ok {
+		return
 	}
-	if originSender == topicsTo && originSender != topicsFrom {
-		return types.DirectionOutgoing, nil
+	direction, err := getTransactionDirection(tx.From, meta.To, addr)
+	if err != nil {
+		return
 	}
-	return "", errors.New("Unknown direction")
+	tx.Direction = direction
 }
 
-func getTransferDirectory(sender, recipient, addr string) (dir types.Direction, err error) {
+func getTransactionDirection(sender, recipient, addr string) (types.Direction, error) {
+	if sender != addr && recipient != addr {
+		return "", errors.New("Unknown direction")
+	}
 	if sender == addr && recipient == addr {
 		return types.DirectionSelf, nil
 	}
-	if sender == addr && recipient != addr {
+	if sender == addr {
 		return types.DirectionOutgoing, nil
-	}
-	if recipient == addr && sender != addr {
+	} else {
 		return types.DirectionIncoming, nil
 	}
-	return "", errors.New("Unknown direction")
 }
diff --git a/services/notifier/base.go b/services/notifier/base.go
index 872dd3b..a942af3 100644
--- a/services/notifier/base.go
+++ b/services/notifier/base.go
@@ -7,6 +7,7 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/streadway/amqp"
 	"github.com/trustwallet/blockatlas/db"
+	"github.com/trustwallet/golibs/types"
 )
 
 const (
@@ -14,23 +15,23 @@ const (
 )
 
 func RunNotifier(database *db.Instance, delivery amqp.Delivery) error {
-	txs, err := GetTransactionsFromDelivery(delivery, Notifier)
+	transactions, err := GetTransactionsFromDelivery(delivery, Notifier)
 	if err != nil {
-		log.WithFields(log.Fields{"service": Notifier, "txs": txs}).Error("failed to get transactions: ", err)
-		return err
+		log.WithFields(log.Fields{"service": Notifier, "body": string(delivery.Body), "error": err}).Error("Unable to unmarshal MQ Message")
+		return nil
 	}
 
 	allAddresses := make([]string, 0)
-	for _, tx := range txs {
+	for _, tx := range transactions {
 		allAddresses = append(allAddresses, tx.GetAddresses()...)
 	}
 
 	addresses := ToUniqueAddresses(allAddresses)
 	for i := range addresses {
-		addresses[i] = strconv.Itoa(int(txs[0].Coin)) + "_" + addresses[i]
+		addresses[i] = strconv.Itoa(int(transactions[0].Coin)) + "_" + addresses[i]
 	}
 
-	if len(txs) == 0 {
+	if len(transactions) == 0 {
 		return nil
 	}
 	subscriptions, err := database.GetSubscriptions(addresses)
@@ -38,13 +39,13 @@ func RunNotifier(database *db.Instance, delivery amqp.Delivery) error {
 		return nil
 	}
 
-	notifications := make([]TransactionNotification, 0)
+	notifications := make([]types.TransactionNotification, 0)
 	for _, sub := range subscriptions {
 		ua, _, ok := UnprefixedAddress(sub.Address)
 		if !ok {
 			continue
 		}
-		notificationsForAddress := buildNotificationsByAddress(ua, txs)
+		notificationsForAddress := buildNotificationsByAddress(ua, transactions)
 		notifications = append(notifications, notificationsForAddress...)
 	}
 
diff --git a/services/notifier/delivery.go b/services/notifier/delivery.go
index e61ddbd..4a5d3e5 100644
--- a/services/notifier/delivery.go
+++ b/services/notifier/delivery.go
@@ -22,7 +22,7 @@ func GetTransactionsFromDelivery(delivery amqp.Delivery, service string) (types.
 	return transactions, nil
 }
 
-func publishNotifications(notifications []TransactionNotification) error {
+func publishNotifications(notifications []types.TransactionNotification) error {
 	raw, err := json.Marshal(notifications)
 	if err != nil {
 		return err
diff --git a/services/notifier/models.go b/services/notifier/models.go
index c3d4794..37edf66 100644
--- a/services/notifier/models.go
+++ b/services/notifier/models.go
@@ -2,19 +2,14 @@ package notifier
 
 import "github.com/trustwallet/golibs/types"
 
-type TransactionNotification struct {
-	Action types.TransactionType `json:"action"`
-	Result types.Tx              `json:"result"`
-}
-
-func buildNotificationsByAddress(address string, txs types.Txs) []TransactionNotification {
+func buildNotificationsByAddress(address string, txs types.Txs) []types.TransactionNotification {
 	transactionsByAddress := toUniqueTransactions(findTransactionsByAddress(txs, address))
 
-	result := make([]TransactionNotification, 0, len(transactionsByAddress))
+	result := make([]types.TransactionNotification, 0, len(transactionsByAddress))
 	for _, tx := range transactionsByAddress {
 		tx.Direction = tx.GetTransactionDirection(address)
 		tx.InferUtxoValue(address, tx.Coin)
-		result = append(result, TransactionNotification{Action: tx.Type, Result: tx})
+		result = append(result, types.TransactionNotification{Action: tx.Type, Result: tx})
 	}
 
 	return result
diff --git a/services/parser/parser.go b/services/parser/parser.go
index aa255e7..e37a44a 100644
--- a/services/parser/parser.go
+++ b/services/parser/parser.go
@@ -262,15 +262,15 @@ func SaveLastParsedBlock(params Params, blocks []types.Block) error {
 	return nil
 }
 
-func publish(params Params, txs types.Txs) error {
+func publish(params Params, transactions types.Txs) error {
 
-	if len(txs) == 0 {
+	if len(transactions) == 0 {
 		return nil
 	}
 
-	body, err := json.Marshal(txs)
+	body, err := json.Marshal(transactions)
 	if err != nil {
-		log.WithFields(log.Fields{"operation": "publish marshal", "coin": params.Api.Coin().Handle}).Error(err)
+		log.WithFields(log.Fields{"operation": "publish marshal", "transactions": transactions, "coin": params.Api.Coin().Handle}).Error(err)
 		return err
 	}
 	return params.TransactionsExchange.Publish(body)
diff --git a/services/subscriber/subscriber.go b/services/subscriber/subscriber.go
index 84e8844..733ae36 100644
--- a/services/subscriber/subscriber.go
+++ b/services/subscriber/subscriber.go
@@ -8,35 +8,27 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/streadway/amqp"
 	"github.com/trustwallet/blockatlas/db"
-	"github.com/trustwallet/blockatlas/pkg/blockatlas"
-)
-
-type Subscriber string
-
-const (
-	Notifications      Subscriber                       = "notifications"
-	AddSubscription    blockatlas.SubscriptionOperation = "AddSubscription"
-	DeleteSubscription blockatlas.SubscriptionOperation = "DeleteSubscription"
+	"github.com/trustwallet/golibs/types"
 )
 
 func RunSubscriber(database *db.Instance, delivery amqp.Delivery) error {
-	var event blockatlas.SubscriptionEvent
+	var event types.SubscriptionEvent
 	err := json.Unmarshal(delivery.Body, &event)
 	if err != nil {
-		log.WithFields(log.Fields{"service": Notifications, "event": event}).Error(err)
-		return err
+		log.WithFields(log.Fields{"service": types.Notifications, "body": string(delivery.Body), "error": err}).Error("Unable to unmarshal MQ Message")
+		return nil
 	}
 
 	subscriptions := event.ParseSubscriptions(event.Subscriptions)
 	switch event.Operation {
-	case AddSubscription:
+	case types.AddSubscription:
 		err := database.CreateSubscriptions(subscriptions)
 		if err != nil {
-			log.WithFields(log.Fields{"service": Notifications, "operation": event.Operation, "subscriptions": subscriptions}).Error(err)
+			log.WithFields(log.Fields{"service": types.Notifications, "operation": event.Operation, "subscriptions": subscriptions}).Error(err)
 			return err
 		}
-		log.WithFields(log.Fields{"service": Notifications, "operation": event.Operation, "subscriptions": len(subscriptions)}).Info("Add subscriptions")
-	case DeleteSubscription:
+		log.WithFields(log.Fields{"service": types.Notifications, "operation": event.Operation, "subscriptions": len(subscriptions)}).Info("Add subscriptions")
+	case types.DeleteSubscription:
 		subscriptionsIds := make([]string, 0)
 		for _, subscription := range subscriptions {
 			subscriptionsIds = append(subscriptionsIds, subscription.AddressID())
diff --git a/services/tokenindexer/api.go b/services/tokenindexer/api.go
index 551a4c8..ff6d0c4 100644
--- a/services/tokenindexer/api.go
+++ b/services/tokenindexer/api.go
@@ -3,10 +3,9 @@ package tokenindexer
 import (
 	"time"
 
-	"github.com/trustwallet/blockatlas/pkg/blockatlas"
-
 	"github.com/trustwallet/blockatlas/db"
 	"github.com/trustwallet/blockatlas/db/models"
+	"github.com/trustwallet/golibs/types"
 )
 
 type Instance struct {
@@ -31,7 +30,7 @@ func (i Instance) GetTokensByAddress(r GetTokensByAddressRequest) (GetTokensByAd
 
 	for coin, coins := range r.AddressesByCoin {
 		for _, address := range coins {
-			list = append(list, blockatlas.GetAddressID(coin, address))
+			list = append(list, types.GetAddressID(coin, address))
 		}
 	}
 	from := time.Unix(int64(r.From), 0)
diff --git a/services/tokenindexer/indexer.go b/services/tokenindexer/indexer.go
index d3d70fb..44c2c97 100644
--- a/services/tokenindexer/indexer.go
+++ b/services/tokenindexer/indexer.go
@@ -17,23 +17,23 @@ const (
 )
 
 func RunTokenIndexer(database *db.Instance, delivery amqp.Delivery) error {
-	txs, err := notifier.GetTransactionsFromDelivery(delivery, TokenIndexer)
+	transactions, err := notifier.GetTransactionsFromDelivery(delivery, TokenIndexer)
 	if err != nil {
-		log.WithFields(log.Fields{"service": TokenIndexer, "txs": txs}).Error("failed to get transactions: ", err)
-		return err
+		log.WithFields(log.Fields{"service": TokenIndexer, "body": string(delivery.Body), "error": err}).Error("Unable to unmarshal MQ Message")
+		return nil
 	}
-	txs = txs.FilterTransactionsByType([]types.TransactionType{
+	transactions = transactions.FilterTransactionsByType([]types.TransactionType{
 		types.TxTokenTransfer,
 		types.TxNativeTokenTransfer,
 	})
 
-	if len(txs) == 0 {
+	if len(transactions) == 0 {
 		return nil
 	}
 
 	// Add new assets to db
 
-	assets := GetAssetsFromTransactions(txs)
+	assets := GetAssetsFromTransactions(transactions)
 	err = database.AddNewAssets(assets)
 	if err != nil {
 		log.WithFields(log.Fields{"service": TokenIndexer}).Error("Failed to add new assets", err)
@@ -41,7 +41,7 @@ func RunTokenIndexer(database *db.Instance, delivery amqp.Delivery) error {
 	}
 
 	// Add asset <> address association
-	addressAssetsMap := assetsMap(txs)
+	addressAssetsMap := assetsMap(transactions)
 
 	return CreateAssociations(database, addressAssetsMap)
 }
diff --git a/services/tokenindexer/indexer_subscribe.go b/services/tokenindexer/indexer_subscribe.go
index 26cec46..bb87414 100644
--- a/services/tokenindexer/indexer_subscribe.go
+++ b/services/tokenindexer/indexer_subscribe.go
@@ -7,30 +7,37 @@ import (
 	"github.com/streadway/amqp"
 	"github.com/trustwallet/blockatlas/db"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
-	"github.com/trustwallet/blockatlas/services/subscriber"
+	"github.com/trustwallet/golibs/types"
 )
 
 type ConsumerIndexer struct {
 	Database   *db.Instance
 	TokensAPIs map[uint]blockatlas.TokensAPI
 	Delivery   func(*db.Instance, map[uint]blockatlas.TokensAPI, amqp.Delivery) error
+	Tag        string
 }
 
 func (c ConsumerIndexer) Callback(msg amqp.Delivery) error {
 	return c.Delivery(c.Database, c.TokensAPIs, msg)
 }
 
+func (c ConsumerIndexer) ConsumerTag() string {
+	return c.Tag
+}
+
 func RunTokenIndexerSubscribe(database *db.Instance, apis map[uint]blockatlas.TokensAPI, delivery amqp.Delivery) error {
-	var event blockatlas.SubscriptionEvent
+	var event types.SubscriptionEvent
 	err := json.Unmarshal(delivery.Body, &event)
 	if err != nil {
-		log.WithFields(log.Fields{"service": SubscriptionsTokenIndexer, "event": event}).Error(err)
-		return err
+		log.WithFields(log.Fields{"service": SubscriptionsTokenIndexer, "body": string(delivery.Body), "error": err}).Error("Unable to unmarshal MQ Message")
+		return nil
 	}
 
+	log.WithFields(log.Fields{"service": TokenIndexer, "event": event.Operation, "subscriptions": len(event.Subscriptions)}).Info("Processing")
+
 	subscriptions := event.ParseSubscriptions(event.Subscriptions)
 	switch event.Operation {
-	case subscriber.AddSubscription:
+	case types.AddSubscription:
 		addressAssetsMap := map[string][]string{}
 
 		for _, coinAddress := range subscriptions {
@@ -38,18 +45,14 @@ func RunTokenIndexerSubscribe(database *db.Instance, apis map[uint]blockatlas.To
 			if !ok {
 				continue
 			}
-			assets, err := api.GetTokenListByAddress(coinAddress.Address)
+			assetIds, err := api.GetTokenListByAddress(coinAddress.Address)
 			if err != nil {
 				continue
 			}
-			assetIds := make([]string, 0)
-			for _, asset := range assets {
-				assetIds = append(assetIds, asset.AssetId())
-			}
 			addressAssetsMap[coinAddress.AddressID()] = assetIds
 		}
 		return CreateAssociations(database, addressAssetsMap)
-	case subscriber.DeleteSubscription:
+	case types.DeleteSubscription:
 		//No action is needed
 		return nil
 	}


Test patch:
diff --git a/pkg/blockatlas/observer_test.go b/pkg/blockatlas/observer_test.go
deleted file mode 100644
index f61e2ab..0000000
--- a/pkg/blockatlas/observer_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package blockatlas
-
-import (
-	"sort"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func Test_parseSubscriptions(t *testing.T) {
-	tests := []struct {
-		name          string
-		subscriptions SubscriptionEvent
-		wantSubs      []Subscription
-	}{
-		{
-			name: "guid with 1 coin",
-			subscriptions: SubscriptionEvent{
-				Subscriptions: Subscriptions{
-					"0": {"xpub6BpYi6J1GZzfY3yY7DbhLLccF3efQa18nQngM3jaehgtNSoEgk6UtPULpC3oK5oA3trczY8Ld34LFw1USMPfGHwTEizdD5QyGcMyuh2UoBA", "xpub6CYwPfnPJLPquufPkb98coSb3mdy1CgaZrWUtYWGJTJ4VWZUbzH9HLGy7nHpP7DG4UdTkYYpirkTWQSP7pWHsrk24Nos5oYNHpfr4BgPVTL"},
-				},
-			},
-			wantSubs: []Subscription{
-				{
-					Coin:    0,
-					Address: "xpub6BpYi6J1GZzfY3yY7DbhLLccF3efQa18nQngM3jaehgtNSoEgk6UtPULpC3oK5oA3trczY8Ld34LFw1USMPfGHwTEizdD5QyGcMyuh2UoBA",
-				},
-				{
-					Coin:    0,
-					Address: "xpub6CYwPfnPJLPquufPkb98coSb3mdy1CgaZrWUtYWGJTJ4VWZUbzH9HLGy7nHpP7DG4UdTkYYpirkTWQSP7pWHsrk24Nos5oYNHpfr4BgPVTL",
-				},
-			},
-		},
-		{
-			name: "guid with 2 coins",
-			subscriptions: SubscriptionEvent{
-				Subscriptions: Subscriptions{
-					"2": {"zpub6rH4MwgyTmuexAX6HAraks5cKv5BbtmwdLirvnU5845ovUJb4abgjt9DtXK4ZEaToRrNj8dQznuLC6Nka4eMviGMinCVMUxKLpuyddcG9Vc"},
-					"0": {"xpub6BpYi6J1GZzfY3yY7DbhLLccF3efQa18nQngM3jaehgtNSoEgk6UtPULpC3oK5oA3trczY8Ld34LFw1USMPfGHwTEizdD5QyGcMyuh2UoBA", "xpub6CYwPfnPJLPquufPkb98coSb3mdy1CgaZrWUtYWGJTJ4VWZUbzH9HLGy7nHpP7DG4UdTkYYpirkTWQSP7pWHsrk24Nos5oYNHpfr4BgPVTL"},
-				},
-			},
-			wantSubs: []Subscription{
-				{
-					Coin:    2,
-					Address: "zpub6rH4MwgyTmuexAX6HAraks5cKv5BbtmwdLirvnU5845ovUJb4abgjt9DtXK4ZEaToRrNj8dQznuLC6Nka4eMviGMinCVMUxKLpuyddcG9Vc",
-				},
-				{
-					Coin:    0,
-					Address: "xpub6BpYi6J1GZzfY3yY7DbhLLccF3efQa18nQngM3jaehgtNSoEgk6UtPULpC3oK5oA3trczY8Ld34LFw1USMPfGHwTEizdD5QyGcMyuh2UoBA",
-				},
-				{
-					Coin:    0,
-					Address: "xpub6CYwPfnPJLPquufPkb98coSb3mdy1CgaZrWUtYWGJTJ4VWZUbzH9HLGy7nHpP7DG4UdTkYYpirkTWQSP7pWHsrk24Nos5oYNHpfr4BgPVTL",
-				},
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			gotSubs := tt.subscriptions.ParseSubscriptions(tt.subscriptions.Subscriptions)
-			sort.Slice(gotSubs, func(i, j int) bool {
-				return gotSubs[i].Coin > gotSubs[j].Coin
-			})
-			sort.Slice(tt.wantSubs, func(i, j int) bool {
-				return tt.wantSubs[i].Coin > tt.wantSubs[j].Coin
-			})
-			assert.EqualValues(t, tt.wantSubs, gotSubs)
-		})
-	}
-}
diff --git a/platform/bitcoin/blockbook/token_test.go b/platform/bitcoin/blockbook/token_test.go
index 560bd44..a1a1ed0 100644
--- a/platform/bitcoin/blockbook/token_test.go
+++ b/platform/bitcoin/blockbook/token_test.go
@@ -3,8 +3,6 @@ package blockbook
 import (
 	"reflect"
 	"testing"
-
-	"github.com/trustwallet/golibs/types"
 )
 
 func TestNormalizeToken(t *testing.T) {
@@ -15,7 +13,7 @@ func TestNormalizeToken(t *testing.T) {
 	tests := []struct {
 		name string
 		args args
-		want []types.Token
+		want []string
 	}{
 		{
 			name: "Should normalize and return token with balance",
@@ -27,12 +25,7 @@ func TestNormalizeToken(t *testing.T) {
 				Symbol:   "USDC",
 				Decimals: 6},
 				coinIndex: 60},
-			want: []types.Token{
-				{
-					Type:    "ERC20",
-					Name:    "USD//C",
-					TokenID: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
-					Symbol:  "USDC", Decimals: 6, Coin: 60}},
+			want: []string{"c60_t0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"},
 		},
 		{
 			name: "Should not return token with zero balance",
@@ -44,7 +37,7 @@ func TestNormalizeToken(t *testing.T) {
 				Symbol:   "USDC",
 				Decimals: 6},
 				coinIndex: 60},
-			want: []types.Token{},
+			want: []string{},
 		}, {
 			name: "Should not return token with zero balance",
 			args: args{srcToken: Token{
@@ -55,7 +48,7 @@ func TestNormalizeToken(t *testing.T) {
 				Symbol:   "USDC",
 				Decimals: 6},
 				coinIndex: 60},
-			want: []types.Token{},
+			want: []string{},
 		},
 	}
 	for _, tt := range tests {
diff --git a/platform/elrond/transaction_test.go b/platform/elrond/transaction_test.go
index afd23af..0d67181 100644
--- a/platform/elrond/transaction_test.go
+++ b/platform/elrond/transaction_test.go
@@ -11,13 +11,14 @@ import (
 )
 
 var (
-	userAddress       = `erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0`
-	txTransferSrc1, _ = mock.JsonStringFromFilePath("mocks/tx.json")
-	txTransferSrc2, _ = mock.JsonStringFromFilePath("mocks/tx_2.json")
-	txTransferSrc3, _ = mock.JsonStringFromFilePath("mocks/tx_3.json")
-	txTransferSrc4, _ = mock.JsonStringFromFilePath("mocks/tx_4.json")
-	txTransferSrc5, _ = mock.JsonStringFromFilePath("mocks/tx_5.json")
-	txTransferSrc6, _ = mock.JsonStringFromFilePath("mocks/tx_6.json")
+	userAddress         = `erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0`
+	txTransferSrc1, _   = mock.JsonStringFromFilePath("mocks/tx.json")
+	txTransferSrc2, _   = mock.JsonStringFromFilePath("mocks/tx_2.json")
+	txTransferSrc3, _   = mock.JsonStringFromFilePath("mocks/tx_3.json")
+	txTransferSrc4, _   = mock.JsonStringFromFilePath("mocks/tx_4.json")
+	txTransferSrc5, _   = mock.JsonStringFromFilePath("mocks/tx_5.json")
+	txTransferSrc6, _   = mock.JsonStringFromFilePath("mocks/tx_6.json")
+	scrNegativeValue, _ = mock.JsonStringFromFilePath("mocks/scr_negative_value.json")
 
 	txTransfer1Normalized = types.Tx{
 		ID:       "30d404cc7a42b0158b95f6adfbf9a517627d60f6c7e497c1442dfdb6460285df",
@@ -168,15 +169,16 @@ func TestNormalize(t *testing.T) {
 }
 
 func TestNormalizeTxs(t *testing.T) {
-	var tx1, tx2, tx3 Transaction
+	var tx1, tx2, tx3, scrNegative Transaction
 
 	_ = json.Unmarshal([]byte(txTransferSrc1), &tx1)
 	_ = json.Unmarshal([]byte(txTransferSrc1), &tx2)
 	_ = json.Unmarshal([]byte(txTransferSrc1), &tx3)
+	_ = json.Unmarshal([]byte(scrNegativeValue), &scrNegative)
 
-	txs := []Transaction{tx1, tx2, tx3}
+	txs := []Transaction{tx1, tx2, tx3, scrNegative}
 	normalizedTxs := NormalizeTxs(txs, userAddress, Block{})
-	require.Equal(t, len(txs), len(normalizedTxs))
+	require.Equal(t, len(txs)-1, len(normalizedTxs))
 }
 
 func testNormalize(t *testing.T, _test *test) {
diff --git a/platform/ethereum/transaction_test.go b/platform/ethereum/transaction_test.go
index ceb0ba2..5c846ba 100644
--- a/platform/ethereum/transaction_test.go
+++ b/platform/ethereum/transaction_test.go
@@ -55,8 +55,8 @@ func (c Client) GetTokenTxs(address, token string, coinIndex uint) (types.TxPage
 	return txs, nil
 }
 
-func (c Client) GetTokenList(address string, coinIndex uint) (types.TokenPage, error) {
-	return types.TokenPage{}, nil
+func (c Client) GetTokenList(address string, coinIndex uint) ([]string, error) {
+	return []string{}, nil
 }
 
 func (c Client) GetCurrentBlockNumber() (int64, error) {
diff --git a/platform/tezos/block_test.go b/platform/tezos/block_test.go
index aa5141b..149052d 100644
--- a/platform/tezos/block_test.go
+++ b/platform/tezos/block_test.go
@@ -9,7 +9,7 @@ import (
 	"github.com/trustwallet/golibs/types"
 )
 
-func TestNormalizeRpcBlock(t *testing.T) {
+func TestProcessRpcBlock(t *testing.T) {
 	type args struct {
 		filename string
 	}
@@ -64,6 +64,7 @@ func TestNormalizeRpcBlock(t *testing.T) {
 							Name:     "Tezos",
 							Symbol:   "XTZ",
 							Decimals: 6,
+							Value:    "163965",
 						},
 					},
 					{
@@ -103,6 +104,7 @@ func TestNormalizeRpcBlock(t *testing.T) {
 							Name:     "Tezos",
 							Symbol:   "XTZ",
 							Decimals: 6,
+							Value:    "163965",
 						},
 					},
 					{
@@ -131,15 +133,16 @@ func TestNormalizeRpcBlock(t *testing.T) {
 	for _, tt := range tests {
 		var block RpcBlock
 		_ = mock.JsonModelFromFilePath("mocks/"+tt.args.filename, &block)
+		rpcClient := &RpcClientMock{Balance: "163965"}
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := NormalizeRpcBlock(block)
+			got, err := ProcessRpcBlock(block, rpcClient)
 			fmt.Println(got)
 			if (err != nil) != tt.wantErr {
-				t.Errorf("NormalizeRpcBlock() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("ProcessRpcBlock() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
 			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("NormalizeRpcBlock() = %v, \nwant %v", got, tt.want)
+				t.Errorf("ProcessRpcBlock() = %v, \nwant %v", got, tt.want)
 			}
 		})
 	}
diff --git a/platform/tron/token_test.go b/platform/tron/token_test.go
index 3246267..8ccde95 100644
--- a/platform/tron/token_test.go
+++ b/platform/tron/token_test.go
@@ -10,26 +10,8 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/trustwallet/golibs/mock"
-	"github.com/trustwallet/golibs/types"
 )
 
-var (
-	tokenDst = types.Token{
-		Name:     "Test",
-		Symbol:   "TST",
-		Decimals: 8,
-		TokenID:  "1",
-		Coin:     195,
-		Type:     "TRC10",
-	}
-)
-
-func TestNormalizeToken(t *testing.T) {
-	asset := AssetInfo{Name: "Test", Symbol: "TST", ID: 1, Decimals: 8}
-	actual := NormalizeToken(asset)
-	assert.Equal(t, tokenDst, actual)
-}
-
 func TestPlatform_GetTokenListByAddress(t *testing.T) {
 	server := httptest.NewServer(createMockedAPI())
 	defer server.Close()
@@ -38,7 +20,7 @@ func TestPlatform_GetTokenListByAddress(t *testing.T) {
 	res, err := p.GetTokenListByAddress("TM1zzNDZD2DPASbKcgdVoTYhfmYgtfwx9R")
 	assert.Nil(t, err)
 	sort.Slice(res, func(i, j int) bool {
-		return res[i].TokenID < res[j].TokenID
+		return res[i] < res[j]
 	})
 	rawRes, err := json.Marshal(res)
 	assert.Nil(t, err)
diff --git a/platform/vechain/transaction_test.go b/platform/vechain/transaction_test.go
index 3c25083..87fdd96 100644
--- a/platform/vechain/transaction_test.go
+++ b/platform/vechain/transaction_test.go
@@ -10,16 +10,12 @@ import (
 	"github.com/trustwallet/golibs/types"
 )
 
-var (
-	transferSrc, _     = mock.JsonStringFromFilePath("mocks/transfer.json")
-	trxId, _           = mock.JsonStringFromFilePath("mocks/tx_id.json")
-	transferLogSrc, _  = mock.JsonStringFromFilePath("mocks/transfer_log.json")
-	trxReceipt, _      = mock.JsonStringFromFilePath("mocks/transfer_receipt.json")
-	revertedTx, _      = mock.JsonStringFromFilePath("mocks/reverted_tx.json")
-	revertedReceipt, _ = mock.JsonStringFromFilePath("mocks/reverted_receipt.json")
-)
-
 func TestNormalizeTransaction(t *testing.T) {
+	var (
+		transferSrc, _ = mock.JsonStringFromFilePath("mocks/transfer.json")
+		trxId, _       = mock.JsonStringFromFilePath("mocks/tx_id.json")
+	)
+
 	tests := []struct {
 		name     string
 		addr     string
@@ -68,35 +64,59 @@ func TestNormalizeTransaction(t *testing.T) {
 
 func TestNormalizeTokenTransaction(t *testing.T) {
 	tests := []struct {
-		name      string
-		txData    string
-		txReceipt string
-		expected  types.TxPage
+		name        string
+		txFile      string
+		receiptFile string
+		address     string
+		expected    types.TxPage
 	}{
-		{"Normalize VIP180 token transfer", transferLogSrc, trxReceipt, types.TxPage{
+		{"Normalize outgoing VTHO tx", "outgoing_vtho_tx.json", "outgoing_vtho_receipt.json", "0xe99399dd211eF54c301A5d1AA813471d92122eA8", types.TxPage{
+			{
+				ID:        "0x0677f91de4787d295087acec0a7ba317b0019fbf296fed630fdb5afbfca97a58",
+				Coin:      coin.VET,
+				From:      "0xe99399dd211eF54c301A5d1AA813471d92122eA8",
+				To:        "0x0000000000000000000000000000456E65726779",
+				Date:      1610958570,
+				Type:      types.TxTokenTransfer,
+				Fee:       types.Amount("36518000000000000000"),
+				Status:    types.StatusCompleted,
+				Block:     8045756,
+				Direction: types.DirectionOutgoing,
+				Meta: types.TokenTransfer{
+					Name:     gasTokenName,
+					Symbol:   gasTokenSymbol,
+					TokenID:  "0x0000000000000000000000000000456E65726779",
+					From:     "0xe99399dd211eF54c301A5d1AA813471d92122eA8",
+					To:       "0xB5e883349e68aB59307d1604555AC890fAC47128",
+					Value:    types.Amount("7000000000000000000"),
+					Decimals: 18,
+				},
+			},
+		}},
+		{"Normalize incoming VTHO tx", "incoming_vtho_tx.json", "incoming_vtho_receipt.json", "0xe99399dd211eF54c301A5d1AA813471d92122eA8", types.TxPage{
 			{
-				ID:        "0x42f5eba46ddcc458243c753545a3faa849502d078efbc5b74baddea9e6ea5b04",
+				ID:        "0xb356fa7b3a371f1518a5f9bc51e951d0dac2ef04d58b532c7ca50a52aa5cddb4",
 				Coin:      coin.VET,
-				From:      "0x2c7A8d5ccE0d5E6a8a31233B7Dc3DAE9AaE4b405",
+				From:      "0xB5e883349e68aB59307d1604555AC890fAC47128",
 				To:        "0x0000000000000000000000000000456E65726779",
-				Date:      1574278180,
+				Date:      1610958460,
 				Type:      types.TxTokenTransfer,
 				Fee:       types.Amount("36582000000000000000"),
 				Status:    types.StatusCompleted,
-				Block:     4382764,
+				Block:     8045745,
 				Direction: types.DirectionIncoming,
 				Meta: types.TokenTransfer{
 					Name:     gasTokenName,
 					Symbol:   gasTokenSymbol,
 					TokenID:  "0x0000000000000000000000000000456E65726779",
-					From:     "0x2c7A8d5ccE0d5E6a8a31233B7Dc3DAE9AaE4b405",
-					To:       "0xB5e883349e68aB59307d1604555AC890fAC47128",
-					Value:    types.Amount("68000000000000000000"),
+					From:     "0xB5e883349e68aB59307d1604555AC890fAC47128",
+					To:       "0xe99399dd211eF54c301A5d1AA813471d92122eA8",
+					Value:    types.Amount("1000000000000000000000"),
 					Decimals: 18,
 				},
 			},
 		}},
-		{"Normalize reverted token transfer", revertedTx, revertedReceipt, types.TxPage{
+		{"Normalize reverted token transfer", "reverted_tx.json", "reverted_receipt.json", "0x7cFFB7632252Bae3766734d61F148f0Ea78Fc08C", types.TxPage{
 			{
 				ID:     "0x7fae32a743e42eaec54642e2a5742a185299f5b4bedaf12c60f65705661de932",
 				Coin:   coin.VET,
@@ -116,16 +136,18 @@ func TestNormalizeTokenTransaction(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			var tx Tx
-			err := json.Unmarshal([]byte(tt.txData), &tx)
+			err := mock.JsonModelFromFilePath("mocks/"+tt.txFile, &tx)
 			assert.Nil(t, err)
 
 			var receipt TxReceipt
-			errR := json.Unmarshal([]byte(tt.txReceipt), &receipt)
-			assert.Nil(t, errR)
+			err = mock.JsonModelFromFilePath("mocks/"+tt.receiptFile, &receipt)
+			assert.Nil(t, err)
 
 			actual, err := platform.NormalizeTokenTransaction(tx, receipt)
 			assert.Nil(t, err)
 
+			updateTransactionDirection(&actual[0], tt.address)
+
 			assert.Equal(t, len(actual), 1, "tx could not be normalized")
 			assert.Equal(t, tt.expected, actual, "tx don't equal")
 		})
@@ -174,35 +196,7 @@ func Test_getRecipientAddress(t *testing.T) {
 	}
 }
 
-func Test_getTokenTransactionDirectory(t *testing.T) {
-	addr1 := "0xb5e883349e68ab59307d1604555ac890fac47128"
-	addr2 := "0eec2bbedbb8b18357dab0b753cd1893bb832284"
-	tests := []struct {
-		name         string
-		originSender string
-		topicsFrom   string
-		topicsTo     string
-		expected     types.Direction
-		expectErr    bool
-	}{
-		{"Self direction", addr1, addr1, addr1, types.DirectionSelf, false},
-		{"In direction", addr1, addr1, addr2, types.DirectionIncoming, false},
-		{"Out direction", addr1, addr2, addr1, types.DirectionOutgoing, false},
-		{"Unknown direction", addr1, addr2, addr2, "", true},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			actual, err := getTokenTransactionDirectory(tt.originSender, tt.topicsFrom, tt.topicsTo)
-			if tt.expectErr {
-				assert.NotNil(t, err)
-			}
-			assert.Equal(t, tt.expected, actual)
-		})
-	}
-}
-
-func Test_getTransferDirectory(t *testing.T) {
+func Test_getTransactionDirection(t *testing.T) {
 	addr1 := "0xb5e883349e68ab59307d1604555ac890fac47128"
 	addr2 := "0eec2bbedbb8b18357dab0b753cd1893bb832284"
 	tests := []struct {
@@ -221,7 +215,7 @@ func Test_getTransferDirectory(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			actual, err := getTransferDirectory(tt.sender, tt.recipient, tt.address)
+			actual, err := getTransactionDirection(tt.sender, tt.recipient, tt.address)
 			if tt.expectErr {
 				assert.NotNil(t, err)
 			}