Repository: trustwallet/blockatlas
Pull request #1099 (2020-05-15 14:08:50)

Add harmony staking support in mainnet
This is resubmit for old PRs (#733 and #923) which supports harmony staking in block atlas.
The old PR failed test because the Harmony Mainnet didn't have the staking RPC (and I can't use testnet endpoints).

Now I can re-submit this PR since Harmony MainNet has staking RPC ready. 


Test status:
1) passed local test
2) passed the tests which failed in the previous PRs:
1. cd cmd/platform_api
2. ATLAS_PLATFORM=harmony go run main.go
3. Try these requests:

http://localhost:8420/v2/harmony/staking/validators
http://localhost:8420/v2/harmony/staking/delegations/one1e4mr7tp0a76wnhv9xd0wzentdjnjnsh3fwzgfv


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

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

```diff
@@          Coverage Diff           @@
##           master   #1099   +/-   ##
======================================
- Coverage      38%     38%   -1%     
======================================
  Files         156     157    +1     
  Lines        5298    5385   +87     
======================================
+ Hits         2039    2064   +25     
- Misses       3123    3184   +61     
- Partials      136     137    +1     
```


| [Impacted Files](https://codecov.io/gh/trustwallet/blockatlas/pull/1099?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [platform/harmony/client.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1099/diff?src=pr&el=tree#diff-cGxhdGZvcm0vaGFybW9ueS9jbGllbnQuZ28=) | `6% <0%> (-5%)` | :arrow_down: |
| [platform/harmony/transaction.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1099/diff?src=pr&el=tree#diff-cGxhdGZvcm0vaGFybW9ueS90cmFuc2FjdGlvbi5nbw==) | `49% <ø> (ø)` | |
| [platform/harmony/stake.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1099/diff?src=pr&el=tree#diff-cGxhdGZvcm0vaGFybW9ueS9zdGFrZS5nbw==) | `36% <36%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/trustwallet/blockatlas/pull/1099?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/1099?src=pr&el=footer). Last update [033efb7...23930be](https://codecov.io/gh/trustwallet/blockatlas/pull/1099?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.codacy.yml b/.codacy.yml
deleted file mode 100644
index 4b6a06d..0000000
--- a/.codacy.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-exclude_paths:
-  - 'mock/ext-api-dyson/**'
diff --git a/README.md b/README.md
index aca802e..ebe256e 100644
--- a/README.md
+++ b/README.md
@@ -149,11 +149,16 @@ By default starting any of the [services](#architecture) will enable all platfor
 To run a specific service only by passing environmental variable, e.g: `platfrom_api` :
 ```shell
 ATLAS_PLATFORM=ethereum go run cmd/platform_api/main.go
+
+ATLAS_PLATFORM=ethereum binance bitcoin go run cmd/platform_api/main.go # for multiple platforms
 ```
 
 or change in config file
 ```yaml
-platform: ethereum
+# Single
+platform: [ethereum]
+# Multiple 
+platform: [ethereum, binance, bitcoin]
 ```
 
 This way you can one platform per binary, for scalability and sustainability.
diff --git a/cmd/observer_notifier/main.go b/cmd/observer_notifier/main.go
index 850d712..b71fe18 100644
--- a/cmd/observer_notifier/main.go
+++ b/cmd/observer_notifier/main.go
@@ -13,6 +13,7 @@ import (
 
 const (
 	defaultConfigPath = "../../config.yml"
+	prod              = "prod"
 )
 
 var (
@@ -42,7 +43,7 @@ func init() {
 	}
 
 	var err error
-	database, err = db.New(pgUri)
+	database, err = db.New(pgUri, prod)
 	if err != nil {
 		logger.Fatal(err)
 	}
diff --git a/cmd/observer_parser/main.go b/cmd/observer_parser/main.go
index 83e59f3..e828283 100644
--- a/cmd/observer_parser/main.go
+++ b/cmd/observer_parser/main.go
@@ -20,6 +20,7 @@ import (
 
 const (
 	defaultConfigPath = "../../config.yml"
+	prod              = "prod"
 )
 
 var (
@@ -38,10 +39,10 @@ func init() {
 
 	mqHost := viper.GetString("observer.rabbitmq.uri")
 	prefetchCount := viper.GetInt("observer.rabbitmq.consumer.prefetch_count")
-	platformHandle := viper.GetString("platform")
+	platformHandles := viper.GetStringSlice("platform")
 
 	internal.InitRabbitMQ(mqHost, prefetchCount)
-	platform.Init(platformHandle)
+	platform.Init(platformHandles)
 
 	if err := mq.RawTransactions.Declare(); err != nil {
 		logger.Fatal(err)
@@ -63,7 +64,7 @@ func init() {
 		logger.Fatal("minimum block polling interval cannot be greater or equal than maximum")
 	}
 	var err error
-	database, err = db.New(pgUri)
+	database, err = db.New(pgUri, prod)
 	if err != nil {
 		logger.Fatal(err)
 	}
@@ -83,6 +84,7 @@ func main() {
 
 	wg.Add(len(platform.BlockAPIs))
 	for _, api := range platform.BlockAPIs {
+		time.Sleep(time.Millisecond * 5)
 		coin := api.Coin()
 		pollInterval := notifier.GetInterval(coin.BlockTime, minInterval, maxInterval)
 
diff --git a/cmd/observer_subscriber/main.go b/cmd/observer_subscriber/main.go
index 77225cb..38d8dfd 100644
--- a/cmd/observer_subscriber/main.go
+++ b/cmd/observer_subscriber/main.go
@@ -14,6 +14,7 @@ import (
 
 const (
 	defaultConfigPath = "../../config.yml"
+	prod              = "prod"
 )
 
 var (
@@ -35,7 +36,7 @@ func init() {
 	internal.InitRabbitMQ(mqHost, prefetchCount)
 
 	var err error
-	database, err = db.New(pgUri)
+	database, err = db.New(pgUri, prod)
 	if err != nil {
 		logger.Fatal(err)
 	}
diff --git a/cmd/platform_api/main.go b/cmd/platform_api/main.go
index 5bc8bc2..40c971d 100644
--- a/cmd/platform_api/main.go
+++ b/cmd/platform_api/main.go
@@ -28,7 +28,7 @@ func init() {
 
 	engine = internal.InitEngine(viper.GetString("gin.mode"))
 
-	platform.Init(viper.GetString("platform"))
+	platform.Init(viper.GetStringSlice("platform"))
 }
 
 func main() {
diff --git a/coin/coins.go b/coin/coins.go
index dcebc47..88e22e6 100644
--- a/coin/coins.go
+++ b/coin/coins.go
@@ -1,6 +1,6 @@
 // Code generated by go generate; DO NOT EDIT.
 // This file was generated by robots at
-// 2020-03-19 12:32:14.57052 -0600 MDT m=&#43;0.001450289
+// 2020-05-15 00:06:57.247245 &#43;0300 MSK m=&#43;0.001827650
 // using data from coins.yml
 package coin
 
@@ -243,7 +243,7 @@ var Coins = map[uint]Coin{
 		Name:             "BNB",
 		Decimals:         8,
 		BlockTime:        4000,
-		MinConfirmations: 0,
+		MinConfirmations: 2,
 		SampleAddr:       "tbnb1fhr04azuhcj0dulm7ka40y0cqjlafwae9k9gk2",
 	},
 	VET: {
@@ -557,7 +557,6 @@ var Coins = map[uint]Coin{
 		SampleAddr:       "NEAR6Y66fCzeKqWiwxoPox5oGeDN9VhNCu7CEQ9M86iniqoN9vg2X",
 	},
 }
-
 func Ethereum() Coin {
 	return Coins[ETH]
 }
@@ -702,3 +701,4 @@ func Solana() Coin {
 func Near() Coin {
 	return Coins[NEAR]
 }
+
diff --git a/coin/coins.yml b/coin/coins.yml
index 4379fd1..f1bc5b9 100644
--- a/coin/coins.yml
+++ b/coin/coins.yml
@@ -130,6 +130,7 @@
   name: BNB
   decimals: 8
   blockTime: 4000
+  minConfirmations: 2
   sampleAddress: 'tbnb1fhr04azuhcj0dulm7ka40y0cqjlafwae9k9gk2'
 
 - id: 818
diff --git a/config.yml b/config.yml
index 0cef731..f0f6a87 100644
--- a/config.yml
+++ b/config.yml
@@ -11,7 +11,7 @@ gin:
 # If all - run all platforms in one binary. You can pick specific coin handle to run binary only with specific coin
 # Example: ethereum
 # You can see all the coin handles at coins/coins.yml file
-platform: all
+platform: [ethereum]
 
 # The transaction watcher
 observer:
@@ -37,11 +37,10 @@ observer:
 postgres:
   uri: postgresql://user:pass@localhost/my_db?sslmode=disable
 
-# [BNB] Binance DEX: https://wallet.binance.org
-#       Binance Chain: https://explorer.binance.org
+# [BNB] Binance DEX: https://www.binance.org/
 binance:
-  api: https://explorer.binance.org/api/v1
-  dex: https://dex.binance.org/api
+  api: https://dex.binance.org/api
+  explorer: https://explorer.binance.org/api
 
 # [NIM] Nimiq: https://nimiq.com
 #nimiq:
@@ -67,7 +66,7 @@ tezos:
 # [ETH] Ethereum: https://ethereum.org
 ethereum:
   api: https://localhost:4567 #(Trust-Ray API)
-  blockbook_api: https://eth1.trezor.io/api/
+  blockbook_api: https://eth1.trezor.io/api
   collections_api: https://api.opensea.io
 #  collections_api_key: [opensea_api_key]
   rpc: https://main-rpc.linkpool.io
diff --git a/configmock.yml b/configmock.yml
index 757655f..95b934c 100644
--- a/configmock.yml
+++ b/configmock.yml
@@ -33,8 +33,8 @@ postgres:
 # [BNB] Binance DEX: https://wallet.binance.org
 #       Binance Chain: https://explorer.binance.org
 binance:
-  api: http://localhost:3347/binance-api/v1
-  dex: http://localhost:3347/binance-dex
+  api: https://dex.binance.org/api
+  explorer: http://localhost:3347/binance-explorer/api
 
 # [NIM] Nimiq: https://nimiq.com
 nimiq:
diff --git a/db/db.go b/db/db.go
index c20036e..fe79c96 100644
--- a/db/db.go
+++ b/db/db.go
@@ -2,9 +2,10 @@ package db
 
 import (
 	"github.com/jinzhu/gorm"
-	_ "github.com/jinzhu/gorm/dialects/postgres"
 	"github.com/trustwallet/blockatlas/db/models"
 	"github.com/trustwallet/blockatlas/pkg/logger"
+	"go.elastic.co/apm/module/apmgorm"
+	_ "go.elastic.co/apm/module/apmgorm/dialects/postgres"
 	"time"
 )
 
@@ -12,8 +13,17 @@ type Instance struct {
 	Gorm *gorm.DB
 }
 
-func New(uri string) (*Instance, error) {
-	g, err := gorm.Open("postgres", uri)
+func New(uri, env string) (*Instance, error) {
+	var (
+		g   *gorm.DB
+		err error
+	)
+	if env == "prod" {
+		g, err = apmgorm.Open("postgres", uri)
+	} else {
+		g, err = gorm.Open("postgres", uri)
+	}
+
 	if err != nil {
 		return nil, err
 	}
diff --git a/db/subscriptions.go b/db/subscriptions.go
index 35bb2d2..917fc40 100644
--- a/db/subscriptions.go
+++ b/db/subscriptions.go
@@ -1,9 +1,11 @@
 package db
 
 import (
+	"context"
 	"fmt"
 	"github.com/trustwallet/blockatlas/db/models"
 	"github.com/trustwallet/blockatlas/pkg/errors"
+	"go.elastic.co/apm/module/apmgorm"
 	"strconv"
 	"strings"
 	"time"
@@ -11,13 +13,13 @@ import (
 
 const rawBulkInsert = `INSERT INTO subscription_data(subscription_id, coin, address) VALUES %s ON CONFLICT DO NOTHING`
 
-func (i *Instance) GetSubscriptionData(coin uint, addresses []string) ([]models.SubscriptionData, error) {
+func (i *Instance) GetSubscriptionData(coin uint, addresses []string, ctx context.Context) ([]models.SubscriptionData, error) {
 	if len(addresses) == 0 {
 		return nil, errors.E("Empty addresses")
 	}
-
+	g := apmgorm.WithContext(ctx, i.Gorm)
 	var subscriptionsDataList []models.SubscriptionData
-	err := i.Gorm.
+	err := g.
 		Model(&models.SubscriptionData{}).
 		Where("address in (?) AND coin = ?", addresses, coin).
 		Find(&subscriptionsDataList).Error
@@ -28,12 +30,12 @@ func (i *Instance) GetSubscriptionData(coin uint, addresses []string) ([]models.
 	return subscriptionsDataList, nil
 }
 
-func (i *Instance) AddSubscriptions(id uint, subscriptions []models.SubscriptionData) error {
+func (i *Instance) AddSubscriptions(id uint, subscriptions []models.SubscriptionData, ctx context.Context) error {
 	if len(subscriptions) == 0 {
 		return errors.E("Empty subscriptions")
 	}
-
-	txInstance := Instance{Gorm: i.Gorm.Begin()}
+	g := apmgorm.WithContext(ctx, i.Gorm)
+	txInstance := Instance{Gorm: g.Begin()}
 	defer func() {
 		if r := recover(); r != nil {
 			txInstance.Gorm.Rollback()
@@ -106,8 +108,9 @@ func (i *Instance) AddToExistingSubscription(id uint, subscriptions []models.Sub
 	return nil
 }
 
-func (i *Instance) DeleteAllSubscriptions(id uint) error {
-	request := i.Gorm.Where("subscription_id = ?", id)
+func (i *Instance) DeleteAllSubscriptions(id uint, ctx context.Context) error {
+	g := apmgorm.WithContext(ctx, i.Gorm)
+	request := g.Where("subscription_id = ?", id)
 	if err := request.Error; err != nil {
 		return err
 	}
diff --git a/db/tracker.go b/db/tracker.go
index 1007e0b..86c8ba6 100644
--- a/db/tracker.go
+++ b/db/tracker.go
@@ -1,7 +1,9 @@
 package db
 
 import (
+	"context"
 	"github.com/trustwallet/blockatlas/db/models"
+	"go.elastic.co/apm/module/apmgorm"
 	"sync"
 )
 
@@ -29,26 +31,27 @@ func (hbm *heightBlockMap) GetHeight(coin string) (int64, bool) {
 	return b, ok
 }
 
-func (i *Instance) GetLastParsedBlockNumber(coin string) (int64, error) {
+func (i *Instance) GetLastParsedBlockNumber(coin string, ctx context.Context) (int64, error) {
 	height, ok := memoryCache.GetHeight(coin)
 	if ok {
 		return height, nil
 	}
 	var tracker models.Tracker
-	if err := i.Gorm.Where(models.Tracker{Coin: coin}).Find(&tracker).Error; err != nil {
+	g := apmgorm.WithContext(ctx, i.Gorm)
+	if err := g.Where(models.Tracker{Coin: coin}).Find(&tracker).Error; err != nil {
 		return 0, nil
 	}
 	return tracker.Height, nil
 }
 
-func (i *Instance) SetLastParsedBlockNumber(coin string, num int64) error {
+func (i *Instance) SetLastParsedBlockNumber(coin string, num int64, ctx context.Context) error {
 	memoryCache.SetHeight(coin, num)
 	tracker := models.Tracker{
 		Coin:   coin,
 		Height: num,
 	}
-
-	return i.Gorm.
+	g := apmgorm.WithContext(ctx, i.Gorm)
+	return g.
 		Set("gorm:insert_option", "ON CONFLICT (coin) DO UPDATE SET height = excluded.height").
 		Where(models.Tracker{Coin: coin}).
 		Create(&tracker).Error
diff --git a/go.mod b/go.mod
index c1415e7..443ac60 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,10 @@ require (
 	github.com/deckarep/golang-set v1.7.1
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.4.0 // indirect
+	github.com/elastic/go-sysinfo v1.3.0 // indirect
+	github.com/elastic/go-windows v1.0.1 // indirect
 	github.com/gin-gonic/gin v1.6.3
+	github.com/golang/protobuf v1.4.2 // indirect
 	github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
 	github.com/jinzhu/gorm v1.9.12
 	github.com/mitchellh/mapstructure v1.3.0
@@ -24,6 +27,7 @@ require (
 	github.com/opencontainers/runc v0.1.1 // indirect
 	github.com/ory/dockertest v3.3.5+incompatible
 	github.com/patrickmn/go-cache v2.1.0+incompatible
+	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.6.0
 	github.com/shopspring/decimal v1.2.0
 	github.com/sirupsen/logrus v1.6.0
@@ -33,10 +37,18 @@ require (
 	github.com/swaggo/gin-swagger v1.2.0
 	github.com/swaggo/swag v1.6.5
 	github.com/trustwallet/ens-coincodec v1.0.5
+	go.elastic.co/apm v1.8.0
+	go.elastic.co/apm/module/apmgin v1.8.0
+	go.elastic.co/apm/module/apmgorm v1.8.0
+	go.elastic.co/apm/module/apmhttp v1.8.0
+	go.elastic.co/apm/module/apmlogrus v1.8.0
+	go.elastic.co/fastjson v1.1.0 // indirect
 	go.uber.org/atomic v1.6.0
 	golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
 	golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
+	golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect
 	golang.org/x/tools v0.0.0-20200513175351-0951661448da // indirect
-	gopkg.in/yaml.v2 v2.2.8
+	gopkg.in/yaml.v2 v2.3.0
 	gotest.tools v2.2.0+incompatible // indirect
+	howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 // indirect
 )
diff --git a/go.sum b/go.sum
index fa19a1e..4d2b753 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,9 @@
 bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro=
 cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
@@ -31,15 +33,20 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -77,12 +84,15 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
 github.com/cpacia/bchutil v0.0.0-20181003130114-b126f6a35b6c h1:4e6Zsb6LFd3kadoMiut2zcd3hCb4zywpJnQa8+NV2Cs=
 github.com/cpacia/bchutil v0.0.0-20181003130114-b126f6a35b6c/go.mod h1:k5D13LCXSsMrQyfdW0yGYs4GWUvirqoxHht8qwtqyRY=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8=
+github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA=
 github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
 github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
+github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
 github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
 github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@@ -92,6 +102,17 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/elastic/go-sysinfo v1.1.1 h1:ZVlaLDyhVkDfjwPGU55CQRCRolNpc7P0BbyhhQZQmMI=
+github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
+github.com/elastic/go-sysinfo v1.3.0 h1:eb2XFGTMlSwG/yyU9Y8jVAYLIzU2sFzWXwo2gmetyrE=
+github.com/elastic/go-sysinfo v1.3.0/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
+github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY=
+github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
+github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
+github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -139,8 +160,11 @@ github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1
 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -159,6 +183,9 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -175,6 +202,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
@@ -205,12 +234,16 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=
 github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
 github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
 github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
+github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -236,6 +269,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
 github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
@@ -251,6 +285,7 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw=
 github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@@ -286,6 +321,7 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
 github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA=
 github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -293,37 +329,49 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A=
 github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
 github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI=
 github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
+github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
@@ -388,7 +436,24 @@ github.com/wealdtech/go-slip44 v1.0.0 h1:g0Wi5EufdD/ULZ4hcZKQ3TGlA6FZhGTCkmVzYmf
 github.com/wealdtech/go-slip44 v1.0.0/go.mod h1://S3V9M6iN1cCflLXMjJYciRPakCDubG9YpWVyW1AvM=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.elastic.co/apm v1.8.0 h1:AWEKpHwRal0yCMd4K8Oxy1HAa7xid+xq1yy+XjgoVU0=
+go.elastic.co/apm v1.8.0/go.mod h1:tCw6CkOJgkWnzEthFN9HUP1uL3Gjc/Ur6m7gRPLaoH0=
+go.elastic.co/apm/module/apmgin v1.8.0 h1:QcXVtFhqAcSGqC/ywzqZBHdWzJG3wyrBxgvCvAZ98do=
+go.elastic.co/apm/module/apmgin v1.8.0/go.mod h1:heH2rXaOluBb+ucwlG3FBCGrx56qHqoxb1KI7uBLlAk=
+go.elastic.co/apm/module/apmgorm v1.8.0 h1:1vKuD8hsbJcChcUQTBb1N8Zy1tILR5pGTExLtijyhko=
+go.elastic.co/apm/module/apmgorm v1.8.0/go.mod h1:18ttEKPQr0cvRSeG95WMXoPUy2BT8Jp1EzhPU5NQYTY=
+go.elastic.co/apm/module/apmhttp v1.8.0 h1:5AJPefWJzWDLX/47XIDfaloGiYWkkOQEULvlrI6Ieaw=
+go.elastic.co/apm/module/apmhttp v1.8.0/go.mod h1:9LPFlEON51/lRbnWDfqAWErihIiAFDUMfMV27YjoWQ8=
+go.elastic.co/apm/module/apmlogrus v1.8.0 h1:HSz+gNSa88zLvoX36OaCSgbqTTCn7u3GrR6WqRu8RiU=
+go.elastic.co/apm/module/apmlogrus v1.8.0/go.mod h1:0TsyfBEaY5FaGS2p9UlSRhmf1T1zhmf9vcwgQTlI064=
+go.elastic.co/apm/module/apmsql v1.8.0 h1:YMGTshRcC9SI8p+hJNI7OzNnk7Dn9RjwuwO0MRcbvvE=
+go.elastic.co/apm/module/apmsql v1.8.0/go.mod h1:pX+PSxIcEv5BvIYJjQnODzNwJ6+DJDeLb0fzrhSOIhE=
+go.elastic.co/fastjson v1.0.0 h1:ooXV/ABvf+tBul26jcVViPT3sBir0PvXgibYB1IQQzg=
+go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs=
+go.elastic.co/fastjson v1.1.0 h1:3MrGBWWVIxe/xvsbpghtkFoPciPhOCmjsR/HfwEeQR4=
+go.elastic.co/fastjson v1.1.0/go.mod h1:boNGISWMjQsUPy/t6yqt2/1Wx4YNPSe+mZjlyw9vKKI=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -408,9 +473,8 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
-golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -432,6 +496,7 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -442,6 +507,7 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -451,6 +517,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
@@ -471,6 +538,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -483,11 +551,16 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
 golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -495,6 +568,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -517,13 +591,16 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20200513175351-0951661448da h1:ZR1ivkcQoKXKtux9Rx3Em7iiSViMxQ5suNd5PZMUkPc=
 golang.org/x/tools v0.0.0-20200513175351-0951661448da/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -535,13 +612,16 @@ google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
 google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -551,6 +631,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -573,10 +655,17 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
+howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
+howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc=
+howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/internal/init.go b/internal/init.go
index e61222c..d2da34f 100644
--- a/internal/init.go
+++ b/internal/init.go
@@ -7,6 +7,7 @@ import (
 	"github.com/trustwallet/blockatlas/config"
 	"github.com/trustwallet/blockatlas/mq"
 	"github.com/trustwallet/blockatlas/pkg/logger"
+	"go.elastic.co/apm/module/apmgin"
 
 	"path/filepath"
 	"time"
@@ -42,6 +43,7 @@ func InitEngine(ginMode string) *gin.Engine {
 	gin.SetMode(ginMode)
 	engine := gin.New()
 	engine.Use(middleware.CORSMiddleware())
+	engine.Use(apmgin.Middleware(engine))
 	engine.Use(gin.Logger())
 	engine.Use(middleware.Prometheus())
 	engine.OPTIONS("/*path", middleware.CORSMiddleware())
diff --git a/mock/ext-api-dyson/get/binance-api-tx.js b/mock/ext-api-dyson/get/binance-api-tx.js
deleted file mode 100644
index 63b0e1f..0000000
--- a/mock/ext-api-dyson/get/binance-api-tx.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/// Binance chain block explorer API Mock, tx
-/// Returns:
-/// - Multi-transaction transaction for a specific address
-///   see http://localhost:3347/binance-api/v1/tx?txHash=F53BB470A3B6B83977CFFE5D5F9937FB1CBB8785FBE818D9B38AD43F3ECD82BC
-///   see https://explorer.binance.org/api/v1/tx?txHash=F53BB470A3B6B83977CFFE5D5F9937FB1CBB8785FBE818D9B38AD43F3ECD82BC
-///   see http://localhost:8437/v1/binance/bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp
-///   see http://localhost:8437/v1/binance/bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp?token=BUSD-BD1
-/// - empty response for other txHash'es
-
-module.exports = {
-    path: '/binance-api/v1/tx',
-    template: function(params, query, body) {
-        if (query['txHash'] == 'F53BB470A3B6B83977CFFE5D5F9937FB1CBB8785FBE818D9B38AD43F3ECD82BC') {
-            return {
-                txHash: "F53BB470A3B6B83977CFFE5D5F9937FB1CBB8785FBE818D9B38AD43F3ECD82BC",
-                blockHeight: Number.parseFrom('63280715'),
-                txType: "TRANSFER",
-                timeStamp: Number.parseFrom('1579688431580'),
-                txFee: Number.parseFrom('0.00060'),
-                txAge: Number.parseFrom('2350509'),
-                code: 0,
-                log: "Msg 0: ",
-                confirmBlocks: 5818526,
-                memo: "Trust Wallet Redeem",
-                source: 0,
-                sequence: 175,
-                hasChildren: 1,
-                subTxsDto: {
-                    totalNum: 2,
-                    pageSize: 15,
-                    subTxDtoList: [
-                        {
-                            hash: "F53BB470A3B6B83977CFFE5D5F9937FB1CBB8785FBE818D9B38AD43F3ECD82BC",
-                            height: Number.parseFrom('63280715'),
-                            type: "TRANSFER",
-                            value: 0.00375,
-                            asset: "BNB",
-                            fromAddr: "bnb1rhv98jcx2yu26shxedskttjzpkvsrz4nd226yv",
-                            toAddr: "bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp",
-                            fee: Number.parseFrom('0.00060')
-                        },
-                        {
-                            hash: "F53BB470A3B6B83977CFFE5D5F9937FB1CBB8785FBE818D9B38AD43F3ECD82BC",
-                            height: Number.parseFrom('63280715'),
-                            type: "TRANSFER",
-                            value: 10.0,
-                            asset: "BUSD-BD1",
-                            fromAddr: "bnb1rhv98jcx2yu26shxedskttjzpkvsrz4nd226yv",
-                            toAddr: "bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp",
-                            fee: null
-                        }
-                    ]
-                }
-            };
-        }
-
-        // not found txHash, return empty response
-        return {}
-    }
-};
diff --git a/mock/ext-api-dyson/get/binance-api-txs.js b/mock/ext-api-dyson/get/binance-api-txs.js
deleted file mode 100644
index 3c0591d..0000000
--- a/mock/ext-api-dyson/get/binance-api-txs.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/// Binance chain block explorer API Mock, txs
-/// Return
-/// - for address 'bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp' 2 txs, one of them multi
-/// - for address 'bnb1mnf0g6zhy2rpy63w8ryc6yp77s0fmqfyzjtkvd' 1 tx
-/// - empty list for other addresses
-/// See:
-/// curl "http://localhost:3347/explorer-api/v1/txs?address=bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp&page=1&rows=20&txAsset=BNB&txType=TRANSFER"
-/// curl "https://explorer.binance.org/api/v1/txs?address=bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp&page=1&rows=20&txAsset=BNB&txType=TRANSFER"
-/// curl "http://localhost:8437/v1/binance/bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp"
-
-module.exports = {
-    path: '/binance-api/v1/txs',
-    template: function(params, query, body) {
-        //console.log(query);
-        var address = query['address'];
-        //console.log('address', address);
-        switch (address) {
-            case 'bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp':
-                return JSON.parse(`{
-                    "txNums": 2,
-                    "txArray": [
-                        {
-                            "txHash": "4A5E688755E3588B89C5F0325274430AA52786E527313739C542BC69436E18F4",
-                            "blockHeight": 63282272,
-                            "txType": "TRANSFER",
-                            "timeStamp": 1579689046296,
-                            "fromAddr": "bnb1563k58pc3keeuwkhlrxwz7sdsetyn9l7gdnznp",
-                            "toAddr": "bnb136ns6lfw4zs5hg4n85vdthaad7hq5m4gtkgf23",
-                            "value": 0.55415460,
-                            "txAsset": "BNB",
-                            "txFee": 0.00037500,
-                            "txAge": 5273327,
-                            "code": 0,
-                            "log": "Msg 0: ",
-                            "confirmBlocks": 0,
-                            "memo": "102101832",
-                            "source": 0,
-                            "hasChildren": 0
-                        },
-                        {
-                            "txHash": "F53BB470A3B6B83977CFFE5D5F9937FB1CBB8785FBE818D9B38AD43F3ECD82BC",
-                            "blockHeight": 63280715,
-                            "txType": "TRANSFER",
-                            "timeStamp": 1579688431580,
-                            "txFee": 0.00060000,
-                            "txAge": 5273941,
-                            "code": 0,
-                            "log": "Msg 0: ",
-                            "confirmBlocks": 0,
-                            "memo": "Trust Wallet Redeem",
-                            "source": 0,
-                            "hasChildren": 1
-                        }
-                    ]
-                }`);
-
-            case 'bnb1mnf0g6zhy2rpy63w8ryc6yp77s0fmqfyzjtkvd':
-                return JSON.parse(`{
-                    "txNums": 1,
-                    "txArray": [
-                        {
-                            "txHash": "DDA939C475755B2A00123CDA37A5EC442F502EA2983014D04F647C3CB9FB57A0",
-                            "blockHeight": 75110907,
-                            "txType": "TRANSFER",
-                            "timeStamp": 1584450870939,
-                            "fromAddr": "bnb166fvwtfra2atta5mm8mygt2fyltktqctgpedcg",
-                            "toAddr": "bnb1mnf0g6zhy2rpy63w8ryc6yp77s0fmqfyzjtkvd",
-                            "value": 0.00037500,
-                            "txAsset": "BNB",
-                            "txFee": 0.00037500,
-                            "txAge": 10170,
-                            "code": 0,
-                            "log": "Msg 0: ",
-                            "confirmBlocks": 0,
-                            "memo": "",
-                            "source": 0,
-                            "hasChildren": 0
-                        }
-                    ]
-                }`);
-        }
-
-        // not found, address
-        return {txNums: 0, txArray: []}
-    }
-};
diff --git a/mock/ext-api-dyson/get/binance-dex-commands2.js b/mock/ext-api-dyson/get/binance-dex-commands2.js
deleted file mode 100644
index 6a7299f..0000000
--- a/mock/ext-api-dyson/get/binance-dex-commands2.js
+++ /dev/null
@@ -1,543 +0,0 @@
-/// Binance chain block explorer API Mock, Dex
-/// See:
-/// curl "http://localhost:3347/binance-dex/v1/account/bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m"
-/// curl "http://localhost:3347/binance-dex/v1/tokens?limit=1000&offset=0"
-/// curl "https://dex.binance.org/api/v1/account/bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m"
-/// curl "https://dex.binance.org/api/v1/tokens?limit=1000&offset=0"
-/// curl "http://localhost:8437/v2/binance/tokens/bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m?Authorization=Bearer"
-
-module.exports = {
-    path: '/binance-dex/:version/:command1/:command2?',
-    template: function(params, query, body) {
-        //console.log(params);
-        //console.log(query);
-        switch (params.version) {
-            case 'v1':
-                switch (params.command1) {
-                    case 'account':
-                        switch (params.command2) {
-                            case 'bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m':
-                                return JSON.parse(`
-                                    {
-                                        "account_number": 51,
-                                        "address": "bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m",
-                                        "balances": [
-                                            {
-                                                "free": "1732627268.91580163",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "CHZ-ECD"
-                                            },
-                                            {
-                                                "free": "927279.98843502",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "GTO-908"
-                                            },
-                                            {
-                                                "free": "22990392.97970594",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "LBA-340"
-                                            },
-                                            {
-                                                "free": "13504.20603578",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "LTC-F07"
-                                            },
-                                            {
-                                                "free": "1.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "SLV-986"
-                                            },
-                                            {
-                                                "free": "3400.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "TWT-8C2"
-                                            },
-                                            {
-                                                "free": "417595410.39908617",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "COS-2E4"
-                                            },
-                                            {
-                                                "free": "8859448.20434497",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "XRP-BF2"
-                                            },
-                                            {
-                                                "free": "66199647.50538896",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "VRAB-B56"
-                                            },
-                                            {
-                                                "free": "565.32000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "AERGO-46B"
-                                            },
-                                            {
-                                                "free": "145.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ART-3C9"
-                                            },
-                                            {
-                                                "free": "483312.47553546",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BOLT-4C6"
-                                            },
-                                            {
-                                                "free": "85668472.69714541",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ERD-D06"
-                                            },
-                                            {
-                                                "free": "372741.55000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "MDAB-D42"
-                                            },
-                                            {
-                                                "free": "56343499.10367935",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ONE-5F9"
-                                            },
-                                            {
-                                                "free": "2000000.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BCPT-95A"
-                                            },
-                                            {
-                                                "free": "5.00110000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BEAR-14C"
-                                            },
-                                            {
-                                                "free": "2000.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "GIV-94E"
-                                            },
-                                            {
-                                                "free": "9.44356120",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "NEXO-A84"
-                                            },
-                                            {
-                                                "free": "193182441.20916349",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "UND-EBC"
-                                            },
-                                            {
-                                                "free": "569871.98092216",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "TUSDB-888"
-                                            },
-                                            {
-                                                "free": "316344310.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BKRW-AB7"
-                                            },
-                                            {
-                                                "free": "0.00091449",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "EOSBEAR-721"
-                                            },
-                                            {
-                                                "free": "10918.75299816",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "EOSBULL-F0D"
-                                            },
-                                            {
-                                                "free": "296751584.10699693",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "TROY-9B8"
-                                            },
-                                            {
-                                                "free": "450587749.03659246",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "WINB-41F"
-                                            },
-                                            {
-                                                "free": "1438139.59497068",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ARN-71B"
-                                            },
-                                            {
-                                                "free": "19597996.48343213",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "COTI-CBB"
-                                            },
-                                            {
-                                                "free": "3572612.16675809",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "LTO-BDF"
-                                            },
-                                            {
-                                                "free": "1.22227000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "XRPBEAR-00B"
-                                            },
-                                            {
-                                                "free": "8619272.37637807",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "CAN-677"
-                                            },
-                                            {
-                                                "free": "338507.95870700",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "HNST-3C9"
-                                            },
-                                            {
-                                                "free": "173741504.28850373",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BTTB-D31"
-                                            },
-                                            {
-                                                "free": "65500.72562560",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BUSD-BD1"
-                                            },
-                                            {
-                                                "free": "8549.17210965",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ETH-1C9"
-                                            },
-                                            {
-                                                "free": "1654100.82380293",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "TOMOB-4BC"
-                                            },
-                                            {
-                                                "free": "5.00455774",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "XRPBULL-E7C"
-                                            },
-                                            {
-                                                "free": "35.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "AWC-986"
-                                            },
-                                            {
-                                                "free": "1587996.65265528",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BNB"
-                                            },
-                                            {
-                                                "free": "45605.00637205",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "QARK-FCE"
-                                            },
-                                            {
-                                                "free": "1406863.84448749",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ARPA-575"
-                                            },
-                                            {
-                                                "free": "1619733.62576473",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ENTRP-C8D"
-                                            },
-                                            {
-                                                "free": "0.01392000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "MTXLT-286"
-                                            },
-                                            {
-                                                "free": "2340.58605759",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BCH-1FD"
-                                            },
-                                            {
-                                                "free": "3.49158551",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BULL-BE4"
-                                            },
-                                            {
-                                                "free": "1.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ECO-083"
-                                            },
-                                            {
-                                                "free": "0.50000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "WICC-01D"
-                                            },
-                                            {
-                                                "free": "4235575.51194174",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "KAVA-10C"
-                                            },
-                                            {
-                                                "free": "10.00000000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "PVT-554"
-                                            },
-                                            {
-                                                "free": "9772.00009772",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "UPX-F3E"
-                                            },
-                                            {
-                                                "free": "221000.60154058",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "USDSB-1AC"
-                                            },
-                                            {
-                                                "free": "4372695.27174854",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "WRX-ED1"
-                                            },
-                                            {
-                                                "free": "19906076.03857030",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "PHB-2DF"
-                                            },
-                                            {
-                                                "free": "12696933.52213685",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "TRXB-2E6"
-                                            },
-                                            {
-                                                "free": "267793524.10892121",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BLINK-9C6"
-                                            },
-                                            {
-                                                "free": "3370.02600563",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "BTCB-1DE"
-                                            },
-                                            {
-                                                "free": "103467039.59858401",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "CBM-4B2"
-                                            },
-                                            {
-                                                "free": "4095309.32370000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "DUSK-45E"
-                                            },
-                                            {
-                                                "free": "82964636.96725382",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "FTM-A64"
-                                            },
-                                            {
-                                                "free": "182686435.86912742",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ANKR-E97"
-                                            },
-                                            {
-                                                "free": "5.00100000",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ETHBEAR-B2B"
-                                            },
-                                            {
-                                                "free": "0.00872511",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "ETHBULL-D33"
-                                            },
-                                            {
-                                                "free": "39286407.28666093",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "MATIC-84A"
-                                            },
-                                            {
-                                                "free": "20813555.86251786",
-                                                "frozen": "0.00000000",
-                                                "locked": "0.00000000",
-                                                "symbol": "MITH-C76"
-                                            }
-                                        ],
-                                        "flags": 0,
-                                        "public_key": [
-                                            3,
-                                            86,
-                                            224,
-                                            165,
-                                            128,
-                                            56,
-                                            154,
-                                            111,
-                                            210,
-                                            204,
-                                            145,
-                                            205,
-                                            82,
-                                            92,
-                                            109,
-                                            90,
-                                            77,
-                                            128,
-                                            84,
-                                            175,
-                                            112,
-                                            223,
-                                            23,
-                                            72,
-                                            78,
-                                            88,
-                                            103,
-                                            143,
-                                            159,
-                                            87,
-                                            74,
-                                            11,
-                                            77
-                                        ],
-                                        "sequence": 547072
-                                    }
-                                `);
-                        }
-                        break;
-
-                    case 'tokens':
-                        return JSON.parse(`
-                            [
-                                {
-                                    "mintable": true,
-                                    "name": "Africa Stable-Coin",
-                                    "original_symbol": "ABCD",
-                                    "owner": "bnb1ujvzeuft0ezf9fu4u0mk52t8mc7t8geyfkevms",
-                                    "symbol": "ABCD-5D8",
-                                    "total_supply": "3347000.00000000"
-                                },
-                                {
-                                    "mintable": false,
-                                    "name": "Aditus",
-                                    "original_symbol": "ADI",
-                                    "owner": "bnb1djdymfgzknmcsu9dzm9s0uavdszn0cl82z4hps",
-                                    "symbol": "ADI-6BB",
-                                    "total_supply": "750000000.00000000"
-                                },
-                                {
-                                    "mintable": false,
-                                    "name": "Aergo",
-                                    "original_symbol": "AERGO",
-                                    "owner": "bnb1llqhwwwmh878844tm3g8v47k0t7xtnhl4hggjl",
-                                    "symbol": "AERGO-46B",
-                                    "total_supply": "500000000.00000000"
-                                },
-                                {
-                                    "mintable": false,
-                                    "name": "Alaris",
-                                    "original_symbol": "ALA",
-                                    "owner": "bnb1pmdkvw6cquwylr46wcrl82xzmul0y2jpj5cwx7",
-                                    "symbol": "ALA-DCD",
-                                    "total_supply": "60000000.00000000"
-                                },
-                                {
-                                    "mintable": false,
-                                    "name": "ANKR",
-                                    "original_symbol": "ANKR",
-                                    "owner": "bnb1hvg059mkwleum35j6y2qjn4fvmgl7zxtlah4tn",
-                                    "symbol": "ANKR-E97",
-                                    "total_supply": "10000000000.00000000"
-                                },
-                                {
-                                    "mintable": false,
-                                    "name": "Aeron",
-                                    "original_symbol": "ARN",
-                                    "owner": "bnb1dq8ae0ayztqp99peggq5sygzf3n7u2ze4t0jne",
-                                    "symbol": "ARN-71B",
-                                    "total_supply": "20000000.00000000"
-                                },
-                                {
-                                    "mintable": true,
-                                    "name": "ARPA",
-                                    "original_symbol": "ARPA",
-                                    "owner": "bnb1mecnt25u3j9ne7th5av7hqvnmzvyrr7ny8hg8c",
-                                    "symbol": "ARPA-575",
-                                    "total_supply": "12000000.00000000"
-                                },
-                                {
-                                    "mintable": false,
-                                    "name": "Maecenas ART Token",
-                                    "original_symbol": "ART",
-                                    "owner": "bnb13plj9kycvcew5v0achpatnd5l5pacys9h0gu8l",
-                                    "symbol": "ART-3C9",
-                                    "total_supply": "100000000.00000000"
-                                },
-                                {
-                                    "mintable": true,
-                                    "name": "Atlas Protocol",
-                                    "original_symbol": "ATP",
-                                    "owner": "bnb1msw3avv894nlpeu0vn4qlkl0r65a3rp7gtz5hf",
-                                    "symbol": "ATP-38C",
-                                    "total_supply": "40000000.00000000"
-                                },
-                                {
-                                    "mintable": false,
-                                    "name": "Travala.com Token",
-                                    "original_symbol": "AVA",
-                                    "owner": "bnb1dm9c7gccgd07td5r69m50u8fg8danfgqvlhj6c",
-                                    "symbol": "AVA-645",
-                                    "total_supply": "61383832.00000000"
-                                }
-                            ]
-                        `);
-                }
-        }
-
-        // not found, address
-        return {txNums: 0, txArray: []}
-    }
-};
diff --git a/mock/ext-api-dyson/get/binance-rpc-commands2.js b/mock/ext-api-dyson/get/binance-rpc-commands2.js
new file mode 100644
index 0000000..a20a7f4
--- /dev/null
+++ b/mock/ext-api-dyson/get/binance-rpc-commands2.js
@@ -0,0 +1,143 @@
+/// Binance chain RPC Mock
+/// See:
+/// curl "http://localhost:3347/binance-rpc/v1/account/bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q"
+/// curl "http://localhost:3347/binance-rpc/v1/tokens?limit=1000&offset=0"
+/// curl "https://{binance_rpc}/v1/account/bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q"
+/// curl "https://{binance_rpc}/v1/tokens?limit=1000&offset=0"
+/// curl "http://localhost:8437/v2/binance/tokens/bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q?Authorization=Bearer"
+
+module.exports = {
+    path: '/binance-rpc/:version/:command1/:command2?',
+    template: function(params, query, body) {
+        switch (params.version) {
+            case 'v1':
+                switch (params.command1) {
+                    case 'account':
+                        switch (params.command2) {
+                            case 'bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q':
+                                return JSON.parse(`
+                                    {
+                                        "account_number": 273171,
+                                        "address": "bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q",
+                                        "balances": [
+                                            {
+                                                "free": "226.53110295",
+                                                "frozen": "0.00000000",
+                                                "locked": "0.00000000",
+                                                "symbol": "BNB"
+                                            },
+                                            {
+                                                "free": "2623.96917801",
+                                                "frozen": "0.00000000",
+                                                "locked": "0.00000000",
+                                                "symbol": "BUSD-BD1"
+                                            },
+                                            {
+                                                "free": "0.05000000",
+                                                "frozen": "0.00000000",
+                                                "locked": "0.00000000",
+                                                "symbol": "TWT-8C2"
+                                            }
+                                        ],
+                                        "flags": 0,
+                                        "public_key": [
+                                            2, 142, 117
+                                        ],
+                                        "sequence": 75
+                                    }
+                                `);
+                        }
+                        break;
+
+                    case 'tokens':
+                        return JSON.parse(`
+                            [
+                                {
+                                    "mintable": true,
+                                    "name": "Africa Stable-Coin",
+                                    "original_symbol": "ABCD",
+                                    "owner": "bnb1ujvzeuft0ezf9fu4u0mk52t8mc7t8geyfkevms",
+                                    "symbol": "ABCD-5D8",
+                                    "total_supply": "3347000.00000000"
+                                },
+                                {
+                                    "mintable": false,
+                                    "name": "Aditus",
+                                    "original_symbol": "ADI",
+                                    "owner": "bnb1djdymfgzknmcsu9dzm9s0uavdszn0cl82z4hps",
+                                    "symbol": "ADI-6BB",
+                                    "total_supply": "750000000.00000000"
+                                },
+                                {
+                                    "mintable": false,
+                                    "name": "Aergo",
+                                    "original_symbol": "AERGO",
+                                    "owner": "bnb1llqhwwwmh878844tm3g8v47k0t7xtnhl4hggjl",
+                                    "symbol": "AERGO-46B",
+                                    "total_supply": "500000000.00000000"
+                                },
+                                {
+                                    "mintable": false,
+                                    "name": "Alaris",
+                                    "original_symbol": "ALA",
+                                    "owner": "bnb1pmdkvw6cquwylr46wcrl82xzmul0y2jpj5cwx7",
+                                    "symbol": "ALA-DCD",
+                                    "total_supply": "60000000.00000000"
+                                },
+                                {
+                                    "mintable": false,
+                                    "name": "ANKR",
+                                    "original_symbol": "ANKR",
+                                    "owner": "bnb1hvg059mkwleum35j6y2qjn4fvmgl7zxtlah4tn",
+                                    "symbol": "ANKR-E97",
+                                    "total_supply": "10000000000.00000000"
+                                },
+                                {
+                                    "mintable": false,
+                                    "name": "Aeron",
+                                    "original_symbol": "ARN",
+                                    "owner": "bnb1dq8ae0ayztqp99peggq5sygzf3n7u2ze4t0jne",
+                                    "symbol": "ARN-71B",
+                                    "total_supply": "20000000.00000000"
+                                },
+                                {
+                                    "mintable": true,
+                                    "name": "ARPA",
+                                    "original_symbol": "ARPA",
+                                    "owner": "bnb1mecnt25u3j9ne7th5av7hqvnmzvyrr7ny8hg8c",
+                                    "symbol": "ARPA-575",
+                                    "total_supply": "12000000.00000000"
+                                },
+                                {
+                                    "mintable": false,
+                                    "name": "Maecenas ART Token",
+                                    "original_symbol": "ART",
+                                    "owner": "bnb13plj9kycvcew5v0achpatnd5l5pacys9h0gu8l",
+                                    "symbol": "ART-3C9",
+                                    "total_supply": "100000000.00000000"
+                                },
+                                {
+                                    "mintable": true,
+                                    "name": "Atlas Protocol",
+                                    "original_symbol": "ATP",
+                                    "owner": "bnb1msw3avv894nlpeu0vn4qlkl0r65a3rp7gtz5hf",
+                                    "symbol": "ATP-38C",
+                                    "total_supply": "40000000.00000000"
+                                },
+                                {
+                                    "mintable": false,
+                                    "name": "Travala.com Token",
+                                    "original_symbol": "AVA",
+                                    "owner": "bnb1dm9c7gccgd07td5r69m50u8fg8danfgqvlhj6c",
+                                    "symbol": "AVA-645",
+                                    "total_supply": "61383832.00000000"
+                                }
+                            ]
+                        `);
+                }
+        }
+
+        // not found, address
+        return {txNums: 0, txArray: []}
+    }
+};
diff --git a/mock/ext-api-dyson/get/binance-rpc-txs.js b/mock/ext-api-dyson/get/binance-rpc-txs.js
new file mode 100644
index 0000000..5813d3d
--- /dev/null
+++ b/mock/ext-api-dyson/get/binance-rpc-txs.js
@@ -0,0 +1,69 @@
+/// Binance chain block explorer mock transaction
+/// Returns:
+/// - Multi-transaction transaction for a specific address
+///   1. newman calls: http://localhost:8437/v1/binance/bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q
+///   2. Block Atlas calls internally : https://{binance_explorer}/api/v1/txs?address=bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q&page=1&rows=25&txType=TRANSFER
+///   3. Dyson response mock data: http://localhost:3347/binance-explorer/api/v1/txs?address=bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q&page=1&rows=25&txType=TRANSFER
+/// - empty response for other txHash'es
+
+// Example contains each type, BNB transfer, BEP2 transfer, BEP2 transfer as multisend transaction
+module.exports = {
+    path: '/binance-explorer/api/v1/txs',
+    template: function (params, query, body) {
+        return JSON.parse(`{
+    "txNums": 3,
+    "txArray": [
+        {
+            "txHash": "963D8C627DE1E739845C0AC0C0EAC3387B53806C77289ACB8C1653CD6D62C9CC",
+            "blockHeight": 84191249,
+            "txType": "TRANSFER",
+            "timeStamp": 1588086370574,
+            "fromAddr": "bnb14cjy0yl23xkf0hnw3ql295v8nghqstvlzkvqpl",
+            "toAddr": "bnb1mr5f97rx5wnkfcakx9fcpvljmx2s6kwqc08yur",
+            "value": 2800.00000000,
+            "txAsset": "TWT-8C2",
+            "txFee": 0.00037500,
+            "txAge": 446701,
+            "code": 0,
+            "log": "Msg 0: ",
+            "confirmBlocks": 0,
+            "memo": "",
+            "source": 0,
+            "hasChildren": 0
+        },
+        {
+            "txHash": "4577CB3B5B202696E9E0B093A6DA973C7DD9CBC6808DA1326872745C35F3C089",
+            "blockHeight": 84191216,
+            "txType": "TRANSFER",
+            "timeStamp": 1588086357686,
+            "fromAddr": "bnb1mr5f97rx5wnkfcakx9fcpvljmx2s6kwqc08yur",
+            "toAddr": "bnb14cjy0yl23xkf0hnw3ql295v8nghqstvlzkvqpl",
+            "value": 0.00040000,
+            "txAsset": "BNB",
+            "txFee": 0.00037500,
+            "txAge": 446714,
+            "code": 0,
+            "log": "Msg 0: ",
+            "confirmBlocks": 0,
+            "memo": "",
+            "source": 0,
+            "hasChildren": 0
+        },
+        {
+            "txHash": "FAD8C1C5E450BE5E0913B12007AAEACC307F8CFFAFFB0844A9F83155E1235C25",
+            "blockHeight": 80167666,
+            "txType": "TRANSFER",
+            "timeStamp": 1586464452922,
+            "txFee": 0.29970000,
+            "txAge": 2068619,
+            "code": 0,
+            "log": "Msg 0: ",
+            "confirmBlocks": 0,
+            "memo": "",
+            "source": 0,
+            "hasChildren": 1
+        }
+    ]
+}`)
+    }
+};
diff --git a/pkg/blockatlas/client.go b/pkg/blockatlas/client.go
index fd285e7..f3b3e2c 100644
--- a/pkg/blockatlas/client.go
+++ b/pkg/blockatlas/client.go
@@ -2,8 +2,10 @@ package blockatlas
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
 	"fmt"
+	"go.elastic.co/apm/module/apmhttp"
 	"io"
 	"io/ioutil"
 	"net/http"
@@ -55,13 +57,22 @@ var DefaultErrorHandler = func(res *http.Response, uri string) error {
 	return nil
 }
 
+func (r *Request) GetWithContext(result interface{}, path string, query url.Values, ctx context.Context) error {
+	var queryStr = ""
+	if query != nil {
+		queryStr = query.Encode()
+	}
+	uri := strings.Join([]string{r.GetBase(path), queryStr}, "?")
+	return r.Execute("GET", uri, nil, result, ctx)
+}
+
 func (r *Request) Get(result interface{}, path string, query url.Values) error {
 	var queryStr = ""
 	if query != nil {
 		queryStr = query.Encode()
 	}
 	uri := strings.Join([]string{r.GetBase(path), queryStr}, "?")
-	return r.Execute("GET", uri, nil, result)
+	return r.Execute("GET", uri, nil, result, context.Background())
 }
 
 func (r *Request) Post(result interface{}, path string, body interface{}) error {
@@ -70,10 +81,19 @@ func (r *Request) Post(result interface{}, path string, body interface{}) error
 		return err
 	}
 	uri := r.GetBase(path)
-	return r.Execute("POST", uri, buf, result)
+	return r.Execute("POST", uri, buf, result, context.Background())
 }
 
-func (r *Request) Execute(method string, url string, body io.Reader, result interface{}) error {
+func (r *Request) PostWithContext(result interface{}, path string, body interface{}, ctx context.Context) error {
+	buf, err := GetBody(body)
+	if err != nil {
+		return err
+	}
+	uri := r.GetBase(path)
+	return r.Execute("POST", uri, buf, result, ctx)
+}
+
+func (r *Request) Execute(method string, url string, body io.Reader, result interface{}, ctx context.Context) error {
 	req, err := http.NewRequest(method, url, body)
 	if err != nil {
 		return errors.E(err, errors.TypePlatformRequest)
@@ -83,7 +103,9 @@ func (r *Request) Execute(method string, url string, body io.Reader, result inte
 		req.Header.Set(key, value)
 	}
 
-	res, err := r.HttpClient.Do(req)
+	c := apmhttp.WrapClient(r.HttpClient)
+
+	res, err := c.Do(req.WithContext(ctx))
 	if err != nil {
 		return errors.E(err, errors.TypePlatformRequest)
 	}
diff --git a/pkg/blockatlas/clientcache.go b/pkg/blockatlas/clientcache.go
index a6e5116..670196d 100644
--- a/pkg/blockatlas/clientcache.go
+++ b/pkg/blockatlas/clientcache.go
@@ -1,6 +1,7 @@
 package blockatlas
 
 import (
+	"context"
 	"crypto/sha1"
 	"encoding/base64"
 	"encoding/json"
@@ -41,6 +42,21 @@ func (r *Request) PostWithCache(result interface{}, path string, body interface{
 	return err
 }
 
+func (r *Request) PostWithCacheAndContext(result interface{}, path string, body interface{}, cache time.Duration, ctx context.Context) error {
+	key := r.generateKey(path, nil, body)
+	err := memoryCache.getCache(key, result)
+	if err == nil {
+		return nil
+	}
+
+	err = r.PostWithContext(result, path, body, ctx)
+	if err != nil {
+		return err
+	}
+	memoryCache.setCache(key, result, cache)
+	return err
+}
+
 func (r *Request) GetWithCache(result interface{}, path string, query url.Values, cache time.Duration) error {
 	key := r.generateKey(path, query, nil)
 	err := memoryCache.getCache(key, result)
@@ -56,6 +72,21 @@ func (r *Request) GetWithCache(result interface{}, path string, query url.Values
 	return err
 }
 
+func (r *Request) GetWithCacheAndContext(result interface{}, path string, query url.Values, cache time.Duration, ctx context.Context) error {
+	key := r.generateKey(path, query, nil)
+	err := memoryCache.getCache(key, result)
+	if err == nil {
+		return nil
+	}
+
+	err = r.Get(result, path, query)
+	if err != nil {
+		return err
+	}
+	memoryCache.setCache(key, result, cache)
+	return err
+}
+
 //nolint
 func (mc *memCache) deleteCache(key string) {
 	mc.RLock()
diff --git a/pkg/blockatlas/jsonrpc.go b/pkg/blockatlas/jsonrpc.go
index fd5968d..0f2d79e 100644
--- a/pkg/blockatlas/jsonrpc.go
+++ b/pkg/blockatlas/jsonrpc.go
@@ -1,6 +1,7 @@
 package blockatlas
 
 import (
+	"context"
 	"encoding/json"
 
 	"github.com/trustwallet/blockatlas/pkg/errors"
@@ -67,6 +68,32 @@ func (r *Request) RpcCall(result interface{}, method string, params interface{})
 	return resp.GetObject(result)
 }
 
+func (r *Request) RpcCallWithContext(result interface{}, method string, params interface{}, ctx context.Context) error {
+
+	req := &RpcRequest{JsonRpc: JsonRpcVersion, Method: method, Params: params, Id: genId()}
+	var resp *RpcResponse
+	err := r.PostWithContext(&resp, "", req, ctx)
+	if err != nil {
+		return err
+	}
+	if resp.Error != nil {
+		return errors.E("RPC Call error", errors.Params{
+			"method":        method,
+			"error_code":    resp.Error.Code,
+			"error_message": resp.Error.Message})
+	}
+	return resp.GetObject(result)
+}
+
+func (r *Request) RpcBatchCallWithContext(requests RpcRequests, ctx context.Context) ([]RpcResponse, error) {
+	var resp []RpcResponse
+	err := r.PostWithContext(&resp, "", requests.fillDefaultValues(), ctx)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
 func (r *Request) RpcBatchCall(requests RpcRequests) ([]RpcResponse, error) {
 	var resp []RpcResponse
 	err := r.Post(&resp, "", requests.fillDefaultValues())
diff --git a/pkg/blockatlas/marshal.go b/pkg/blockatlas/marshal.go
index 91b9a54..8be4e22 100644
--- a/pkg/blockatlas/marshal.go
+++ b/pkg/blockatlas/marshal.go
@@ -47,7 +47,9 @@ func (t *Tx) UnmarshalJSON(data []byte) error {
 	default:
 		return errors.E("unsupported tx type", errors.Params{"type": t.Type})
 	}
-	if err := json.Unmarshal(raw, t.Meta); err != nil {
+
+	err := json.Unmarshal(raw, t.Meta)
+	if err != nil {
 		return err
 	}
 	return nil
diff --git a/pkg/blockatlas/tx.go b/pkg/blockatlas/tx.go
index e36e141..d0ec8a6 100644
--- a/pkg/blockatlas/tx.go
+++ b/pkg/blockatlas/tx.go
@@ -93,10 +93,9 @@ type (
 		Date int64 `json:"date"`
 		// Height of the block the transaction was included in
 		Block uint64 `json:"block"`
-		// Status of the transaction
+		// Status of the transaction e.g: "completed", "pending", "error"
 		Status Status `json:"status"`
-		// Empty if the transaction was successful,
-		// else error explaining why the transaction failed (optional)
+		// Empty if the transaction "completed" or "pending", else error explaining why the transaction failed (optional)
 		Error string `json:"error,omitempty"`
 		// Transaction nonce or sequence
 		Sequence uint64 `json:"sequence"`
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index 0540af0..221f407 100755
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -3,6 +3,7 @@ package logger
 import (
 	"fmt"
 	log "github.com/sirupsen/logrus"
+	"go.elastic.co/apm/module/apmlogrus"
 	"os"
 	"strings"
 )
@@ -12,6 +13,7 @@ type Params map[string]interface{}
 func InitLogger() {
 	log.SetFormatter(&log.TextFormatter{})
 	log.SetOutput(os.Stdout)
+	log.AddHook(&apmlogrus.Hook{})
 }
 
 type message struct {
diff --git a/pkg/numbers/decimal.go b/pkg/numbers/decimal.go
index 667470a..72e40ef 100644
--- a/pkg/numbers/decimal.go
+++ b/pkg/numbers/decimal.go
@@ -11,8 +11,16 @@ import (
 // "12.345" => "12345"
 // "0.0230" => "230"
 func DecimalToSatoshis(dec string) (string, error) {
-	out := strings.Replace(dec, ".", "", 1)
-	out = strings.TrimLeft(out, "0")
+	out := strings.TrimLeft(dec, " ")
+	out = strings.TrimRight(out, " ")
+	out = strings.Replace(out, ".", "", 1)
+	// trim left 0's but keep last
+	if l := len(out); l >= 2 {
+		out = strings.TrimLeft(out[:l-1], "0") + out[l-1:l]
+	}
+	if len(out) == 0 {
+		return "", errors.E("Invalid empty input", errors.Params{"dec": dec, "dec_trimmed": out})
+	}
 	for _, c := range out {
 		if !unicode.IsNumber(c) {
 			return "", errors.E("not a number", errors.Params{"dec": dec, "c": c})
diff --git a/pkg/numbers/number.go b/pkg/numbers/number.go
index c36be11..7ac3208 100644
--- a/pkg/numbers/number.go
+++ b/pkg/numbers/number.go
@@ -32,10 +32,21 @@ func Float64toPrecision(num float64, precision int) float64 {
 	return float64(Round(num*output)) / output
 }
 
+// 0.1010 => "0.101"
 func Float64toString(num float64) string {
 	return strconv.FormatFloat(num, 'f', -1, 64)
 }
 
+// "0.00037500" => 0.000375, "non-string-number" => 0
+func StringNumberToFloat64(str string) (float64,  error) {
+	value, err := strconv.ParseFloat(str, 64)
+	if err != nil {
+		return 0, err
+	} else {
+		return value, nil
+	}
+}
+
 func FromDecimal(dec string) string {
 	v, err := DecimalToSatoshis(dec)
 	if err != nil {
diff --git a/platform/binance/base.go b/platform/binance/base.go
index 55f9fb3..678bf0f 100644
--- a/platform/binance/base.go
+++ b/platform/binance/base.go
@@ -6,18 +6,18 @@ import (
 )
 
 type Platform struct {
-	client    Client
-	dexClient DexClient
+	rpcClient      Client
+	explorerClient ExplorerClient
 }
 
-func Init(api, dex string) *Platform {
-	p := &Platform{
-		client:    Client{blockatlas.InitClient(api)},
-		dexClient: DexClient{blockatlas.InitClient(dex)},
+func Init(rpcApi, explorerApi string) *Platform {
+	p := Platform{
+		rpcClient:      Client{blockatlas.InitClient(rpcApi)},
+		explorerClient: ExplorerClient{blockatlas.InitClient(explorerApi)},
 	}
-	p.client.ErrorHandler = getHTTPError
-	p.dexClient.ErrorHandler = getHTTPError
-	return p
+	p.rpcClient.ErrorHandler = handleHTTPError
+	p.explorerClient.ErrorHandler = handleHTTPError
+	return &p
 }
 
 func (p *Platform) Coin() coin.Coin {
diff --git a/platform/binance/block.go b/platform/binance/block.go
index c4647d8..90b931d 100644
--- a/platform/binance/block.go
+++ b/platform/binance/block.go
@@ -2,38 +2,74 @@ package binance
 
 import (
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
-	"github.com/trustwallet/blockatlas/pkg/errors"
+	"github.com/trustwallet/blockatlas/pkg/numbers"
+	"time"
 )
 
 func (p *Platform) CurrentBlockNumber() (int64, error) {
-	// No native function to get height in explorer API
-	// Workaround: Request list of blocks
-	// and return number of the newest one
-	list, err := p.client.GetBlockList(1)
+	info, err := p.rpcClient.fetchNodeInfo()
 	if err != nil {
 		return 0, err
 	}
-	if len(list.BlockArray) == 0 {
-		return 0, errors.E("no block descriptor found", errors.TypePlatformApi)
-	}
-	return list.BlockArray[0].BlockHeight, nil
+
+	return info.SyncInfo.LatestBlockHeight, nil
 }
 
 func (p *Platform) GetBlockByNumber(num int64) (*blockatlas.Block, error) {
-	srcTxs, err := p.client.GetBlockByNumber(num)
+	blockTransactions, err := p.rpcClient.fetchBlockTransactions(num)
 	if err != nil {
 		return nil, err
 	}
 
-	var txs blockatlas.TxPage
-	childTxs, err := p.getTxChildChan(srcTxs.Txs)
-	if err == nil {
-		txs = NormalizeTxs(childTxs, "", "")
-	} else {
-		txs = NormalizeTxs(srcTxs.Txs, "", "")
+	explorerTransactions := make([]ExplorerTxs, 0, len(blockTransactions))
+	for _, tx := range blockTransactions {
+		explorerTransactions = append(explorerTransactions, normalizeTxsToExplorer(tx))
+	}
+
+	var normalizedTxs []blockatlas.Tx
+	for _, tx := range explorerTransactions {
+		normalizedTx := normalizeTx(tx, "")
+		if normalizedTx == nil {
+			continue
+		}
+		normalizedTxs = append(normalizedTxs, normalizedTx...)
+	}
+
+	return &blockatlas.Block{Number: num, Txs: normalizedTxs}, nil
+}
+
+func normalizeTxsToExplorer(txV2 TxV2) ExplorerTxs {
+	tx := ExplorerTxs{
+		TxAsset:     txV2.Asset,
+		Code:        txV2.Code,
+		FromAddr:    txV2.FromAddr,
+		TxHash:      txV2.TxHash,
+		Memo:        txV2.Memo,
+		ToAddr:      txV2.ToAddr,
+		TxType:      txV2.Type,
+		BlockHeight: txV2.BlockHeight,
+	}
+
+	if value, err := numbers.StringNumberToFloat64(txV2.Value); err == nil {
+		tx.Value = value
+	}
+	if txV2.Fee == "" && len(txV2.SubTransactions) > 1 {
+		txV2.Fee = txV2.SubTransactions[0].Fee
+	}
+	if fee, err := numbers.StringNumberToFloat64(txV2.Fee); err == nil {
+		tx.TxFee = fee
+	}
+	if len(txV2.SubTransactions) > 0 {
+		tx.HasChildren = 1
+	}
+	if t, err := time.Parse(time.RFC3339, txV2.Timestamp); err == nil {
+		tx.Timestamp = t.Unix()
+	}
+
+	mts := make([]MultiTransfer, len(txV2.SubTransactions))
+	for i, st := range txV2.SubTransactions {
+		mts[i] = MultiTransfer{Amount: st.Value, Asset: st.Asset, From: st.FromAddr, To: st.ToAddr}
 	}
-	return &blockatlas.Block{
-		Number: num,
-		Txs:    txs,
-	}, nil
+	tx.MultisendTransfers = mts
+	return tx
 }
diff --git a/platform/binance/client.go b/platform/binance/client.go
index cd1b2f1..b920034 100644
--- a/platform/binance/client.go
+++ b/platform/binance/client.go
@@ -2,83 +2,70 @@ package binance
 
 import (
 	"encoding/json"
+	"fmt"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 	"github.com/trustwallet/blockatlas/pkg/errors"
 	"github.com/trustwallet/blockatlas/pkg/logger"
 	"net/http"
 	"net/url"
-	"strconv"
+	"time"
 )
 
-// TODO Headers + rate limiting
-
 type Client struct {
 	blockatlas.Request
 }
 
-func (c *Client) GetBlockList(count int) (*BlockList, error) {
-	result := new(BlockList)
-	query := url.Values{"rows": {strconv.Itoa(count)}, "page": {"1"}}
-	err := c.Get(result, "blocks", query)
+const tokensLimit = "1000"
+
+func (c *Client) fetchNodeInfo() (*NodeInfo, error) {
+	result := new(NodeInfo)
+	err := c.Get(result, "v1/node-info", nil)
 	return result, err
 }
 
-func (c *Client) GetBlockByNumber(num int64) (*TxPage, error) {
-	stx := new(TxPage)
-	query := url.Values{
-		"blockHeight": {strconv.FormatInt(num, 10)},
-		// Only first 100 transactions of block returned
-		// Shouldn't be a problem at the current transaction rate
-		"rows": {"100"},
-		"page": {"1"},
-	}
-	err := c.Get(stx, "txs", query)
-	return stx, err
+func (c *Client) fetchBlockTransactions(num int64) ([]TxV2, error) {
+	stx := new(BlockTransactions)
+	err := c.Get(stx, fmt.Sprintf("v2/transactions-in-block/%d", num), nil)
+	return stx.Txs, err
+}
+
+func (c *Client) fetchAccountMetadata(address string) (*Account, error) {
+	var result Account
+	err := c.Get(&result, fmt.Sprintf("v1/account/%s", address), nil)
+	return &result, err
 }
 
-func (c *Client) GetTxsOfAddress(address string, token string) (*TxPage, error) {
-	stx := new(TxPage)
-	query := url.Values{"address": {address}, "rows": {"20"}, "page": {"1"}, "txAsset": {token}, "txType": {"TRANSFER"}}
-	err := c.Get(stx, "txs", query)
-	return stx, err
+func (c *Client) fetchTokens() (*TokenList, error) {
+	stp := new(TokenList)
+	query := url.Values{"limit": {tokensLimit}}
+	err := c.GetWithCache(stp, "v1/tokens", query, time.Minute*1)
+	return stp, err
 }
 
-func (c *Client) GetTx(hash string) (stx Tx, err error) {
-	err = c.Get(&stx, "tx", url.Values{"txHash": {hash}})
-	return
+func (c *Client) fetchTransactionHash(hash string) (*TxHashRPC, error) {
+	var result TxHashRPC
+	err := c.Get(&result, fmt.Sprintf("v1/tx/%s", hash), url.Values{"format": {"json"}})
+	return &result, err
 }
 
-func getHTTPError(res *http.Response, desc string) error {
+func handleHTTPError(res *http.Response, desc string) error {
 	switch res.StatusCode {
 	case http.StatusBadRequest:
-		return getAPIError(res, desc)
+		return handleAPIError(res, desc)
 	case http.StatusNotFound:
 		return blockatlas.ErrNotFound
 	case http.StatusOK:
 		return nil
 	default:
-		return errors.E("getHTTPError error", errors.Params{"status": res.Status})
+		return errors.E("handleHTTPError error", errors.Params{"status": res.Status})
 	}
 }
 
-func getAPIError(res *http.Response, desc string) error {
-	var sErr Error
-	err := json.NewDecoder(res.Body).Decode(&sErr)
-	if err != nil {
-		err = errors.E(err, errors.TypePlatformUnmarshal, errors.Params{"desc": desc})
-		logger.Error(err, "Binance: Failed to decode error response")
-		return blockatlas.ErrSourceConn
-	}
-
-	switch sErr.Message {
-	case "address is not valid":
+func handleAPIError(res *http.Response, desc string) error {
+	var e Error
+	if json.NewDecoder(res.Body).Decode(&e) == nil && e.Message == "address is not valid" {
 		return blockatlas.ErrInvalidAddr
 	}
-
-	logger.Error("Binance: Failed", desc, err, logger.Params{
-		"status":  res.StatusCode,
-		"code":    sErr.Code,
-		"message": sErr.Message,
-	})
+	logger.Error(desc, logger.Params{"status": res.StatusCode, "code": e.Code, "message": e.Message})
 	return blockatlas.ErrSourceConn
 }
diff --git a/platform/binance/dex_client.go b/platform/binance/dex_client.go
deleted file mode 100644
index 094fcde..0000000
--- a/platform/binance/dex_client.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package binance
-
-import (
-	"fmt"
-	"github.com/trustwallet/blockatlas/pkg/blockatlas"
-	"net/url"
-	"time"
-)
-
-// TODO Headers + rate limiting
-
-type DexClient struct {
-	blockatlas.Request
-}
-
-func (c *DexClient) GetAccountMetadata(address string) (account *Account, err error) {
-	path := fmt.Sprintf("v1/account/%s", address)
-	err = c.Get(&account, path, nil)
-	return account, err
-}
-
-func (c *DexClient) GetTokens() (*TokenPage, error) {
-	stp := new(TokenPage)
-	query := url.Values{"limit": {"1000"}, "offset": {"0"}}
-	err := c.GetWithCache(stp, "v1/tokens", query, time.Hour*1)
-	return stp, err
-}
diff --git a/platform/binance/explorer_client.go b/platform/binance/explorer_client.go
new file mode 100644
index 0000000..1c9bce4
--- /dev/null
+++ b/platform/binance/explorer_client.go
@@ -0,0 +1,32 @@
+package binance
+
+import (
+	"github.com/trustwallet/blockatlas/coin"
+	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"net/url"
+)
+
+type ExplorerClient struct {
+	blockatlas.Request
+}
+
+const (
+	explorerRows = "25"
+	explorerPage = "1"
+)
+
+func (c *ExplorerClient) getTxsOfAddress(address, token string) (ExplorerResponse, error) {
+	result := new(ExplorerResponse)
+	if token == "" {
+		token = coin.Binance().Symbol
+	}
+	query := url.Values{
+		"address": {address},
+		"rows":    {explorerRows},
+		"page":    {explorerPage},
+		"txType":  {string(TxTransfer)},
+		"txAsset": {token},
+	}
+	err := c.Get(result, "v1/txs", query)
+	return *result, err
+}
diff --git a/platform/binance/model.go b/platform/binance/model.go
index 8523d09..1a350fd 100644
--- a/platform/binance/model.go
+++ b/platform/binance/model.go
@@ -1,229 +1,250 @@
 package binance
 
 import (
-	"encoding/json"
 	"fmt"
 	"github.com/trustwallet/blockatlas/coin"
-	"github.com/trustwallet/blockatlas/pkg/errors"
+	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 	"github.com/trustwallet/blockatlas/pkg/numbers"
-	"math"
 	"strconv"
-	"strings"
 )
 
-type Account struct {
-	AccountNumber int       `json:"account_number"`
-	Address       string    `json:"address"`
-	Balances      []Balance `json:"balances"`
-	PublicKey     []byte    `json:"public_key"`
-	Sequence      uint64    `json:"sequence"`
-}
+const (
+	TxTransfer              TxType                  = "TRANSFER"       // e.g: BNB, TWT-8C2
+	SingleTransferOperation ExplorerTransactionType = "singleTransfer" // e.g: BNB, TWT-8C2
+	MultiTransferOperation  ExplorerTransactionType = "multiTransfer"  // e.g [BNB, BNB], [TWT-8C2, TWT-8C2]
+)
 
-type Balance struct {
-	Free   string `json:"free"`
-	Frozen string `json:"frozen"`
-	Locked string `json:"locked"`
-	Symbol string `json:"symbol"`
-}
+type (
+	TxType                  string
+	ExplorerTransactionType string
 
-type Error struct {
-	Code    int64  `json:"code"`
-	Message string `json:"message"`
-}
+	Account struct {
+		AccountNumber int       `json:"account_number"`
+		Address       string    `json:"address"`
+		Balances      []Balance `json:"balances"`
+		PublicKey     []byte    `json:"public_key"`
+		Sequence      uint64    `json:"sequence"`
+	}
 
-type BlockDescriptor struct {
-	BlockHeight int64  `json:"blockHeight"`
-	BlockHash   string `json:"blockHash"`
-	TxNum       int    `json:"txNum"`
-}
+	Balance struct {
+		Free   string `json:"free"`
+		Frozen string `json:"frozen"`
+		Locked string `json:"locked"`
+		Symbol string `json:"symbol"`
+	}
 
-type BlockList struct {
-	BlockArray []BlockDescriptor `json:"blockArray"`
-}
+	Error struct {
+		Code    int64  `json:"code"`
+		Message string `json:"message"`
+	}
 
-type TxType string
+	NodeInfo struct {
+		SyncInfo SyncInfo `json:"sync_info"`
+	}
 
-const (
-	TxTransfer    TxType = "TRANSFER"
-	TxNewOrder    TxType = "NEW_ORDER"
-	TxCancelOrder TxType = "CANCEL_ORDER"
-)
+	SyncInfo struct {
+		LatestBlockHeight int64 `json:"latest_block_height"`
+	}
 
-type Tx struct {
-	BlockHeight   uint64      `json:"blockHeight"`
-	Type          TxType      `json:"txType"`
-	Code          int         `json:"code"`
-	ConfirmBlocks int         `json:"confirmBlocks"`
-	Data          string      `json:"data"`
-	FromAddr      string      `json:"fromAddr"`
-	OrderID       string      `json:"orderId"`
-	Timestamp     int64       `json:"timeStamp"`
-	ToAddr        string      `json:"toAddr"`
-	Age           int64       `json:"txAge"`
-	Asset         string      `json:"txAsset"`
-	Fee           json.Number `json:"txFee"`
-	Hash          string      `json:"txHash"`
-	Value         json.Number `json:"value"`
-	Memo          string      `json:"memo"`
-	HasChildren   int         `json:"hasChildren"`
-	SubTxsDto     SubTxsDto   `json:"subTxsDto"`
-}
+	Transactions struct {
+		Total int  `json:"total"`
+		Txs   []Tx `json:"tx"`
+	}
 
-type SubTxsDto struct {
-	TotalNum     uint   `json:"totalNum"`
-	SubTxDtoList SubTxs `json:"subTxDtoList"`
-}
+	Tx struct {
+		Asset       string `json:"txAsset"`
+		BlockHeight uint64 `json:"blockHeight"`
+		Code        int    `json:"code"`
+		Data        string `json:"data"`
+		Fee         string `json:"txFee"`
+		FromAddr    string `json:"fromAddr"`
+		Memo        string `json:"memo"`
+		OrderID     string `json:"orderId"`
+		Sequence    uint64 `json:"sequence"`
+		Source      int    `json:"source"`
+		Timestamp   string `json:"timeStamp"`
+		ToAddr      string `json:"toAddr"`
+		TxHash      string `json:"txHash"`
+		Type        TxType `json:"txType"`
+		Value       string `json:"value"`
+	}
 
-type SubTx struct {
-	Hash     string      `json:"hash"`
-	Height   uint64      `json:"height"`
-	Type     TxType      `json:"type"`
-	Value    json.Number `json:"value"`
-	Asset    string      `json:"asset"`
-	FromAddr string      `json:"fromAddr"`
-	ToAddr   string      `json:"toAddr"`
-	Fee      json.Number `json:"fee"`
-}
+	BlockTransactions struct {
+		BlockHeight int64  `json:"blockHeight"`
+		Txs         []TxV2 `json:"tx"`
+	}
 
-type SubTxs []SubTx
+	TxV2 struct {
+		Tx
+		OrderID         string  `json:"orderId"`         // Optional. Available when the transaction type is NEW_ORDER
+		SubTransactions []SubTx `json:"subTransactions"` // Optional. Available when the transaction has sub-transactions, such as multi-send transaction or a transaction have multiple assets
+	}
 
-func (subTxs *SubTxs) getTxs() (txs []Tx) {
-	mapTx := map[string]Tx{}
-	for _, subTx := range *subTxs {
-		key := subTx.ToAddr + subTx.Asset
-		tx, ok := mapTx[key]
-		if !ok {
-			mapTx[key] = subTx.toTx()
-			continue
-		}
-		txValue, err := tx.Value.Float64()
-		if err != nil {
-			txValue = 0
-		}
-		subTxValue, err := subTx.Value.Float64()
-		if err != nil {
-			subTxValue = 0
-		}
-		value := numbers.Float64toString(txValue + subTxValue)
-		tx.Value = json.Number(value)
-		mapTx[key] = tx
+	SubTx struct {
+		Asset    string `json:"txAsset"`
+		Height   uint64 `json:"blockHeight"`
+		Fee      string `json:"txFee"`
+		FromAddr string `json:"fromAddr"`
+		Hash     string `json:"txHash"`
+		ToAddr   string `json:"toAddr"`
+		Type     TxType `json:"txType"`
+		Value    string `json:"value"`
 	}
-	for _, tx := range mapTx {
-		txs = append(txs, tx)
+
+	TokenList []Token
+
+	Token struct {
+		Name           string `json:"name"`
+		OriginalSymbol string `json:"original_symbol"`
+		Owner          string `json:"owner"`
+		Symbol         string `json:"symbol"`
+		TotalSupply    string `json:"total_supply"`
 	}
-	return
-}
 
-func (subTx *SubTx) toTx() Tx {
-	return Tx{
-		Hash:        subTx.Hash,
-		BlockHeight: subTx.Height,
-		Type:        TxTransfer,
-		FromAddr:    subTx.FromAddr,
-		ToAddr:      subTx.ToAddr,
-		Asset:       subTx.Asset,
-		Fee:         subTx.Fee,
-		Value:       subTx.Value,
-		HasChildren: 0,
+	// Transaction response from Explorer
+	ExplorerResponse struct {
+		Nums int           `json:"txNums"`
+		Txs  []ExplorerTxs `json:"txArray"`
 	}
-}
 
-func (tx *Tx) containAddress(address string) bool {
-	if len(address) == 0 {
-		return true
+	ExplorerTxs struct {
+		BlockHeight        uint64          `json:"blockHeight"`
+		Code               int             `json:"code"`
+		FromAddr           string          `json:"fromAddr"`
+		HasChildren        int             `json:"hasChildren"`
+		Memo               string          `json:"memo"`
+		MultisendTransfers []MultiTransfer `json:"subTxsDto"` // Not part of response, added from hash info tx for simplifying logic
+		Timestamp          int64           `json:"timeStamp"`
+		ToAddr             string          `json:"toAddr"`
+		TxFee              float64         `json:"txFee"`
+		TxHash             string          `json:"txHash"`
+		TxType             TxType          `json:"txType"`
+		Value              float64         `json:"value"`
+		TxAsset            string          `json:"txAsset"`
 	}
-	if tx.FromAddr == address {
-		return true
+
+	TxHashRPC struct {
+		Hash string   `json:"hash"`
+		Tx   TxHashTx `json:"tx"`
 	}
-	if tx.ToAddr == address {
-		return true
+
+	TxHashTx struct {
+		Value Value `json:"value"`
 	}
-	return false
-}
 
-func (tx *Tx) getFee() string {
-	fee := "0"
-	feeNumber, err := tx.Fee.Float64()
-	if err == nil && feeNumber > 0 {
-		fee = numbers.DecimalExp(string(tx.Fee), 8)
+	Value struct {
+		Msg []Msg `json:"msg"`
 	}
-	return fee
-}
 
-func (tx *Tx) getData() (Data, error) {
-	rawIn := json.RawMessage(tx.Data)
-	b, err := rawIn.MarshalJSON()
-	if err != nil {
-		return Data{}, errors.E(err, "getData MarshalJSON", errors.Params{"data": tx.Data})
+	Msg struct {
+		Value MsgValue `json:"value"`
 	}
 
-	var data Data
-	err = json.Unmarshal(b, &data)
-	if err != nil {
-		return Data{}, errors.E(err, "getData Unmarshal", errors.Params{"data": string(b)})
+	MsgValue struct {
+		Inputs  []Input  `json:"inputs"`
+		Outputs []Output `json:"outputs"`
 	}
 
-	symbols := strings.Split(data.OrderData.Symbol, "_")
-	if len(symbols) < 2 {
-		return data, nil
+	Input struct {
+		Address string `json:"address"`
 	}
 
-	data.OrderData.Base = symbols[0]
-	data.OrderData.Quote = symbols[1]
-	return data, nil
-}
+	Output struct {
+		Address string  `json:"address"`
+		Coins   []Coins `json:"coins"`
+	}
+	Coins struct {
+		Amount string `json:"amount"`
+		Denom  string `json:"denom"`
+	}
 
-type Data struct {
-	OrderData OrderData `json:"orderData"`
-}
+	MultiTransfer struct {
+		Amount string `json:"amount"` // Float string ind decimal point
+		Asset  string `json:"asset"`
+		From   string `json:"from"`
+		To     string `json:"to"`
+	}
+)
 
-type OrderData struct {
-	Symbol   string      `json:"symbol"`
-	Base     string      `json:"-"`
-	Quote    string      `json:"-"`
-	Quantity interface{} `json:"quantity"`
-	Price    interface{} `json:"price"`
+func extractMultiTransfers(messages Value) []MultiTransfer {
+	var extracted = make([]MultiTransfer, 0)
+	for _, msg := range messages.Msg {
+		var tr MultiTransfer
+		tr.From = msg.Value.Inputs[0].Address // Assumed multisend transfer has one input, never seen multiple
+		for _, output := range msg.Value.Outputs {
+			tr.To = output.Address
+			for _, c := range output.Coins {
+				tr.Amount = c.Amount
+				tr.Asset = c.Denom
+				extracted = append(extracted, tr)
+			}
+		}
+	}
+	return extracted
 }
 
-func (od OrderData) GetVolume() (int64, bool) {
-	price, ok := od.GetPrice()
-	if !ok {
-		return 0, false
+// Get explorer transfer fee converted to decimal expression
+func (tx *Tx) getFee() string {
+	if _, err := strconv.ParseFloat(tx.Fee, 64); err == nil {
+		return numbers.DecimalExp(tx.Fee, int(coin.Binance().Decimals))
 	}
-	quantity, ok := od.GetQuantity()
-	if !ok {
-		return 0, false
+	return "0"
+}
+
+// Converts explorer transfer fee to amount in decimal expression
+func (tx *ExplorerTxs) getDexFee() blockatlas.Amount {
+	if tx.TxFee > 0 {
+		return blockatlas.Amount(numbers.DecimalExp(numbers.Float64toString(tx.TxFee), int(coin.Binance().Decimals)))
+	} else {
+		return blockatlas.Amount(0)
 	}
-	return removeFloatPoint(price * quantity), true
 }
 
-func (od OrderData) GetPrice() (float64, bool) {
-	return convertValue(od.Price)
+// Get Explorer transfer status based on transfer code
+func (tx *ExplorerTxs) getStatus() blockatlas.Status {
+	switch tx.Code {
+	case 0:
+		return blockatlas.StatusCompleted
+	default:
+		return blockatlas.StatusError
+	}
 }
 
-func (od OrderData) GetQuantity() (float64, bool) {
-	return convertValue(od.Quantity)
+func (tx *ExplorerTxs) getDexValue() blockatlas.Amount {
+	val := numbers.DecimalExp(numbers.Float64toString(tx.Value), int(coin.Binance().Decimals))
+	return blockatlas.Amount(val)
 }
 
-type TxPage struct {
-	Nums int  `json:"txNums"`
-	Txs  []Tx `json:"txArray"`
+// Determines transaction status
+func (tx *Tx) getStatus() blockatlas.Status {
+	switch tx.Code {
+	case 0:
+		return blockatlas.StatusCompleted
+	default:
+		return blockatlas.StatusError
+	}
 }
 
-type Token struct {
-	Mintable       bool   `json:"mintable"`
-	Name           string `json:"name"`
-	OriginalSymbol string `json:"original_symbol"`
-	Owner          string `json:"owner"`
-	Symbol         string `json:"symbol"`
-	TotalSupply    string `json:"total_supply"`
+// Get explorer transfer error message if transaction failed
+func (tx *ExplorerTxs) getError() string {
+	switch tx.getStatus() {
+	case blockatlas.StatusCompleted:
+		return ""
+	default:
+		return "error"
+	}
 }
 
-type TokenPage []Token
+func (tx *Tx) containAddress(address string) bool {
+	if len(address) == 0 || tx.FromAddr == address || tx.ToAddr == address {
+		return true
+	}
+	return false
+}
 
 // findToken find a token into a token list
-func (a TokenPage) findToken(symbol string) *Token {
-	for _, t := range a {
+func (page TokenList) findToken(symbol string) *Token {
+	for _, t := range page {
 		if t.Symbol == symbol {
 			return &t
 		}
@@ -240,33 +261,35 @@ func (balance *Balance) isAllZeroBalance() bool {
 		}
 	}
 	return true
-
 }
 
 func (e *Error) Error() string {
 	return fmt.Sprintf("%d: %s", e.Code, e.Message)
 }
 
-func removeFloatPoint(value float64) int64 {
-	bnbCoin := coin.Coins[coin.BNB]
-	pow := math.Pow(10, float64(bnbCoin.Decimals))
-	return int64(value * pow)
+// Determines Explorer transaction direction relatively to address
+func (tx *ExplorerTxs) getDirection(address string) blockatlas.Direction {
+	if address == "" {
+		return ""
+	}
+
+	if tx.FromAddr == address && tx.ToAddr == address {
+		return blockatlas.DirectionSelf
+	}
+	if tx.FromAddr == address && tx.ToAddr != address {
+		return blockatlas.DirectionOutgoing
+	}
+
+	return blockatlas.DirectionIncoming
 }
 
-func convertValue(value interface{}) (float64, bool) {
-	result := 0.0
-	switch v := value.(type) {
-	case float64:
-		result = v
-	case int:
-		result = float64(v)
-	case string:
-		f, err := strconv.ParseFloat(v, 64)
-		if err == nil {
-			result = f
-		}
-	default:
-		return result, false
+// Determines Explorer transaction type
+func (tx *ExplorerTxs) getTransactionType() ExplorerTransactionType {
+	var txType ExplorerTransactionType
+	if tx.HasChildren == 1 {
+		txType = MultiTransferOperation
+	} else {
+		txType = SingleTransferOperation
 	}
-	return result, true
+	return txType
 }
diff --git a/platform/binance/token.go b/platform/binance/token.go
index 0e63334..425d40f 100644
--- a/platform/binance/token.go
+++ b/platform/binance/token.go
@@ -7,50 +7,52 @@ import (
 )
 
 func (p *Platform) GetTokenListByAddress(address string) (blockatlas.TokenPage, error) {
-	account, err := p.dexClient.GetAccountMetadata(address)
+	account, err := p.rpcClient.fetchAccountMetadata(address)
 	if err != nil || len(account.Balances) == 0 {
 		return []blockatlas.Token{}, nil
 	}
-	tokens, err := p.dexClient.GetTokens()
+	tokens, err := p.rpcClient.fetchTokens()
 	if err != nil {
 		return nil, err
 	}
-	return NormalizeTokens(account.Balances, tokens), nil
+	return normalizeTokens(account.Balances, tokens), nil
 }
 
-// NormalizeToken converts a Binance token into the generic model
-func NormalizeToken(srcToken *Balance, tokens *TokenPage) (t blockatlas.Token, ok bool) {
+// NormalizeTxs converts multiple Binance tokens
+func normalizeTokens(srcBalance []Balance, tokens *TokenList) []blockatlas.Token {
+	tokensList := make([]blockatlas.Token, 0, len(srcBalance))
+	for _, srcToken := range srcBalance {
+		token, ok := normalizeToken(&srcToken, tokens)
+		if !ok {
+			continue
+		}
+		tokensList = append(tokensList, token)
+	}
+	return tokensList
+}
+
+// normalizeToken converts a Binance token into the generic model
+func normalizeToken(srcToken *Balance, tokens *TokenList) (blockatlas.Token, bool) {
+	var result blockatlas.Token
 	if srcToken.isAllZeroBalance() {
-		return t, false
+		return result, false
 	}
 
-	tk := tokens.findToken(srcToken.Symbol)
-	if tk == nil {
-		return t, false
+	token := tokens.findToken(srcToken.Symbol)
+	if token == nil {
+		return result, false
 	}
 
-	t = blockatlas.Token{
-		Name:     tk.Name,
-		Symbol:   tk.OriginalSymbol,
-		TokenID:  tk.Symbol,
+	result = blockatlas.Token{
+		Name:     token.Name,
+		Symbol:   token.OriginalSymbol,
+		TokenID:  token.Symbol,
 		Coin:     coin.BNB,
-		Decimals: uint(decimalPlaces(tk.TotalSupply)),
+		Decimals: uint(decimalPlaces(token.TotalSupply)),
 		Type:     blockatlas.TokenTypeBEP2,
 	}
 
-	return t, true
-}
-
-// NormalizeTxs converts multiple Binance tokens
-func NormalizeTokens(srcBalance []Balance, tokens *TokenPage) (tokenPage []blockatlas.Token) {
-	for _, srcToken := range srcBalance {
-		token, ok := NormalizeToken(&srcToken, tokens)
-		if !ok {
-			continue
-		}
-		tokenPage = append(tokenPage, token)
-	}
-	return
+	return result, true
 }
 
 // decimalPlaces count the decimals places.
diff --git a/platform/binance/transaction.go b/platform/binance/transaction.go
index ba8ab21..84581cb 100644
--- a/platform/binance/transaction.go
+++ b/platform/binance/transaction.go
@@ -1,190 +1,200 @@
 package binance
 
 import (
+	"github.com/trustwallet/blockatlas/coin"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
-	"github.com/trustwallet/blockatlas/pkg/logger"
 	"github.com/trustwallet/blockatlas/pkg/numbers"
+	"strconv"
 	"strings"
 	"sync"
-
-	"github.com/trustwallet/blockatlas/coin"
 )
 
+const emptyToken = ""
+
 func (p *Platform) GetTxsByAddress(address string) (blockatlas.TxPage, error) {
-	// Endpoint supports queries without token query parameter
-	return p.GetTokenTxsByAddress(address, p.Coin().Symbol)
+	explorerResponse, err := p.GetTokenTxsByAddress(address, emptyToken)
+	if err != nil {
+		return nil, err
+	}
+	return filterTxsByType(explorerResponse, blockatlas.TxTransfer), nil
 }
 
-func (p *Platform) GetTokenTxsByAddress(address string, token string) (blockatlas.TxPage, error) {
-	srcTxs, err := p.client.GetTxsOfAddress(address, token)
+func (p *Platform) GetTokenTxsByAddress(address, token string) (blockatlas.TxPage, error) {
+	explorerResponse, err := p.explorerClient.getTxsOfAddress(address, token)
 	if err != nil {
 		return nil, err
 	}
-	txs, err := p.getTxChildChan(srcTxs.Txs)
+
+	explorerTxs, err := p.addTxDetails(explorerResponse.Txs)
 	if err != nil {
 		return nil, err
 	}
-	return NormalizeTxs(txs, token, address), nil
+
+	return normalizeTxs(explorerTxs, address), nil
 }
 
-// getTxChildChan get all child assets from a tx
-func (p *Platform) getTxChildChan(srcTxs []Tx) ([]Tx, error) {
-	txs := make([]Tx, 0)
-	var wg sync.WaitGroup
-	out := make(chan Tx, len(srcTxs))
-	for _, srcTx := range srcTxs {
-		if srcTx.HasChildren != 1 {
-			// Return the same transaction if doesn't have a child
-			txs = append(txs, srcTx)
+func normalizeTxs(explorerTxs []ExplorerTxs, address string) []blockatlas.Tx {
+	var txs []blockatlas.Tx
+	for _, tx := range explorerTxs {
+		normalizedTxs := normalizeTx(tx, address)
+		if normalizedTxs == nil {
 			continue
 		}
+		txs = append(txs, normalizedTxs...)
+	}
+	return txs
+}
+
+func filterTxsByType(txs []blockatlas.Tx, txType blockatlas.TransactionType) []blockatlas.Tx {
+	var result = make([]blockatlas.Tx, 0, len(txs))
+	for _, tx := range txs {
+		if tx.Type == txType {
+			result = append(result, tx)
+		}
+	}
+	return result
+}
+
+func normalizeTx(srcTx ExplorerTxs, address string) []blockatlas.Tx {
+	explorerTxType := srcTx.getTransactionType()
+	switch explorerTxType {
+	case SingleTransferOperation:
+		return normalizeSingleTransfer(srcTx, address)
+	case MultiTransferOperation:
+		return normalizeMultiTransfer(srcTx, address)
+	default:
+		return nil
+	}
+}
+
+func normalizeSingleTransfer(srcTx ExplorerTxs, address string) blockatlas.TxPage {
+	if srcTx.TxType != TxTransfer {
+		return nil
+	}
+	tx := getBase(srcTx)
+	tx.Direction = srcTx.getDirection(address)
+	bnbCoin := coin.Coins[coin.BNB]
+
+	if srcTx.TxAsset == bnbCoin.Symbol {
+		tx.Type = blockatlas.TxTransfer
+		tx.Meta = blockatlas.Transfer{
+			Decimals: bnbCoin.Decimals,
+			Symbol:   bnbCoin.Symbol,
+			Value:    srcTx.getDexValue(),
+		}
+		return blockatlas.TxPage{tx}
+	}
+
+	if srcTx.TxAsset != "" {
+		tx.Type = blockatlas.TxNativeTokenTransfer
+		tx.Meta = blockatlas.NativeTokenTransfer{
+			Decimals: bnbCoin.Decimals,
+			From:     srcTx.FromAddr,
+			Symbol:   tokenSymbol(srcTx.TxAsset),
+			To:       srcTx.ToAddr,
+			TokenID:  srcTx.TxAsset,
+			Value:    srcTx.getDexValue(),
+		}
+		return blockatlas.TxPage{tx}
+	}
+	return nil
+}
+
+func normalizeMultiTransfer(srcTx ExplorerTxs, address string) []blockatlas.Tx {
+	var txs blockatlas.TxPage
+	for _, t := range srcTx.MultisendTransfers {
+		if t.From == address || t.To == address {
+			srcTx.FromAddr = t.From
+			srcTx.ToAddr = t.To
+			srcTx.TxAsset = t.Asset
+
+			if value, err := strconv.ParseFloat(numbers.ToDecimal(t.Amount, 8), 64); err == nil {
+				srcTx.Value = value
+			}
+
+			if single := normalizeSingleTransfer(srcTx, address); single != nil {
+				txs = append(txs, single...)
+			}
+		}
+	}
+	return txs
+}
+
+func (p *Platform) addTxDetails(txs []ExplorerTxs) ([]ExplorerTxs, error) {
+	var (
+		wg              sync.WaitGroup
+		txsWithDetails  = make([]ExplorerTxs, 0, len(txs))
+		txHashChan      = make(chan TxHashRPC, len(txs))
+		multiSendTxsMap = make(map[string]ExplorerTxs, len(txs))
+	)
+
+	for _, tx := range txs {
+		multiSendTxsMap[tx.TxHash] = tx
+
+		if tx.HasChildren != 1 {
+			continue
+		}
+
 		wg.Add(1)
-		go func(srcTx Tx, out chan Tx, wg *sync.WaitGroup) {
+
+		go func(srcTx ExplorerTxs, txHashChan chan TxHashRPC, wg *sync.WaitGroup) {
 			defer wg.Done()
-			tx, err := p.client.GetTx(srcTx.Hash)
+			txHash, err := p.rpcClient.fetchTransactionHash(srcTx.TxHash)
 			if err != nil {
-				// Return the same transaction if an error occurs
-				out <- srcTx
-				logger.Error("GetTransactionsByBlockChan", err, logger.Params{"hash": srcTx.Hash})
 				return
 			}
-			out <- tx
-		}(srcTx, out, &wg)
+			txHashChan <- *txHash
+		}(tx, txHashChan, &wg)
 	}
+
 	wg.Wait()
-	close(out)
-	for r := range out {
-		txs = append(txs, r)
-	}
-	return txs, nil
-}
+	close(txHashChan)
 
-func normalizeTransfer(tx blockatlas.Tx, srcTx Tx, token, address string) (blockatlas.TxPage, bool) {
-	// Verify if the tx has more them one asset
-	if srcTx.HasChildren == 1 {
-		txs := make(blockatlas.TxPage, 0)
-		// Parse all assets as a transaction
-		for _, subTx := range srcTx.SubTxsDto.SubTxDtoList.getTxs() {
-			// If this is not called from a block observer_test, only get the user txs/assets
-			if !subTx.containAddress(address) {
-				continue
-			}
-			// Recursive call to normalize the tx
-			newTxs, ok := normalizeTransfer(tx, subTx, token, address)
+	for res := range txHashChan {
+		if len(res.Tx.Value.Msg) > 0 {
+			a, ok := multiSendTxsMap[res.Hash]
 			if !ok {
 				continue
 			}
-			txs = append(txs, newTxs...)
-		}
-		if len(txs) == 0 {
-			return txs, false
+			a.MultisendTransfers = extractMultiTransfers(res.Tx.Value)
+			multiSendTxsMap[res.Hash] = a
 		}
-		return txs, true
 	}
 
-	// Verify if this is the same asset we are looking for
-	if len(token) > 0 && srcTx.Asset != token {
-		return blockatlas.TxPage{tx}, false
+	for _, tx := range multiSendTxsMap {
+		txsWithDetails = append(txsWithDetails, tx)
 	}
 
-	tx.From = srcTx.FromAddr
-	tx.To = srcTx.ToAddr
+	return txsWithDetails, nil
+}
 
-	bnbCoin := coin.Coins[coin.BNB]
-	value := numbers.DecimalExp(string(srcTx.Value), 8)
-	if srcTx.Asset == bnbCoin.Symbol {
-		// Condition for native transfer (BNB)
-		tx.Meta = blockatlas.Transfer{
-			Value:    blockatlas.Amount(value),
-			Symbol:   bnbCoin.Symbol,
-			Decimals: bnbCoin.Decimals,
-		}
-		return blockatlas.TxPage{tx}, true
+// Construct base Tx out of explorer transfer using common fields for all type of Blockatlas transfers
+func getBase(srcTx ExplorerTxs) blockatlas.Tx {
+	base := blockatlas.Tx{
+		ID:    srcTx.TxHash,
+		Coin:  coin.BNB,
+		From:  srcTx.FromAddr,
+		Fee:   srcTx.getDexFee(),
+		Date:  srcTx.Timestamp / 1000,
+		Block: srcTx.BlockHeight,
+		Memo:  srcTx.Memo,
+		To:    srcTx.ToAddr,
 	}
 
-	// Condition for native token transfer
-	tx.Meta = blockatlas.NativeTokenTransfer{
-		TokenID:  srcTx.Asset,
-		Symbol:   TokenSymbol(srcTx.Asset),
-		Value:    blockatlas.Amount(value),
-		Decimals: bnbCoin.Decimals,
-		From:     srcTx.FromAddr,
-		To:       srcTx.ToAddr,
+	status := srcTx.getStatus()
+	base.Status = status
+	if status == blockatlas.StatusError {
+		base.Error = srcTx.getError()
 	}
-	return blockatlas.TxPage{tx}, true
-}
 
-// NormalizeTx converts a Binance transaction into the generic model
-func NormalizeTx(srcTx Tx, token, address string) (blockatlas.TxPage, bool) {
-	tx := blockatlas.Tx{
-		ID:     srcTx.Hash,
-		Coin:   coin.BNB,
-		From:   srcTx.FromAddr,
-		To:     srcTx.ToAddr,
-		Fee:    blockatlas.Amount(srcTx.getFee()),
-		Date:   srcTx.Timestamp / 1000,
-		Block:  srcTx.BlockHeight,
-		Status: blockatlas.StatusCompleted,
-		Memo:   srcTx.Memo,
-	}
-
-	switch srcTx.Type {
-	case TxTransfer:
-		return normalizeTransfer(tx, srcTx, token, address)
-	}
-	//case TxCancelOrder, TxNewOrder:
-	//	return tx, false
-	//	dt, err := srcTx.getData()
-	//	if err != nil {
-	//		return tx, false
-	//	}
-	//
-	//	symbol := dt.OrderData.Quote
-	//	if len(token) > 0 && symbol != token {
-	//		return tx, false
-	//	}
-	//
-	//	key := blockatlas.KeyPlaceOrder
-	//	title := blockatlas.KeyTitlePlaceOrder
-	//	if srcTx.Type == TxCancelOrder {
-	//		key = blockatlas.KeyCancelOrder
-	//		title = blockatlas.KeyTitleCancelOrder
-	//	}
-	//	volume, ok := dt.OrderData.GetVolume()
-	//	if ok {
-	//		value = strconv.Itoa(int(volume))
-	//	}
-	//
-	//	tx.Meta = blockatlas.AnyAction{
-	//		Coin:     coin.BNB,
-	//		TokenID:  dt.OrderData.Symbol,
-	//		Symbol:   TokenSymbol(symbol),
-	//		Name:     symbol,
-	//		Value:    blockatlas.Amount(value),
-	//		Decimals: coin.Coins[coin.BNB].Decimals,
-	//		Title:    title,
-	//		Key:      key,
-	//	}
-	//}
-	return blockatlas.TxPage{tx}, false
+	return base
 }
 
-func TokenSymbol(asset string) string {
+// Extract BEP2 token symbol from asset name e.g: TWT-8C2 => TWT
+func tokenSymbol(asset string) string {
 	s := strings.Split(asset, "-")
 	if len(s) > 1 {
 		return s[0]
 	}
 	return asset
 }
-
-// NormalizeTxs converts multiple Binance transactions
-func NormalizeTxs(srcTxs []Tx, token, adress string) (txs []blockatlas.Tx) {
-	for _, srcTx := range srcTxs {
-		tx, ok := NormalizeTx(srcTx, token, adress)
-		if !ok {
-			continue
-		}
-		txs = append(txs, tx...)
-	}
-	return
-}
diff --git a/platform/cosmos/transaction.go b/platform/cosmos/transaction.go
index 5a08c1f..3de7d5f 100644
--- a/platform/cosmos/transaction.go
+++ b/platform/cosmos/transaction.go
@@ -146,9 +146,13 @@ func (p *Platform) fillTransfer(tx *blockatlas.Tx, transfer MessageValueTransfer
 }
 
 func (p *Platform) fillDelegate(tx *blockatlas.Tx, delegate MessageValueDelegate, events Events, msgType TxType) {
-	value, err := numbers.DecimalToSatoshis(delegate.Amount.Quantity)
-	if err != nil {
-		return
+	value := ""
+	if len(delegate.Amount.Quantity) > 0 {
+		var err error
+		value, err = numbers.DecimalToSatoshis(delegate.Amount.Quantity)
+		if err != nil {
+			return
+		}
 	}
 	tx.From = delegate.DelegatorAddr
 	tx.To = delegate.ValidatorAddr
diff --git a/platform/harmony/client.go b/platform/harmony/client.go
index 67f48c6..70de06a 100644
--- a/platform/harmony/client.go
+++ b/platform/harmony/client.go
@@ -2,9 +2,11 @@ package harmony
 
 import (
 	"fmt"
+	"strconv"
+
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"github.com/trustwallet/blockatlas/pkg/logger"
 	"github.com/trustwallet/blockatlas/pkg/numbers"
-	"strconv"
 )
 
 type Client struct {
@@ -41,6 +43,40 @@ func (c *Client) GetBlockByNumber(num int64) (info BlockInfo, err error) {
 	return
 }
 
+
+func (c *Client) GetValidators() (validators Validators, err error) {
+	err = rpcCallStub(c, &validators.Validators, "hmy_getAllValidatorInformation", []interface{}{-1})
+
+	if err != nil {
+		logger.Error(err, "Harmony: Failed to get all validator addresses")
+	}
+
+	return
+}
+
+func (c *Client) GetDelegations(address string) (delegations Delegations, err error) {
+	err = rpcCallStub(c, &delegations.List, "hmy_getDelegationsByDelegator", []interface{}{address})
+
+	if err != nil {
+		logger.Error(err, "Harmony: Failed to get delegations for address")
+	}
+	return
+}
+
+func (c *Client) GetBalance(address string) (string, error) {
+	var result string
+	err := rpcCallStub(c, &result, "hmy_getBalance", []interface{}{address, "latest"})
+
+	if err != nil {
+		return "0", err
+	}
+	balance, err := numbers.HexToDecimal(result)
+	if err != nil {
+		return "0", err
+	}
+	return balance, nil
+}
+
 func hexToInt(hex string) (uint64, error) {
 	nonceStr, err := numbers.HexToDecimal(hex)
 	if err != nil {
@@ -48,3 +84,8 @@ func hexToInt(hex string) (uint64, error) {
 	}
 	return strconv.ParseUint(nonceStr, 10, 64)
 }
+
+// rpcCallStub is can be overwritten by the unit test
+var rpcCallStub = func (c *Client, result interface{}, method string, params interface{}) error {
+	return c.RpcCall(result, method, params)
+}
diff --git a/platform/harmony/model.go b/platform/harmony/model.go
index a4134e2..698465e 100644
--- a/platform/harmony/model.go
+++ b/platform/harmony/model.go
@@ -26,3 +26,31 @@ type BlockInfo struct {
 	Number       string        `json:"number"`
 	Transactions []Transaction `json:"transactions"`
 }
+
+type ValidatorInfo struct {
+	Address string  `json:"address"`
+}
+
+type LifetimeInfo struct {
+	Apr     string `json:"apr"`
+}
+
+type Validator struct {
+	Info      ValidatorInfo    `json:"validator"`
+	Active    bool             `json:"currently-in-committee"`
+	Lifetime  LifetimeInfo      `json:"lifetime"`
+}
+
+type Validators struct {
+	Validators []Validator `json:"result"`
+}
+
+type Delegation struct {
+	DelegatorAddress string   `json:"delegator_address"`
+	ValidatorAddress string   `json:"validator_address"`
+	Amount           float64  `json:"amount"`
+}
+
+type Delegations struct {
+	List []Delegation `json:"result"`
+}
diff --git a/platform/harmony/stake.go b/platform/harmony/stake.go
new file mode 100644
index 0000000..7064654
--- /dev/null
+++ b/platform/harmony/stake.go
@@ -0,0 +1,123 @@
+package harmony
+
+import (
+	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"github.com/trustwallet/blockatlas/pkg/errors"
+	"github.com/trustwallet/blockatlas/pkg/logger"
+	services "github.com/trustwallet/blockatlas/services/assets"
+	"math/big"
+	"strconv"
+)
+
+const (
+	lockTime  = 604800 // in seconds (7 epochs or 7 days)
+)
+
+func (p *Platform) GetValidators() (blockatlas.ValidatorPage, error) {
+	results := make(blockatlas.ValidatorPage, 0)
+	validators, err := p.client.GetValidators()
+	if err != nil {
+		return results, err
+	}
+
+	for _, v := range validators.Validators {
+		var apr float64
+		if apr, err = strconv.ParseFloat(v.Lifetime.Apr, 64); err != nil {
+			apr = 0
+		}
+		results = append(results, normalizeValidator(v, apr))
+	}
+
+	return results, nil
+}
+
+func (p *Platform) GetDetails() blockatlas.StakingDetails {
+	apr := p.GetMaxAPR()
+	return getDetails(apr)
+}
+
+func (p *Platform) GetMaxAPR() float64 {
+	validators, err := p.client.GetValidators()
+	if err != nil {
+		logger.Error("GetMaxAPR", logger.Params{"details": err, "platform": p.Coin().Symbol})
+		return Annual
+	}
+
+	var max = 0.0
+	for _, e := range validators.Validators {
+		var apr float64
+		if apr, err = strconv.ParseFloat(e.Lifetime.Apr, 64); err != nil {
+			apr = 0.0
+		}
+
+		if apr > max {
+			max = apr
+		}
+	}
+
+	return max
+}
+
+func (p *Platform) GetDelegations(address string) (blockatlas.DelegationsPage, error) {
+	delegations, err := p.client.GetDelegations(address)
+	if err != nil {
+		return nil, err
+	}
+
+	validators, err := services.GetValidatorsMap(p)
+	if err != nil {
+		return nil, err
+	}
+
+	return NormalizeDelegations(delegations.List, validators), nil
+}
+
+func (p *Platform) UndelegatedBalance(address string) (string, error) {
+	balance, err := p.client.GetBalance(address)
+	if err != nil {
+		return "0", err
+	}
+	return balance, nil
+}
+
+func NormalizeDelegations(delegations []Delegation, validators blockatlas.ValidatorMap) []blockatlas.Delegation {
+	results := make([]blockatlas.Delegation, 0)
+	for _, v := range delegations {
+		validator, ok := validators[v.ValidatorAddress]
+		if !ok {
+			logger.Error(errors.E("Validator not found", errors.Params{"address": v.ValidatorAddress, "platform": "harmony", "delegation": v.DelegatorAddress}))
+			continue
+		}
+
+		bigval := new(big.Float)
+		bigval.SetFloat64(v.Amount)
+
+		result := new(big.Int)
+		bigval.Int(result) // store converted number in result
+
+		delegation := blockatlas.Delegation{
+			Delegator: validator,
+			Value:     result.String(), // v.Amount.String(),
+			Status:    blockatlas.DelegationStatusActive,
+		}
+		results = append(results, delegation)
+	}
+	return results
+}
+
+func getDetails(apr float64) blockatlas.StakingDetails {
+	return blockatlas.StakingDetails{
+		Reward:        blockatlas.StakingReward{Annual: apr},
+		MinimumAmount: blockatlas.Amount("1000"),
+		LockTime:      lockTime,
+		Type:          blockatlas.DelegationTypeDelegate,
+	}
+}
+
+func normalizeValidator(v Validator, apr float64) (validator blockatlas.Validator) {
+	return blockatlas.Validator{
+		Status:  v.Active,
+		ID:      v.Info.Address,
+		Details: getDetails(apr),
+	}
+}
diff --git a/platform/harmony/transaction.go b/platform/harmony/transaction.go
index 890b5b4..920ed2c 100644
--- a/platform/harmony/transaction.go
+++ b/platform/harmony/transaction.go
@@ -8,6 +8,8 @@ import (
 	"strconv"
 )
 
+const Annual = 10
+
 func (p *Platform) GetTxsByAddress(address string) (blockatlas.TxPage, error) {
 	result, err := p.client.GetTxsOfAddress(address)
 	if err != nil {
diff --git a/platform/platform.go b/platform/platform.go
index 287eaa2..7790401 100644
--- a/platform/platform.go
+++ b/platform/platform.go
@@ -76,7 +76,7 @@ func getAllHandlers() blockatlas.Platforms {
 		coin.Aeternity().Handle:    aeternity.Init(GetApiVar(coin.AE)),
 		coin.Solana().Handle:       solana.Init(GetApiVar(coin.SOL)),
 		coin.Tezos().Handle:        tezos.Init(GetApiVar(coin.XTZ), GetRpcVar(coin.XTZ)),
-		coin.Binance().Handle:      binance.Init(GetApiVar(coin.BNB), GetVar("binance.dex")),
+		coin.Binance().Handle:      binance.Init(GetApiVar(coin.BNB), GetVar("binance.explorer")),
 		coin.Zilliqa().Handle:      zilliqa.Init(GetApiVar(coin.ZIL), GetVar("zilliqa.key"), GetRpcVar(coin.ZIL), GetVar("zilliqa.lookup")),
 		coin.Kusama().Handle:       polkadot.Init(coin.KSM, GetApiVar(coin.KSM)),
 		coin.Stellar().Handle:      stellar.Init(coin.XLM, GetApiVar(coin.XLM)),
diff --git a/platform/registry.go b/platform/registry.go
index b7bc91a..9dc4280 100644
--- a/platform/registry.go
+++ b/platform/registry.go
@@ -27,25 +27,31 @@ var (
 	NamingAPIs map[uint]blockatlas.NamingServiceAPI
 )
 
-func getActivePlatforms(handle string) []blockatlas.Platform {
-	platforms := getAllHandlers()
-	logger.Info("Platform API setup with: ", logger.Params{"handle": handle})
-
-	if handle == allPlatformsHandle {
-		return platforms.GetPlatformList()
+func getActivePlatforms(handles []string) []blockatlas.Platform {
+	if len(handles) == 0 {
+		logger.Fatal("Please, use ATLAS_PLATFORM handle with non-empty value, see more at Readme. Example: all", logger.Params{"ATLAS_PLATFORM": handles})
+		return nil
 	}
 
-	platform, ok := platforms[handle]
-	if ok {
-		return []blockatlas.Platform{platform}
-	}
+	allPlatforms := getAllHandlers()
+	logger.Info("Platform API setup with: ", logger.Params{"handles": handles})
+
+	platforms := make([]blockatlas.Platform, 0, len(handles))
 
-	logger.Fatal("Please, use ATLAS_PLATFORM handle with non-empty value, see more at Readme. Example: all", logger.Params{"ATLAS_PLATFORM": handle})
-	return nil
+	for _, handle := range handles {
+		if handle == allPlatformsHandle {
+			return allPlatforms.GetPlatformList()
+		}
+		p, ok := allPlatforms[handle]
+		if ok {
+			platforms = append(platforms, p)
+		}
+	}
+	return platforms
 }
 
-func Init(platformHandle string) {
-	platformList := getActivePlatforms(platformHandle)
+func Init(platformHandles []string) {
+	platformList := getActivePlatforms(platformHandles)
 
 	Platforms = make(map[string]blockatlas.Platform)
 	BlockAPIs = make(map[string]blockatlas.BlockAPI)
diff --git a/services/observer/notifier/notifier.go b/services/observer/notifier/notifier.go
index 35e1b09..898242b 100644
--- a/services/observer/notifier/notifier.go
+++ b/services/observer/notifier/notifier.go
@@ -1,6 +1,7 @@
 package notifier
 
 import (
+	"context"
 	"encoding/json"
 	"github.com/streadway/amqp"
 	"github.com/trustwallet/blockatlas/db"
@@ -9,6 +10,7 @@ import (
 	"github.com/trustwallet/blockatlas/pkg/errors"
 	"github.com/trustwallet/blockatlas/pkg/logger"
 	"github.com/trustwallet/blockatlas/pkg/numbers"
+	"go.elastic.co/apm"
 	"sync"
 	"time"
 )
@@ -24,6 +26,9 @@ type TransactionNotification struct {
 }
 
 func RunNotifier(database *db.Instance, delivery amqp.Delivery) {
+	tx := apm.DefaultTracer.StartTransaction("RunNotifier", "app")
+	defer tx.End()
+	ctx := apm.ContextWithTransaction(context.Background(), tx)
 	defer func() {
 		if err := delivery.Ack(false); err != nil {
 			logger.Error(err)
@@ -46,7 +51,7 @@ func RunNotifier(database *db.Instance, delivery amqp.Delivery) {
 	}
 
 	addresses := blockTransactions.GetUniqueAddresses()
-	subscriptionsDataList, err := database.GetSubscriptionData(txs[0].Coin, addresses)
+	subscriptionsDataList, err := database.GetSubscriptionData(txs[0].Coin, addresses, ctx)
 	if err != nil || len(subscriptionsDataList) == 0 {
 		return
 	}
@@ -57,14 +62,17 @@ func RunNotifier(database *db.Instance, delivery amqp.Delivery) {
 		go buildAndPostMessage(
 			blockTransactions,
 			blockatlas.Subscription{Coin: data.Coin, Address: data.Address, Id: data.SubscriptionId},
-			&wg)
+			&wg, ctx)
 	}
 	wg.Wait()
 }
 
-func buildAndPostMessage(blockTransactions blockatlas.TxSetMap, sub blockatlas.Subscription, wg *sync.WaitGroup) {
+func buildAndPostMessage(blockTransactions blockatlas.TxSetMap, sub blockatlas.Subscription, wg *sync.WaitGroup, ctx context.Context) {
 	defer wg.Done()
 
+	span, ctx := apm.StartSpan(ctx, "buildAndPostMessage", "app")
+	defer span.End()
+
 	tx, ok := blockTransactions.Map[sub.Address]
 	if !ok {
 		return
@@ -84,14 +92,16 @@ func buildAndPostMessage(blockTransactions blockatlas.TxSetMap, sub blockatlas.S
 		notifications = append(notifications, notification)
 	}
 
-	batches := getNotificationBatches(notifications, MaxPushNotificationsBatchLimit)
+	batches := getNotificationBatches(notifications, MaxPushNotificationsBatchLimit, ctx)
 
 	for _, batch := range batches {
-		publishNotificationBatch(batch)
+		publishNotificationBatch(batch, ctx)
 	}
 }
 
-func publishNotificationBatch(batch []TransactionNotification) {
+func publishNotificationBatch(batch []TransactionNotification, ctx context.Context) {
+	span, _ := apm.StartSpan(ctx, "getNotificationBatches", "app")
+	defer span.End()
 	raw, err := json.Marshal(batch)
 	if err != nil {
 		err = errors.E(err, " failed to dispatch event")
@@ -114,7 +124,9 @@ func GetInterval(value int, minInterval, maxInterval time.Duration) time.Duratio
 	return time.Duration(pMax)
 }
 
-func getNotificationBatches(notifications []TransactionNotification, sizeUint uint) [][]TransactionNotification {
+func getNotificationBatches(notifications []TransactionNotification, sizeUint uint, ctx context.Context) [][]TransactionNotification {
+	span, _ := apm.StartSpan(ctx, "getNotificationBatches", "app")
+	defer span.End()
 	size := int(sizeUint)
 	resultLength := (len(notifications) + size - 1) / size
 	result := make([][]TransactionNotification, resultLength)
diff --git a/services/observer/parser/parser.go b/services/observer/parser/parser.go
index 58195f0..e5e103c 100644
--- a/services/observer/parser/parser.go
+++ b/services/observer/parser/parser.go
@@ -8,6 +8,8 @@ import (
 	"github.com/trustwallet/blockatlas/mq"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 	"github.com/trustwallet/blockatlas/pkg/errors"
+	"go.elastic.co/apm"
+	"strconv"
 	"sync/atomic"
 
 	"github.com/trustwallet/blockatlas/pkg/logger"
@@ -53,33 +55,51 @@ func RunParser(params Params) {
 			params.StopChannel <- struct{}{}
 			return
 		default:
-			lastParsedBlock, currentBlock, err := GetBlocksIntervalToFetch(params)
-			if err != nil || lastParsedBlock > currentBlock {
-				logger.Error(err, logger.Params{"coin": params.Api.Coin().Handle})
-				time.Sleep(params.ParsingBlocksInterval)
-				continue
-			}
+			parse(params)
+			time.Sleep(params.ParsingBlocksInterval)
+		}
+	}
+}
 
-			blocks := FetchBlocks(params, lastParsedBlock, currentBlock)
+func parse(params Params) {
+	tx := apm.DefaultTracer.StartTransaction("parse", "app")
+	ctx := apm.ContextWithTransaction(context.Background(), tx)
 
-			err = SaveLastParsedBlock(params, blocks)
-			if err != nil {
-				logger.Error(err, logger.Params{"coin": params.Api.Coin().Handle})
-				time.Sleep(params.ParsingBlocksInterval)
-				continue
-			}
+	defer tx.End()
+
+	lastParsedBlock, currentBlock, err := GetBlocksIntervalToFetch(params, ctx)
+	if err != nil || lastParsedBlock > currentBlock {
+		logger.Error(err, logger.Params{"coin": params.Api.Coin().Handle})
+		time.Sleep(params.ParsingBlocksInterval)
+		tx.Result = "failed"
+		return
+	}
+	tx.Context.SetTag("lastParsedBlock", strconv.Itoa(int(lastParsedBlock)))
+	tx.Context.SetTag("lastParsedBlock", strconv.Itoa(int(lastParsedBlock)))
 
-			txs := ConvertToBatch(blocks)
+	blocks := FetchBlocks(params, lastParsedBlock, currentBlock, ctx)
 
-			PublishTransactionsBatch(params, txs)
+	tx.Context.SetTag("blocks len", strconv.Itoa(len(blocks)))
 
-			time.Sleep(params.ParsingBlocksInterval)
-		}
+	err = SaveLastParsedBlock(params, blocks, ctx)
+	if err != nil {
+		logger.Error(err, logger.Params{"coin": params.Api.Coin().Handle})
+		time.Sleep(params.ParsingBlocksInterval)
+		tx.Result = "failed"
+		return
 	}
+
+	txs := ConvertToBatch(blocks, ctx)
+	tx.Result = "success"
+	tx.Context.SetTag("txs", strconv.Itoa(len(txs)))
+	PublishTransactionsBatch(params, txs, ctx)
 }
 
-func GetBlocksIntervalToFetch(params Params) (int64, int64, error) {
-	lastParsedBlock, err := params.Database.GetLastParsedBlockNumber(params.Api.Coin().Handle)
+func GetBlocksIntervalToFetch(params Params, ctx context.Context) (int64, int64, error) {
+	span, ctx := apm.StartSpan(ctx, "GetBlocksIntervalToFetch", "app")
+	defer span.End()
+
+	lastParsedBlock, err := params.Database.GetLastParsedBlockNumber(params.Api.Coin().Handle, ctx)
 	if err != nil {
 		return 0, 0, errors.E(err, "Polling failed: tracker didn't return last known block number")
 	}
@@ -100,7 +120,10 @@ func GetBlocksIntervalToFetch(params Params) (int64, int64, error) {
 	return lastParsedBlock, currentBlock, nil
 }
 
-func FetchBlocks(params Params, lastParsedBlock, currentBlock int64) []blockatlas.Block {
+func FetchBlocks(params Params, lastParsedBlock, currentBlock int64, ctx context.Context) []blockatlas.Block {
+	span, ctx := apm.StartSpan(ctx, "FetchBlocks", "app")
+	defer span.End()
+
 	if lastParsedBlock == currentBlock {
 		logger.Info("No new blocks", logger.Params{"last": lastParsedBlock, "coin": params.Api.Coin().ID, "time": time.Now().Unix()})
 		return nil
@@ -124,7 +147,7 @@ func FetchBlocks(params Params, lastParsedBlock, currentBlock int64) []blockatla
 		time.Sleep(params.FetchBlocksTimeout)
 		go func(i int64, wg *sync.WaitGroup) {
 			defer wg.Done()
-			err := fetchBlock(params.Api, i, blocksChan)
+			err := fetchBlock(params.Api, i, blocksChan, ctx)
 			if err != nil {
 				errorsChan <- err
 				return
@@ -156,8 +179,10 @@ func FetchBlocks(params Params, lastParsedBlock, currentBlock int64) []blockatla
 	return blocksList
 }
 
-func fetchBlock(api blockatlas.BlockAPI, num int64, blocksChan chan<- blockatlas.Block) error {
-	block, err := getBlockByNumberWithRetry(5, time.Second*5, api.GetBlockByNumber, num)
+func fetchBlock(api blockatlas.BlockAPI, num int64, blocksChan chan<- blockatlas.Block, ctx context.Context) error {
+	span, ctx := apm.StartSpan(ctx, "fetchBlock", "app")
+	defer span.End()
+	block, err := getBlockByNumberWithRetry(5, time.Second*5, api.GetBlockByNumber, num, api.Coin().Symbol, ctx)
 	if err != nil {
 		return errors.E(fmt.Sprintf("%d", num))
 	}
@@ -165,7 +190,10 @@ func fetchBlock(api blockatlas.BlockAPI, num int64, blocksChan chan<- blockatlas
 	return nil
 }
 
-func SaveLastParsedBlock(params Params, blocks []blockatlas.Block) error {
+func SaveLastParsedBlock(params Params, blocks []blockatlas.Block, ctx context.Context) error {
+	span, ctx := apm.StartSpan(ctx, "SaveLastParsedBlock", "app")
+	defer span.End()
+
 	if len(blocks) == 0 {
 		return nil
 	}
@@ -178,7 +206,7 @@ func SaveLastParsedBlock(params Params, blocks []blockatlas.Block) error {
 	if lastBlockNumber <= 0 {
 		return errors.E(fmt.Sprintf("Parser of %s failed to save last block, lastBlockNumber <= 0", params.Api.Coin().Handle))
 	}
-	err := params.Database.SetLastParsedBlockNumber(params.Api.Coin().Handle, lastBlockNumber)
+	err := params.Database.SetLastParsedBlockNumber(params.Api.Coin().Handle, lastBlockNumber, ctx)
 	if err != nil {
 		return err
 	}
@@ -187,7 +215,9 @@ func SaveLastParsedBlock(params Params, blocks []blockatlas.Block) error {
 	return nil
 }
 
-func ConvertToBatch(blocks []blockatlas.Block) blockatlas.Txs {
+func ConvertToBatch(blocks []blockatlas.Block, ctx context.Context) blockatlas.Txs {
+	span, ctx := apm.StartSpan(ctx, "ConvertToBatch", "app")
+	defer span.End()
 	if len(blocks) == 0 {
 		return nil
 	}
@@ -201,7 +231,7 @@ func ConvertToBatch(blocks []blockatlas.Block) blockatlas.Txs {
 		wg.Add(1)
 		go func(block blockatlas.Block, wg *sync.WaitGroup) {
 			defer wg.Done()
-			txsBatch.fillBatch(block.Txs)
+			txsBatch.fillBatch(block.Txs, ctx)
 		}(block, &wg)
 	}
 	wg.Wait()
@@ -215,18 +245,20 @@ func ConvertToBatch(blocks []blockatlas.Block) blockatlas.Txs {
 	return txsBatch.Txs
 }
 
-func PublishTransactionsBatch(params Params, txs blockatlas.Txs) {
+func PublishTransactionsBatch(params Params, txs blockatlas.Txs, ctx context.Context) {
+	span, ctx := apm.StartSpan(ctx, "PublishTransactionsBatch", "app")
+	defer span.End()
 	if len(txs) == 0 {
 		logger.Info("------------------------------------------------------------")
 		return
 	}
 
-	batches := getTxsBatches(txs, params.TxBatchLimit)
+	batches := getTxsBatches(txs, params.TxBatchLimit, ctx)
 
 	var wg sync.WaitGroup
 	for _, batch := range batches {
 		wg.Add(1)
-		go publish(params, batch, &wg)
+		go publish(params, batch, &wg, ctx)
 	}
 	wg.Wait()
 
@@ -234,7 +266,9 @@ func PublishTransactionsBatch(params Params, txs blockatlas.Txs) {
 	logger.Info("------------------------------------------------------------")
 }
 
-func getTxsBatches(txs blockatlas.Txs, sizeUint uint) []blockatlas.Txs {
+func getTxsBatches(txs blockatlas.Txs, sizeUint uint, ctx context.Context) []blockatlas.Txs {
+	span, _ := apm.StartSpan(ctx, "getTxsBatches", "app")
+	defer span.End()
 	size := int(sizeUint)
 	resultLength := (len(txs) + size - 1) / size
 	result := make([]blockatlas.Txs, resultLength)
@@ -249,7 +283,9 @@ func getTxsBatches(txs blockatlas.Txs, sizeUint uint) []blockatlas.Txs {
 	return result
 }
 
-func publish(params Params, txs blockatlas.Txs, wg *sync.WaitGroup) {
+func publish(params Params, txs blockatlas.Txs, wg *sync.WaitGroup, ctx context.Context) {
+	span, _ := apm.StartSpan(ctx, "publish", "app")
+	defer span.End()
 	defer wg.Done()
 	body, err := json.Marshal(txs)
 	if err != nil {
@@ -263,7 +299,9 @@ func publish(params Params, txs blockatlas.Txs, wg *sync.WaitGroup) {
 	}
 }
 
-func getBlockByNumberWithRetry(attempts int, sleep time.Duration, getBlockByNumber GetBlockByNumber, n int64) (*blockatlas.Block, error) {
+func getBlockByNumberWithRetry(attempts int, sleep time.Duration, getBlockByNumber GetBlockByNumber, n int64, symbol string, ctx context.Context) (*blockatlas.Block, error) {
+	span, ctx := apm.StartSpan(ctx, "getBlockByNumberWithRetry", "app")
+	defer span.End()
 	r, err := getBlockByNumber(n)
 	if err != nil {
 		if s, ok := err.(stop); ok {
@@ -280,17 +318,20 @@ func getBlockByNumberWithRetry(attempts int, sleep time.Duration, getBlockByNumb
 					"number":   n,
 					"attempts": attempts,
 					"sleep":    sleep.String(),
+					"symbol":   symbol,
 				},
 			)
 
 			time.Sleep(sleep)
-			return getBlockByNumberWithRetry(attempts, sleep*2, getBlockByNumber, n)
+			return getBlockByNumberWithRetry(attempts, sleep*2, getBlockByNumber, n, symbol, ctx)
 		}
 	}
 	return r, err
 }
 
-func (t *transactionsBatch) fillBatch(transactions blockatlas.Txs) {
+func (t *transactionsBatch) fillBatch(transactions []blockatlas.Tx, ctx context.Context) {
+	span, _ := apm.StartSpan(ctx, "fillBatch", "app")
+	defer span.End()
 	t.Lock()
 	defer t.Unlock()
 	if len(transactions) == 0 {
diff --git a/services/observer/subscriber/subscriber.go b/services/observer/subscriber/subscriber.go
index 7137817..1100b43 100644
--- a/services/observer/subscriber/subscriber.go
+++ b/services/observer/subscriber/subscriber.go
@@ -1,12 +1,14 @@
 package subscriber
 
 import (
+	"context"
 	"encoding/json"
 	"github.com/streadway/amqp"
 	"github.com/trustwallet/blockatlas/db"
 	"github.com/trustwallet/blockatlas/db/models"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 	"github.com/trustwallet/blockatlas/pkg/logger"
+	"go.elastic.co/apm"
 )
 
 const (
@@ -16,6 +18,9 @@ const (
 )
 
 func RunSubscriber(database *db.Instance, delivery amqp.Delivery) {
+	tx := apm.DefaultTracer.StartTransaction("RunSubscriber", "app")
+	defer tx.End()
+	ctx := apm.ContextWithTransaction(context.Background(), tx)
 	var event blockatlas.SubscriptionEvent
 	err := json.Unmarshal(delivery.Body, &event)
 	if err != nil {
@@ -30,13 +35,13 @@ func RunSubscriber(database *db.Instance, delivery amqp.Delivery) {
 
 	switch event.Operation {
 	case AddSubscription, UpdateSubscription:
-		err = database.AddSubscriptions(id, ToSubscriptionData(subscriptions))
+		err = database.AddSubscriptions(id, ToSubscriptionData(subscriptions), ctx)
 		if err != nil {
 			logger.Error(err, params)
 		}
 		logger.Info("Added", params)
 	case DeleteSubscription:
-		err := database.DeleteAllSubscriptions(id)
+		err := database.DeleteAllSubscriptions(id, ctx)
 		if err != nil {
 			logger.Error(err, params)
 		}


Test patch:
diff --git a/db/tracker_test.go b/db/tracker_test.go
index a251216..502c4a8 100644
--- a/db/tracker_test.go
+++ b/db/tracker_test.go
@@ -1,6 +1,7 @@
 package db
 
 import (
+	"context"
 	"github.com/DATA-DOG/go-sqlmock"
 	"github.com/jinzhu/gorm"
 	"github.com/stretchr/testify/assert"
@@ -19,7 +20,7 @@ func TestHeightBlockMap_SetHeight(t *testing.T) {
 	mock.ExpectCommit()
 	i := Instance{Gorm: db}
 
-	assert.Nil(t, i.SetLastParsedBlockNumber("bitcoin", 1))
+	assert.Nil(t, i.SetLastParsedBlockNumber("bitcoin", 1, context.Background()))
 }
 
 func TestHeightBlockMap_GetHeight(t *testing.T) {
@@ -33,8 +34,8 @@ func TestHeightBlockMap_GetHeight(t *testing.T) {
 	mock.ExpectCommit()
 	i := Instance{Gorm: db}
 
-	assert.Nil(t, i.SetLastParsedBlockNumber("bitcoin", 1))
-	block, err := i.GetLastParsedBlockNumber("bitcoin")
+	assert.Nil(t, i.SetLastParsedBlockNumber("bitcoin", 1, context.Background()))
+	block, err := i.GetLastParsedBlockNumber("bitcoin", context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, int64(1), block)
 
@@ -43,7 +44,7 @@ func TestHeightBlockMap_GetHeight(t *testing.T) {
 			`SELECT * FROM "trackers"  WHERE ("trackers"."coin" = $1`)).WithArgs("ethereum").WillReturnRows(sqlmock.NewRows([]string{"coin", "height"}).
 		AddRow("ethereum", 1))
 
-	b, err := i.GetLastParsedBlockNumber("ethereum")
+	b, err := i.GetLastParsedBlockNumber("ethereum", context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, int64(1), b)
 
diff --git a/pkg/numbers/amount_test.go b/pkg/numbers/amount_test.go
index 5b95462..8db856b 100644
--- a/pkg/numbers/amount_test.go
+++ b/pkg/numbers/amount_test.go
@@ -33,7 +33,7 @@ func Test_addAmount(t *testing.T) {
 	}
 }
 
-func Test_decimalToSatoshis(t *testing.T) {
+func Test_ToSatoshi(t *testing.T) {
 	tests := []struct {
 		name   string
 		amount string
@@ -47,7 +47,7 @@ func Test_decimalToSatoshis(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			if got := ToSatoshi(tt.amount); got != tt.want {
-				t.Errorf("decimalToSatoshis() = %v, want %v", got, tt.want)
+				t.Errorf("ToSatoshi() = %v, want %v", got, tt.want)
 			}
 		})
 	}
diff --git a/pkg/numbers/decimal_test.go b/pkg/numbers/decimal_test.go
index b32ed89..89c3c37 100644
--- a/pkg/numbers/decimal_test.go
+++ b/pkg/numbers/decimal_test.go
@@ -9,14 +9,35 @@ func TestDecimalToSatoshis(t *testing.T) {
 			t.Error(err)
 		}
 		if expected != actual {
-			t.Errorf("expected: %s, got %s", expected, actual)
+			t.Errorf("expected %s, got %s, input %s", expected, actual, input)
+		}
+	}
+
+	assertSatError := func(input string) {
+		actual, err := DecimalToSatoshis(input)
+		if err == nil {
+			t.Errorf("Expected error but no error: got %s, input %s", actual, input)
 		}
 	}
 
 	assertSatEquals("10", "1.0")
 	assertSatEquals("1", "0.1")
 	assertSatEquals("13602", "136.02")
+	assertSatEquals("13602", "0136.02")
 	assertSatEquals("1500000", "0.01500000")
+	assertSatEquals("0", "0")
+	assertSatEquals("2030", "0.002030")
+	assertSatEquals("101010", "0101010")
+	assertSatEquals("11001100", "0011001100")
+	assertSatEquals("376", " 376")
+	assertSatEquals("376", "376 ")
+	
+	assertSatError("12NotNumber34")
+	assertSatError("12,34")
+	assertSatError("")
+	assertSatError(" ")
+	assertSatError("37 6")
+	assertSatError("37,6")
 }
 
 func TestDecimalExp(t *testing.T) {
@@ -30,6 +51,7 @@ func TestDecimalExp(t *testing.T) {
 
 	// No-Op
 	assertEquals("0", 300, "0")
+	assertEquals("0", 8, "0")
 	assertEquals("123", 0, "123")
 	assertEquals("0.456", 0, "0.456")
 	assertEquals("123.456", 0, "123.456")
@@ -57,6 +79,7 @@ func TestDecimalExp(t *testing.T) {
 	// Tiny
 	assertEquals("0.001234", -1, "0.0001234")
 	assertEquals("0.001234", 1, "0.01234")
+	assertEquals("0.000375", 8, "37500")
 }
 
 func TestCutZeroFractional(t *testing.T) {
diff --git a/pkg/numbers/number_test.go b/pkg/numbers/number_test.go
index 23d0ce4..5af1964 100644
--- a/pkg/numbers/number_test.go
+++ b/pkg/numbers/number_test.go
@@ -64,4 +64,32 @@ func TestFloat64toString(t *testing.T) {
 	assert.Equal(t, Float64toString(1), "1")
 	assert.Equal(t, Float64toString(1.1), "1.1")
 	assert.Equal(t, Float64toString(1.015), "1.015")
+	assert.Equal(t, Float64toString(2800.00000000), "2800")
+	assert.Equal(t, Float64toString(0.00037500), "0.000375")
+}
+
+func TestStringNumberToFloat64(t *testing.T) {
+	var tests = []struct {
+		stringNumber string
+		expect       float64
+		ecpectErr    bool
+	}{
+		{"0.29970000", 0.2997, false},
+		{"0.00037500", 0.000375, false},
+		{"1.0", 1, false},
+		{"1", 1, false},
+		{"0", 0, false},
+		{"23.12", 23.120, true},
+	}
+
+	for _, tt := range tests {
+		t.Run("", func(t *testing.T) {
+			actual, err := StringNumberToFloat64(tt.stringNumber)
+			if tt.ecpectErr {
+				assert.Nil(t, err)
+			} else {
+				assert.Equal(t, tt.expect, actual)
+			}
+		})
+	}
 }
diff --git a/platform/binance/block_test.go b/platform/binance/block_test.go
new file mode 100644
index 0000000..6baaf37
--- /dev/null
+++ b/platform/binance/block_test.go
@@ -0,0 +1,124 @@
+package binance
+
+import (
+	"encoding/json"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+const (
+	bnbSingleTransfer = `
+{
+    "blockHeight": 84191216,
+    "tx": [
+        {
+            "txHash": "4577CB3B5B202696E9E0B093A6DA973C7DD9CBC6808DA1326872745C35F3C089",
+            "blockHeight": 84191216,
+            "txType": "TRANSFER",
+            "timeStamp": "2020-04-28T15:05:57.686Z",
+            "fromAddr": "bnb1mr5f97rx5wnkfcakx9fcpvljmx2s6kwqc08yur",
+            "toAddr": "bnb14cjy0yl23xkf0hnw3ql295v8nghqstvlzkvqpl",
+            "value": "0.00040000",
+            "txAsset": "BNB",
+            "txFee": "0.00037500",
+            "code": 0,
+            "data": null,
+            "memo": "",
+            "source": 0,
+            "sequence": 95
+        }
+    ]
+}`
+	bep2MultiTransfer = `
+{
+    "blockHeight": 80167666,
+    "tx": [
+        {
+            "txHash": "FAD8C1C5E450BE5E0913B12007AAEACC307F8CFFAFFB0844A9F83155E1235C25",
+            "blockHeight": 80167666,
+            "txType": "TRANSFER",
+            "timeStamp": "2020-04-09T20:34:12.922Z",
+            "fromAddr": null,
+            "toAddr": null,
+            "value": null,
+            "txAsset": null,
+            "txFee": null,
+            "code": 0,
+            "data": null,
+            "memo": "multisend",
+            "source": 1,
+            "sequence": 72,
+            "subTransactions": [
+                {
+                    "txHash": "FAD8C1C5E450BE5E0913B12007AAEACC307F8CFFAFFB0844A9F83155E1235C25",
+                    "blockHeight": 80167666,
+                    "txType": "TRANSFER",
+                    "fromAddr": "bnb1ds83nt2tz2s9m7kkdcu53t3ccjc07un4xvdld7",
+                    "toAddr": "bnb14cjy0yl23xkf0hnw3ql295v8nghqstvlzkvqpl",
+                    "txAsset": "TWT-8C2",
+                    "txFee": "0.29970000",
+                    "value": "2800.00000000"
+                }
+            ]
+        }
+    ]
+}
+`
+)
+
+var (
+	expectBnbSingleRPCV2TransferResponse = ExplorerTxs{
+		BlockHeight:        84191216,
+		Code:               0,
+		FromAddr:           "bnb1mr5f97rx5wnkfcakx9fcpvljmx2s6kwqc08yur",
+		HasChildren:        0,
+		Memo:               "",
+		MultisendTransfers: []MultiTransfer{},
+		Timestamp:          1588086357,
+		ToAddr:             "bnb14cjy0yl23xkf0hnw3ql295v8nghqstvlzkvqpl",
+		TxFee:              0.000375,
+		TxHash:             "4577CB3B5B202696E9E0B093A6DA973C7DD9CBC6808DA1326872745C35F3C089",
+		TxType:             "TRANSFER",
+		Value:              0.0004,
+		TxAsset:            "BNB",
+	}
+
+	expectBEP2MultiRPCV2TransferResponse = ExplorerTxs{
+		BlockHeight:        80167666,
+		Code:               0,
+		HasChildren:        1,
+		Memo:               "multisend",
+		MultisendTransfers: bep2MultisendTransfer,
+		Timestamp:          1586464452,
+		TxHash:             "FAD8C1C5E450BE5E0913B12007AAEACC307F8CFFAFFB0844A9F83155E1235C25",
+		TxType:             "TRANSFER",
+		Value:              0,
+	}
+
+	sender   = "bnb1ds83nt2tz2s9m7kkdcu53t3ccjc07un4xvdld7"
+	receiver = "bnb14cjy0yl23xkf0hnw3ql295v8nghqstvlzkvqpl"
+
+	bep2MultisendTransfer = []MultiTransfer{
+		{Amount: "2800.00000000", Asset: "TWT-8C2", From: sender, To: receiver},
+	}
+)
+
+func Test_normalizeBlockSubTx(t *testing.T) {
+	tests := []struct {
+		name       string
+		V2Response string
+		expected   ExplorerTxs
+	}{
+		{name: "Normalize single BNB transfer", V2Response: bnbSingleTransfer, expected: expectBnbSingleRPCV2TransferResponse},
+		{name: "Normalize multiple BEP2 transfer", V2Response: bep2MultiTransfer, expected: expectBEP2MultiRPCV2TransferResponse},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var blockTxs BlockTransactions
+			err := json.Unmarshal([]byte(tt.V2Response), &blockTxs)
+			assert.Nil(t, err)
+			assert.Equal(t, tt.expected, normalizeTxsToExplorer(blockTxs.Txs[0]), "tx don't equal")
+		})
+	}
+}
diff --git a/platform/binance/model_test.go b/platform/binance/model_test.go
index fabaa16..36b61f1 100644
--- a/platform/binance/model_test.go
+++ b/platform/binance/model_test.go
@@ -1,353 +1,248 @@
 package binance
 
 import (
-	"encoding/json"
 	"github.com/stretchr/testify/assert"
-	"sort"
+	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"strconv"
 	"testing"
-	"time"
 )
 
-var newOrderDataDst = Data{OrderData: OrderData{
-	Symbol:   "AWC-986_BNB",
-	Base:     "AWC-986",
-	Quote:    "BNB",
-	Quantity: 2.0,
-	Price:    0.00324939,
-}}
-
-var cancelOrderDataDst = Data{OrderData: OrderData{
-	Symbol:   "GTO-908_BNB",
-	Base:     "GTO-908",
-	Quote:    "BNB",
-	Quantity: 1.0,
-	Price:    0.00104716,
-}}
-
-func TestTx_getData(t *testing.T) {
-	tests := []struct {
-		name string
-		Data string
-		want Data
-	}{
-		{
-			"new order",
-			"{\"orderData\":{\"symbol\":\"AWC-986_BNB\",\"orderType\":\"limit\",\"side\":\"buy\",\"price\":0.00324939,\"quantity\":2.00000000,\"timeInForce\":\"GTE\",\"orderId\":\"D13BAF4BD6638FA3AAD6EBCA0E4BEEA73DF4D519-30\"}}",
-			newOrderDataDst,
-		},
-		{
-			"cancel order",
-			"{\"orderData\":{\"symbol\":\"GTO-908_BNB\",\"orderType\":\"limit\",\"side\":\"buy\",\"price\":0.00104716,\"quantity\":1.00000000,\"timeInForce\":\"GTE\",\"orderId\":\"D13BAF4BD6638FA3AAD6EBCA0E4BEEA73DF4D519-28\"}}",
-			cancelOrderDataDst,
-		},
+func Test_isZeroBalance(t *testing.T) {
+	type testZeroStruct struct {
+		name    string
+		balance Balance
+		want    bool
+	}
+	// all combinations of 3 variables with 2 possible value 0 or 1 is 2^3 = 8
+	tests := []testZeroStruct{
+		{"1", Balance{"0.00000000", "0.00000000", "0.00000000", "BNB"}, true},
+		{"2", Balance{"0.00000000", "0", "0.00000001", "BNB"}, false},
+		{"3", Balance{"0.00000000", "0.00000001", "0.00000000", "BNB"}, false},
+		{"4", Balance{"0.00000000", "0.00000001", "0.00000001", "BNB"}, false},
+		{"5", Balance{"0.00000001", "0.00000000", "0.00000000", "BNB"}, false},
+		{"6", Balance{"0.00000001", "0.00000000", "0.00000001", "BNB"}, false},
+		{"7", Balance{"0.00000001", "0.00000001", "0.00000000", "BNB"}, false},
+		{"8", Balance{"0.00000001", "0.00000001", "0.00000001", "BNB"}, false},
+		{"Negative", Balance{"-0.00000001", "0.00000001", "0.00000001", "BNB"}, false},
+		{"Bad others are 0", Balance{"f", "0.0000000", "0.0000000", "BNB"}, false},
+		{"Bad others are not 0", Balance{"f", "0.0000001", "0.0000000", "BNB"}, false},
+		{"Empty others are not 0", Balance{"", "0.00000001", "0.00000001", "BNB"}, false},
+		{"Empty others are 0", Balance{"", "0.00000000", "0.00000000", "BNB"}, false},
+		{"Big", Balance{"9999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
+			"99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", "0.00000000", "0.00000000", "BNB"}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			tx := &Tx{Data: tt.Data}
-			got, _ := tx.getData()
-			assert.Equal(t, tt.want, got)
+			if got := tt.balance.isAllZeroBalance(); got != tt.want {
+				t.Errorf("isAllZeroBalance() = %v, want %v, name %v", got, tt.want, tt.name)
+			}
 		})
 	}
 }
 
-func TestConvertValue(t *testing.T) {
+//
+
+func TestTx_containAddress(t *testing.T) {
+	type fields struct {
+		FromAddr string
+		ToAddr   string
+	}
 	tests := []struct {
-		name       string
-		value      interface{}
-		wantResult float64
-		wantError  bool
+		name    string
+		fields  fields
+		address string
+		want    bool
 	}{
-		{"test string 1", "9", 9, false},
-		{"test number 1", 9, 9, false},
-		{"test string 2", "9380938973", 9380938973, false},
-		{"test number 2", 9380938973, 9380938973, false},
-		{"test string 3", "0.0000003", 0.0000003, false},
-		{"test number 3", 0.0000003, 0.0000003, false},
-		{"test string 4", "0.44", 0.44, false},
-		{"test number 4", 0.44, 0.44, false},
-		{"test string 5", "3334", 3334, false},
-		{"test number 5", 3334, 3334, false},
-		{"test error", time.Time{}, 3334, true},
+		{"test from address valid", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", true},
+		{"test to address valid", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn", true},
+		{"test no address valid", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "tbnb1qxm48ndhmh7su0r7zgwmwkltuqgly57jdf8yf8", false},
+		{"test empty address", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "", true},
+		{"test empty address without from", fields{FromAddr: "", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "", true},
+		{"test empty address without to", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: ""}, "", true},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, ok := convertValue(tt.value)
-			if tt.wantError {
-				assert.False(t, ok)
-				return
+			tx := &Tx{
+				FromAddr: tt.fields.FromAddr,
+				ToAddr:   tt.fields.ToAddr,
+			}
+			if got := tx.containAddress(tt.address); got != tt.want {
+				t.Errorf("containAddress() = %v, want %v", got, tt.want)
 			}
-			assert.True(t, ok)
-			assert.Equal(t, tt.wantResult, got)
 		})
 	}
 }
 
-func Test_removeFloatPoint(t *testing.T) {
+func Test_getFee(t *testing.T) {
 	tests := []struct {
-		name  string
-		value float64
-		want  int64
+		name string
+		fee  string
+		want string
 	}{
-		{"test float 1", 0.0034, 340000},
-		{"test float 2", 0.00000013, 13},
-		{"test float 3", 0.938984, 93898400},
-		{"test float 4", 0.1, 10000000},
-		{"test int 1", 12, 1200000000},
-		{"test int 2", 2333333333, 233333333300000000},
+		{"test empty", "", "0"},
+		{"test error", "test", "0"},
+		{"test float 1", "444.5", "44450000000"},
+		{"test float 2", "0.00000001", "1"},
+		{"test float 3", "0.00037500", "37500"}, // standard transfer fee
+		{"test int", "3", "300000000"},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := removeFloatPoint(tt.value); got != tt.want {
-				t.Errorf("removeFloatPoint() = %v, want %v", got, tt.want)
+			tx := &Tx{Fee: tt.fee}
+			if got := tx.getFee(); got != tt.want {
+				t.Errorf("getFee() = %v, want %v", got, tt.want)
 			}
 		})
 	}
 }
 
-func Test_isZeroBalance(t *testing.T) {
-	type testZeroStruct struct {
-		name    string
-		balance Balance
-		want    bool
-	}
-	// all combinations of 3 variables with 2 possible value 0 or 1 is 2^3 = 8
-	tests := []testZeroStruct{
-		{"1",
-			Balance{"0.00000000", "0.00000000", "0.00000000", "BNB"},
-			true,
-		},
-		{"2",
-			Balance{"0.00000000", "0", "0.00000001", "BNB"},
-			false,
-		},
-		{"3",
-			Balance{"0.00000000", "0.00000001", "0.00000000", "BNB"},
-			false,
-		},
-		{"4",
-			Balance{"0.00000000", "0.00000001", "0.00000001", "BNB"},
-			false,
-		},
-		{"5",
-			Balance{"0.00000001", "0.00000000", "0.00000000", "BNB"},
-			false,
-		},
-		{"6",
-			Balance{"0.00000001", "0.00000000", "0.00000001", "BNB"},
-			false,
-		},
-		{"7",
-			Balance{"0.00000001", "0.00000001", "0.00000000", "BNB"},
-			false,
-		},
-		{"8",
-			Balance{"0.00000001", "0.00000001", "0.00000001", "BNB"},
-			false,
-		},
-		{"Negative",
-			Balance{"-0.00000001", "0.00000001", "0.00000001", "BNB"},
-			false,
-		},
-		{"Bad others are 0",
-			Balance{"f",
-				"0.0000000", "0.0000000", "BNB"},
-			false,
-		},
-		{"Bad others are not 0",
-			Balance{"f",
-				"0.0000001", "0.0000000", "BNB"},
-			false,
-		},
-		{"Empty others are not 0",
-			Balance{"",
-				"0.00000001", "0.00000001", "BNB"},
-			false,
-		},
-		{"Empty others are 0",
-			Balance{"",
-				"0.00000000", "0.00000000", "BNB"},
-			false,
-		},
-		{"Big",
-			Balance{"9999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" +
-				"99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
-				"0.00000000", "0.00000000", "BNB"},
-			false,
-		},
+func Test_getStatus(t *testing.T) {
+	tests := []struct {
+		name   string
+		trx    Tx
+		expect blockatlas.Status
+	}{
+		{"Should have status completed", Tx{Code: 0}, blockatlas.StatusCompleted},
+		{"Should have status error", Tx{Code: 1}, blockatlas.StatusError},
+		{"Should have status error", Tx{Code: -1}, blockatlas.StatusError},
 	}
 	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := tt.balance.isAllZeroBalance(); got != tt.want {
-				t.Errorf("isAllZeroBalance() = %v, want %v, name %v", got, tt.want, tt.name)
-			}
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, tt.trx.getStatus(), tt.expect)
 		})
 	}
 }
 
-var (
-	subTxDst = SubTx{
-		Hash:     "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-		Height:   63591484,
-		Type:     "TRANSFER",
-		Value:    "0.00000001",
-		Asset:    "BNB",
-		FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-		ToAddr:   "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-		Fee:      "0.0006",
-	}
-	subTxTokenDst = SubTx{
-		Hash:     "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-		Height:   63591485,
-		Type:     "TRANSFER",
-		Value:    "0.000064",
-		Asset:    "AERGO-46B",
-		FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-		ToAddr:   "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-		Fee:      "0.0006",
-	}
-	txDst = Tx{
-		Hash:        "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-		BlockHeight: 63591484,
-		Type:        "TRANSFER",
-		Value:       "0.00000001",
-		Asset:       "BNB",
-		FromAddr:    "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-		ToAddr:      "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-		Fee:         "0.0006",
-	}
-	txTokenDst = Tx{
-		Hash:        "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-		BlockHeight: 63591485,
-		Type:        "TRANSFER",
-		Value:       "0.000064",
-		Asset:       "AERGO-46B",
-		FromAddr:    "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-		ToAddr:      "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-		Fee:         "0.0006",
-	}
-)
-
-func TestSubTx_toTx(t *testing.T) {
+func Test_getError(t *testing.T) {
 	tests := []struct {
-		name  string
-		subTx SubTx
-		want  Tx
+		name   string
+		trx    ExplorerTxs
+		expect string
 	}{
-		{"test conversion subTx to Tx", subTxTokenDst,
-			Tx{
-				Hash:        "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-				BlockHeight: 63591485,
-				Type:        TxTransfer,
-				FromAddr:    "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-				ToAddr:      "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-				Asset:       "AERGO-46B",
-				Fee:         "0.0006",
-				Value:       "0.000064",
-				SubTxsDto:   SubTxsDto{},
-			},
-		},
+		{"Should not have error message", ExplorerTxs{Code: 0}, ""},
+		{"Should have error message", ExplorerTxs{Code: 1}, "error"},
+		{"Should have error message", ExplorerTxs{Code: -1}, "error"},
 	}
 	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got := tt.subTx.toTx()
-			assert.Equal(t, tt.want, got, "conversion failed")
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, tt.trx.getError(), tt.expect)
 		})
 	}
 }
 
-func TestSubTxs_getTxs(t *testing.T) {
-	txDuplicatedTokenDst := txTokenDst
-	txDuplicatedTokenDst.Value = "0.000128"
-	txDuplicatedDst := txDst
-	txDuplicatedDst.Value = "0.00000002"
+func Test_QuantityTransferType(t *testing.T) {
 	tests := []struct {
-		name    string
-		subTxs  SubTxs
-		wantTxs []Tx
+		name   string
+		trx    ExplorerTxs
+		expect ExplorerTransactionType
 	}{
-		{"test empty", SubTxs{}, nil},
-		{"test subTx transfer", SubTxs{subTxDst}, []Tx{txDst}},
-		{"test subTx token transfer", SubTxs{subTxTokenDst}, []Tx{txTokenDst}},
-		{"test subTx and token transfer", SubTxs{subTxDst, subTxTokenDst}, []Tx{txDst, txTokenDst}},
-		{"test duplicate subTx token transfer", SubTxs{subTxTokenDst, subTxTokenDst}, []Tx{txDuplicatedTokenDst}},
-		{"test duplicate subTx", SubTxs{subTxDst, subTxDst}, []Tx{txDuplicatedDst}},
+		{"Should be multi transfer", ExplorerTxs{HasChildren: 1}, MultiTransferOperation},
+		{"Should be single transfer", ExplorerTxs{HasChildren: 0}, SingleTransferOperation},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			gotTxs := tt.subTxs.getTxs()
-			sort.Slice(gotTxs, func(i, j int) bool {
-				return gotTxs[i].BlockHeight < gotTxs[j].BlockHeight
-			})
-			assert.Equal(t, tt.wantTxs, gotTxs, "get txs from subTxs failed")
+			assert.Equal(t, tt.trx.getTransactionType(), tt.expect)
 		})
 	}
 }
 
-func TestTx_containAddress(t *testing.T) {
-	type fields struct {
-		FromAddr string
-		ToAddr   string
-	}
+func TestExplorerTxs_getDirection(t *testing.T) {
+	const (
+		addr1 = "bnb14cjy0yl23xkf0hnw3ql295v8nghqstvlzkvqpl"
+		addr2 = "bnb1mr5f97rx5wnkfcakx9fcpvljmx2s6kwqc08yur"
+	)
+
 	tests := []struct {
 		name    string
-		fields  fields
 		address string
-		want    bool
+		trx     ExplorerTxs
+		expect  blockatlas.Direction
 	}{
-		{"test from address valid", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", true},
-		{"test to address valid", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn", true},
-		{"test no address valid", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "tbnb1qxm48ndhmh7su0r7zgwmwkltuqgly57jdf8yf8", false},
-		{"test empty address", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "", true},
-		{"test empty address without from", fields{FromAddr: "", ToAddr: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn"}, "", true},
-		{"test empty address without to", fields{FromAddr: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8", ToAddr: ""}, "", true},
+		{"getDirection should be self send", addr1, ExplorerTxs{FromAddr: addr1, ToAddr: addr1}, blockatlas.DirectionSelf},
+		{"getDirection should be incoming", addr1, ExplorerTxs{FromAddr: addr2, ToAddr: addr1}, blockatlas.DirectionIncoming},
+		{"getDirection should be outgoing", addr1, ExplorerTxs{FromAddr: addr1, ToAddr: addr2}, blockatlas.DirectionOutgoing},
+		{"getDirection should be empty", "", ExplorerTxs{FromAddr: addr1, ToAddr: addr2}, blockatlas.Direction("")},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			tx := &Tx{
-				FromAddr: tt.fields.FromAddr,
-				ToAddr:   tt.fields.ToAddr,
-			}
-			if got := tx.containAddress(tt.address); got != tt.want {
-				t.Errorf("containAddress() = %v, want %v", got, tt.want)
-			}
+			assert.Equal(t, tt.trx.getDirection(tt.address), tt.expect)
 		})
 	}
 }
 
-func TestTx_getFee(t *testing.T) {
+func Test_getDexFee(t *testing.T) {
 	tests := []struct {
-		name string
-		fee  json.Number
-		want string
+		name      string
+		trx       ExplorerTxs
+		expectFee blockatlas.Amount
 	}{
-		{"test empty", json.Number(""), "0"},
-		{"test error", json.Number("test"), "0"},
-		{"test float 1", json.Number("444.5"), "44450000000"},
-		{"test float 2", json.Number("0.00000001"), "1"},
-		{"test int", json.Number("3"), "300000000"},
+		{"Should have zero fee", ExplorerTxs{TxFee: 0}, blockatlas.Amount(0)},
+		{"Should have zero fee", ExplorerTxs{TxFee: 0.0}, blockatlas.Amount(0)},
+		{"Should have standard fee", ExplorerTxs{TxFee: 0.00037500}, blockatlas.Amount(strconv.Itoa(37500))},
 	}
 	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			tx := &Tx{Fee: tt.fee}
-			if got := tx.getFee(); got != tt.want {
-				t.Errorf("getFee() = %v, want %v", got, tt.want)
-			}
+		t.Run("", func(t *testing.T) {
+			fee := tt.trx.getDexFee()
+			assert.Equal(t, fee, tt.expectFee)
 		})
 	}
 }
+
+func Test_extractMultiTransfers(t *testing.T) {
+	multiAssetTransfer := Value{
+		Msg: []Msg{
+			{
+				MsgValue{
+					Inputs: []Input{
+						{Address: "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8"},
+					},
+					Outputs: []Output{
+						{Address: "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn", Coins: []Coins{
+							{Amount: "3068900000000", Denom: "AERGO-46B"},
+							{Amount: "4100000000", Denom: "BNB"},
+						},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	wantedAssetMultiTransfers := []MultiTransfer{
+		{
+			Amount: "3068900000000",
+			Asset:  "AERGO-46B",
+			From:   "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
+			To:     "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
+		},
+		{
+			Amount: "4100000000",
+			Asset:  "BNB",
+			From:   "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
+			To:     "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
+		},
+	}
+
+	res := extractMultiTransfers(multiAssetTransfer)
+	assert.NotNil(t, res)
+
+	assert.Equal(t, 2, len(res))
+	assert.Equal(t, res[0], wantedAssetMultiTransfers[0])
+	assert.Equal(t, res[1], wantedAssetMultiTransfers[1])
+}
diff --git a/platform/binance/token_test.go b/platform/binance/token_test.go
index 99106ac..33734c0 100644
--- a/platform/binance/token_test.go
+++ b/platform/binance/token_test.go
@@ -120,11 +120,11 @@ func TestNormalizeToken(t *testing.T) {
 			err := json.Unmarshal([]byte(testToken.apiResponse), &srcToken)
 			assert.Nil(t, err)
 
-			var srcTokens TokenPage
+			var srcTokens TokenList
 			err = json.Unmarshal([]byte(testToken.tokens), &srcTokens)
 			assert.Nil(t, err)
 
-			tk, ok := NormalizeToken(&srcToken, &srcTokens)
+			tk, ok := normalizeToken(&srcToken, &srcTokens)
 			assert.Equal(t, testToken.ok, ok, "token: token could not be normalized")
 			assert.Equal(t, testToken.expected, tk, "token: token don't equal")
 		})
diff --git a/platform/binance/transaction_test.go b/platform/binance/transaction_test.go
index cbe80c7..eb4a8a3 100644
--- a/platform/binance/transaction_test.go
+++ b/platform/binance/transaction_test.go
@@ -3,550 +3,174 @@ package binance
 import (
 	"encoding/json"
 	"github.com/stretchr/testify/assert"
-	"github.com/trustwallet/blockatlas/coin"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 	"testing"
 )
 
 const (
-	transferTransaction = `
-{
-	"blockHeight": 7761368,
-	"code": 0,
-	"confirmBlocks": 2089441,
-	"fromAddr": "tbnb1fhr04azuhcj0dulm7ka40y0cqjlafwae9k9gk2",
-	"hasChildren": 0,
-	"log": "Msg 0: ",
-	"timeStamp": 1555049867552,
-	"toAddr": "tbnb1sylyjw032eajr9cyllp26n04300qzzre38qyv5",
-	"txAge": 836729,
-	"txAsset": "BNB",
-	"txFee": 0.00125,
-	"txHash": "1681EE543FB4B5A628EF21D746E031F018E226D127044A4F9BA5EE2542A44555",
-	"txType": "TRANSFER",
-	"value": 100000,
-	"memo": "test"
-}`
-	tokenTransferTransaction = `
-{
-	"blockHeight": 7928667,
-	"code": 0,
-	"confirmBlocks": 1922024,
-	"fromAddr": "tbnb1ttyn4csghfgyxreu7lmdu3lcplhqhxtzced45a",
-	"hasChildren": 0,
-	"log": "Msg 0: ",
-	"timeStamp": 1555117625829,
-	"toAddr": "tbnb12hlquylu78cjylk5zshxpdj6hf3t0tahwjt3ex",
-	"txAge": 768924,
-	"txAsset": "YLC-D8B",
-	"txFee": 0.00125,
-	"txHash": "95CF63FAA27579A9B6AF84EF8B2DFEAC29627479E9C98E7F5AE4535E213FA4C9",
-	"txType": "TRANSFER",
-	"value": 2.10572645,
-	"memo": "test"
-}`
-	newOrderTransaction = `
-{
-      "txHash": "B0677F3436C1B1661E94D192B84B98AA42AC2485D9808357796EE501CBF794F7",
-      "blockHeight": 10815565,
-      "txType": "NEW_ORDER",
-      "timeStamp": 1559689901929,
-      "fromAddr": "bnb16ya67j7kvw8682kka09qujlw5u7lf4geqef0ku",
-      "value": 0.00649878,
-      "txAsset": "BNB",
-      "txQuoteAsset": "AWC-986",
-      "txFee": 0,
-      "txAge": 14346340,
-      "orderId": "D13BAF4BD6638FA3AAD6EBCA0E4BEEA73DF4D519-30",
-      "data": "{\"orderData\":{\"symbol\":\"BNB_AWC-986\",\"orderType\":\"limit\",\"side\":\"buy\",\"price\":0.00324939,\"quantity\":2.00000000,\"timeInForce\":\"GTE\",\"orderId\":\"D13BAF4BD6638FA3AAD6EBCA0E4BEEA73DF4D519-30\"}}",
-      "code": 0,
-      "log": "Msg 0: ",
-      "confirmBlocks": 0,
-      "memo": "",
-      "source": 0,
-      "hasChildren": 0
-}`
-	cancelOrderTransaction = `
-{
-      "txHash": "F48DE755170C10F4A4C0E6836A708C33EEF9A7144800F25187D5F2349FD15A34",
-      "blockHeight": 10815539,
-      "txType": "CANCEL_ORDER",
-      "timeStamp": 1559689892180,
-      "fromAddr": "bnb16ya67j7kvw8682kka09qujlw5u7lf4geqef0ku",
-      "txFee": 0,
-      "txAge": 14346349,
-      "data": "{\"orderData\":{\"symbol\":\"BNB_GTO-908\",\"orderType\":\"limit\",\"side\":\"buy\",\"price\":0.00104716,\"quantity\":1.00000000,\"timeInForce\":\"GTE\",\"orderId\":\"D13BAF4BD6638FA3AAD6EBCA0E4BEEA73DF4D519-28\"}}",
-      "code": 0,
-      "log": "Msg 0: ",
-      "confirmBlocks": 0,
-      "memo": "",
-      "source": 0,
-      "hasChildren": 0
-}`
-	multipleTx = `
-{
-  "txHash": "0C954A46D5AE90EBF9CB7E6F2EAC0E7C3E8DA2DA94B868962164A3AF9D54BEE8",
-  "blockHeight": 64374278,
-  "txType": "TRANSFER",
-  "timeStamp": 1580128370826,
-  "txFee": 0.0006,
-  "txAge": 222591,
-  "code": 0,
-  "log": "Msg 0: ",
-  "confirmBlocks": 553829,
-  "memo": "",
-  "source": 0,
-  "sequence": 154231,
-  "hasChildren": 1,
-  "subTxsDto": {
-    "totalNum": 2,
-    "pageSize": 15,
-    "subTxDtoList": [
-      {
-        "hash": "0C954A46D5AE90EBF9CB7E6F2EAC0E7C3E8DA2DA94B868962164A3AF9D54BEE8",
-        "height": 64374278,
-        "type": "TRANSFER",
-        "value": 3269,
-        "asset": "AERGO-46B",
-        "fromAddr": "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-        "toAddr": "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-        "fee": 0.0006
-      },
-      {
-        "hash": "0C954A46D5AE90EBF9CB7E6F2EAC0E7C3E8DA2DA94B868962164A3AF9D54BEE8",
-        "height": 64374278,
-        "type": "TRANSFER",
-        "value": 1,
-        "asset": "BNB",
-        "fromAddr": "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-        "toAddr": "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-        "fee": null
-      }
-    ]
-  }
-}`
-	multipleTwiceTx = `
-{
-  "txHash": "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-  "blockHeight": 63591484,
-  "txType": "TRANSFER",
-  "timeStamp": 1580421001269,
-  "txFee": 0.0006,
-  "txAge": 18773,
-  "code": 0,
-  "log": "Msg 0: ",
-  "confirmBlocks": 38965,
-  "memo": "Trust Wallet Redeem",
-  "source": 0,
-  "sequence": 33,
-  "hasChildren": 1,
-  "subTxsDto": {
-    "totalNum": 2,
-    "pageSize": 15,
-    "subTxDtoList": [
-      {
-        "hash": "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-        "height": 63591484,
-        "type": "TRANSFER",
-        "value": 1e-8,
-        "asset": "BNB",
-        "fromAddr": "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-        "toAddr": "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-        "fee": 0.0006
-      },
-      {
-        "hash": "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-        "height": 63591484,
-        "type": "TRANSFER",
-        "value": 1e-8,
-        "asset": "BNB",
-        "fromAddr": "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-        "toAddr": "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-        "fee": null
-      }
-    ]
-  }
-}`
+	addr1 = "bnb13a7gyv5zl57c0rzeu0henx6d0tzspvrrakxxtv"
+	addr2 = "bnb1t6tnm2rckd3pfptngj6u8466v3ah4fcdu78n5y"
+)
+
+const (
+	bnbSingleExplorerTransferResponse = `
+        {
+            "txHash": "73176E5BFA5856AEAB9BAB1F3030E6F2B2F274324052E84562BE9BE70E1AAEE7",
+            "blockHeight": 74821444,
+            "txType": "TRANSFER",
+            "timeStamp": 1588086370574,
+            "fromAddr": "bnb13a7gyv5zl57c0rzeu0henx6d0tzspvrrakxxtv",
+            "toAddr": "bnb1t6tnm2rckd3pfptngj6u8466v3ah4fcdu78n5y",
+            "value": 10.00000000,
+            "txAsset": "BNB",
+            "txFee": 0.00037500,
+            "txAge": 1868055,
+            "code": 0,
+			"log": "Msg 0: ",
+            "confirmBlocks": 0,
+			"memo": "bnb-transfer",
+			"source": 1,
+            "hasChildren": 0
+        }`
+
+	bep2SingleExplorerTransferResponse = `
+		{
+            "txHash": "73176E5BFA5856AEAB9BAB1F3030E6F2B2F274324052E84562BE9BE70E1AAEE7",
+            "blockHeight": 74821444,
+            "txType": "TRANSFER",
+            "timeStamp": 1588086357686,
+            "fromAddr": "bnb13a7gyv5zl57c0rzeu0henx6d0tzspvrrakxxtv",
+            "toAddr": "bnb1t6tnm2rckd3pfptngj6u8466v3ah4fcdu78n5y",
+            "value": 2800.00000000,
+            "txAsset": "TWT-8C2",
+            "txFee": 0.00037500,
+            "txAge": 276251,
+            "code": 0,
+            "log": "Msg 0: ",
+            "confirmBlocks": 0,
+            "memo": "bep2-transfer",
+            "source": 0,
+            "hasChildren": 0
+        }`
+	bep2MultipleExplorerTransferResponse = `
+		{
+  			"txHash": "73176E5BFA5856AEAB9BAB1F3030E6F2B2F274324052E84562BE9BE70E1AAEE7",
+  			"blockHeight": 74821444,
+  			"txType": "TRANSFER",
+  			"timeStamp": 1588086357686,
+  			"txFee": 0.00037500,
+  			"txAge": 2068619,
+  			"code": 0,
+  			"log": "Msg 0: ",
+  			"confirmBlocks": 0,
+ 			"memo": "bep2-transfer",
+  			"source": 0,
+  			"hasChildren": 1,
+  			"subTxsDto": [
+   			 {
+      			"amount": "280000000000",
+      			"asset": "TWT-8C2",
+      			"from": "bnb13a7gyv5zl57c0rzeu0henx6d0tzspvrrakxxtv",
+      			"to": "bnb1t6tnm2rckd3pfptngj6u8466v3ah4fcdu78n5y"
+  			 }
+  			 ]
+		}`
 )
 
 var (
-	transferDst = blockatlas.Tx{
-		ID:     "1681EE543FB4B5A628EF21D746E031F018E226D127044A4F9BA5EE2542A44555",
-		Coin:   coin.BNB,
-		From:   "tbnb1fhr04azuhcj0dulm7ka40y0cqjlafwae9k9gk2",
-		To:     "tbnb1sylyjw032eajr9cyllp26n04300qzzre38qyv5",
-		Fee:    "125000",
-		Date:   1555049867,
-		Block:  7761368,
-		Status: blockatlas.StatusCompleted,
-		Memo:   "test",
+	expectBnbSingleExplorerTransfer = blockatlas.Tx{
+		ID:        "73176E5BFA5856AEAB9BAB1F3030E6F2B2F274324052E84562BE9BE70E1AAEE7",
+		Coin:      714,
+		From:      addr1,
+		To:        addr2,
+		Fee:       "37500",
+		Date:      1588086370,
+		Block:     74821444,
+		Status:    blockatlas.StatusCompleted,
+		Error:     "",
+		Sequence:  0,
+		Type:      blockatlas.TxTransfer,
+		Direction: blockatlas.DirectionOutgoing,
+		Memo:      "bnb-transfer",
 		Meta: blockatlas.Transfer{
-			Value:    "10000000000000",
-			Decimals: 8,
+			Value:    "1000000000",
 			Symbol:   "BNB",
-		},
-	}
-	tokenTransferDst = blockatlas.Tx{
-		ID:     "95CF63FAA27579A9B6AF84EF8B2DFEAC29627479E9C98E7F5AE4535E213FA4C9",
-		Coin:   coin.BNB,
-		From:   "tbnb1ttyn4csghfgyxreu7lmdu3lcplhqhxtzced45a",
-		To:     "tbnb12hlquylu78cjylk5zshxpdj6hf3t0tahwjt3ex",
-		Fee:    "125000",
-		Date:   1555117625,
-		Block:  7928667,
-		Status: blockatlas.StatusCompleted,
-		Memo:   "test",
-		Meta: blockatlas.NativeTokenTransfer{
-			TokenID:  "YLC-D8B",
-			Symbol:   "YLC",
-			Value:    "210572645",
 			Decimals: 8,
-			From:     "tbnb1ttyn4csghfgyxreu7lmdu3lcplhqhxtzced45a",
-			To:       "tbnb12hlquylu78cjylk5zshxpdj6hf3t0tahwjt3ex",
 		},
 	}
-	newOrderTransferDst = blockatlas.Tx{
-		ID:     "B0677F3436C1B1661E94D192B84B98AA42AC2485D9808357796EE501CBF794F7",
-		Coin:   coin.BNB,
-		From:   "bnb16ya67j7kvw8682kka09qujlw5u7lf4geqef0ku",
-		Fee:    "0",
-		Date:   1559689901,
-		Block:  10815565,
-		Status: blockatlas.StatusCompleted,
-		Meta: blockatlas.AnyAction{
-			Coin:     coin.BNB,
-			Title:    blockatlas.KeyTitlePlaceOrder,
-			Key:      blockatlas.KeyPlaceOrder,
-			TokenID:  "BNB_AWC-986",
-			Name:     "AWC-986",
-			Symbol:   "AWC",
-			Value:    "649878",
-			Decimals: 8,
-		},
-	}
-	//TODO: temp test dst
-	metaFreeNewOrderTransferDst = blockatlas.Tx{
-		ID:     "B0677F3436C1B1661E94D192B84B98AA42AC2485D9808357796EE501CBF794F7",
-		Coin:   coin.BNB,
-		From:   "bnb16ya67j7kvw8682kka09qujlw5u7lf4geqef0ku",
-		Fee:    "0",
-		Date:   1559689901,
-		Block:  10815565,
-		Status: blockatlas.StatusCompleted,
-		Meta:   nil,
-	}
-	cancelOrdeTransferDst = blockatlas.Tx{
-		ID:     "F48DE755170C10F4A4C0E6836A708C33EEF9A7144800F25187D5F2349FD15A34",
-		Coin:   coin.BNB,
-		From:   "bnb16ya67j7kvw8682kka09qujlw5u7lf4geqef0ku",
-		Fee:    "0",
-		Date:   1559689892,
-		Block:  10815539,
-		Status: blockatlas.StatusCompleted,
-		Meta: blockatlas.AnyAction{
-			Coin:     coin.BNB,
-			Title:    blockatlas.KeyTitleCancelOrder,
-			Key:      blockatlas.KeyCancelOrder,
-			TokenID:  "BNB_GTO-908",
-			Name:     "GTO-908",
-			Symbol:   "GTO",
-			Value:    "104716",
-			Decimals: 8,
-		},
-	}
-	multipleTxDst = blockatlas.Tx{
-		ID:     "0C954A46D5AE90EBF9CB7E6F2EAC0E7C3E8DA2DA94B868962164A3AF9D54BEE8",
-		Coin:   coin.BNB,
-		From:   "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-		To:     "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-		Fee:    "60000",
-		Date:   1580128370,
-		Block:  64374278,
-		Status: blockatlas.StatusCompleted,
+
+	expectBEP2SingleExplorerTransfer = blockatlas.Tx{
+		ID:        "73176E5BFA5856AEAB9BAB1F3030E6F2B2F274324052E84562BE9BE70E1AAEE7",
+		Coin:      714,
+		From:      addr1,
+		To:        addr2,
+		Fee:       "37500",
+		Date:      1588086357,
+		Block:     74821444,
+		Status:    blockatlas.StatusCompleted,
+		Error:     "",
+		Sequence:  0,
+		Type:      blockatlas.TxNativeTokenTransfer,
+		Direction: blockatlas.DirectionIncoming,
+		Memo:      "bep2-transfer",
 		Meta: blockatlas.NativeTokenTransfer{
-			From:     "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-			To:       "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-			TokenID:  "AERGO-46B",
-			Symbol:   "AERGO",
-			Value:    "326900000000",
-			Decimals: 8,
-		},
-	}
-	multipleTwiceTxDst = blockatlas.Tx{
-		ID:     "C29D822EFBC0C91656D1C5870BA55922F3A72A25BC8415B32D1D1AD0C85142F5",
-		Coin:   coin.BNB,
-		From:   "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-		To:     "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-		Fee:    "60000",
-		Date:   1580421001,
-		Block:  63591484,
-		Status: blockatlas.StatusCompleted,
-		Memo:   "Trust Wallet Redeem",
-		Meta: blockatlas.Transfer{
-			Value:    "2",
-			Decimals: 8,
-			Symbol:   "BNB",
-		},
-	}
-	//TODO: temp test dst
-	metaFreeCancelOrdeTransferDst = blockatlas.Tx{
-		ID:     "F48DE755170C10F4A4C0E6836A708C33EEF9A7144800F25187D5F2349FD15A34",
-		Coin:   coin.BNB,
-		From:   "bnb16ya67j7kvw8682kka09qujlw5u7lf4geqef0ku",
-		Fee:    "0",
-		Date:   1559689892,
-		Block:  10815539,
-		Status: blockatlas.StatusCompleted,
-		Meta:   nil,
-	}
-	baseTransferTx = blockatlas.Tx{
-		ID:     "0C954A46D5AE90EBF9CB7E6F2EAC0E7C3E8DA2DA94B868962164A3AF9D54BEE8",
-		Coin:   coin.BNB,
-		From:   "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-		To:     "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-		Fee:    "60000",
-		Date:   1580128370,
-		Block:  63591484,
-		Status: blockatlas.StatusCompleted,
-		Memo:   "Trust Wallet Redeem",
-		Meta: blockatlas.Transfer{
-			Value:    "2",
 			Decimals: 8,
-			Symbol:   "BNB",
+			From:     addr1,
+			Name:     "",
+			Symbol:   "TWT",
+			To:       addr2,
+			TokenID:  "TWT-8C2",
+			Value:    "280000000000",
 		},
 	}
 )
 
-type testTx struct {
-	name        string
-	apiResponse string
-	expected    blockatlas.Tx
-	token       string
-	wantError   bool
-}
-
-func TestNormalizeTx(t *testing.T) {
-	testTxList := []testTx{
-		{
-			name:        "bnb transfer",
-			apiResponse: transferTransaction,
-			expected:    transferDst,
-			token:       "BNB",
-			wantError:   false,
-		},
-		{
-			name:        "native token transfer",
-			apiResponse: tokenTransferTransaction,
-			expected:    tokenTransferDst,
-			token:       "YLC-D8B",
-			wantError:   false,
-		},
-		{
-			name:        "multiple addresses token transfer",
-			apiResponse: multipleTx,
-			expected:    multipleTxDst,
-			token:       "AERGO-46B",
-			wantError:   false,
-		},
-		{
-			name:        "multiple addresses with two transfers",
-			apiResponse: multipleTwiceTx,
-			expected:    multipleTwiceTxDst,
-			token:       "BNB",
-			wantError:   false,
-		},
-		{
-			name:        "new order transfer",
-			apiResponse: newOrderTransaction,
-			expected:    newOrderTransferDst,
-			token:       "AWC-986",
-			wantError:   true,
-		},
-		{
-			name:        "cancel order transfer",
-			apiResponse: cancelOrderTransaction,
-			expected:    cancelOrdeTransferDst,
-			token:       "GTO-908",
-			wantError:   true,
-		},
-		{
-			name:        "new order transfer",
-			apiResponse: newOrderTransaction,
-			expected:    metaFreeNewOrderTransferDst,
-			token:       "AWC-986",
-			wantError:   true,
-		},
-		{
-			name:        "cancel order transfer",
-			apiResponse: cancelOrderTransaction,
-			expected:    metaFreeCancelOrdeTransferDst,
-			token:       "GTO-908",
-			wantError:   true,
-		},
-		{
-			name:        "normalize error transfer",
-			apiResponse: tokenTransferTransaction,
-			token:       "GTO-908",
-			wantError:   true,
-		},
-	}
-
-	for _, testTxInstance := range testTxList {
-		t.Run(testTxInstance.name, func(t *testing.T) {
-			var srcTx Tx
-			err := json.Unmarshal([]byte(testTxInstance.apiResponse), &srcTx)
-			assert.Nil(t, err)
-			tx, ok := NormalizeTx(srcTx, testTxInstance.token, "")
-			if testTxInstance.wantError {
-				assert.False(t, ok, "transfer: tx could be normalized")
-				return
-			}
-			assert.True(t, ok, "transfer: tx could not be normalized")
-			assert.Equal(t, blockatlas.TxPage{testTxInstance.expected}, tx, "transfer: tx don't equal")
-		})
-	}
-}
-
-type testTxs struct {
-	name        string
-	apiResponse string
-	expected    []blockatlas.Tx
-	token       string
-}
-
-func convertJsonToArray(jsonString string) string {
-	return "[" + jsonString + "]"
-}
-
 func TestNormalizeTxs(t *testing.T) {
-	testTxsList := []testTxs{
-		{
-			name:        "bnb transfer",
-			apiResponse: convertJsonToArray(transferTransaction),
-			expected:    []blockatlas.Tx{transferDst},
-			token:       "BNB",
-		},
-		{
-			name:        "native token transfer",
-			apiResponse: convertJsonToArray(tokenTransferTransaction),
-			expected:    []blockatlas.Tx{tokenTransferDst},
-			token:       "YLC-D8B",
-		},
-		//{
-		//	name:        "all transfers",
-		//	apiResponse: AllTransfersType,
-		//	expected:    []blockatlas.Tx{transferDst, tokenTransferDst, newOrderTransferDst, cancelOrdeTransferDst},
-		//	token:       "",
-		//},
-		//{
-		//	name:        "new order transfer",
-		//	apiResponse: convertJsonToArray(newOrderTransaction),
-		//	expected:    []blockatlas.Tx{newOrderTransferDst},
-		//	token:       "AWC-986",
-		//},
-		//{
-		//	name:        "cancel order transfer",
-		//	apiResponse: convertJsonToArray(cancelOrderTransaction),
-		//	expected:    []blockatlas.Tx{cancelOrdeTransferDst},
-		//	token:       "GTO-908",
-		//}
+	type test struct {
+		name, address, dexTxResponse string
+		expected                     []blockatlas.Tx
+	}
+	tests := []test{
+		{name: "BNB single transfer", dexTxResponse: bnbSingleExplorerTransferResponse, expected: []blockatlas.Tx{expectBnbSingleExplorerTransfer}, address: addr1},
+		{name: "BEP2 single transfer", dexTxResponse: bep2SingleExplorerTransferResponse, expected: []blockatlas.Tx{expectBEP2SingleExplorerTransfer}, address: addr2},
+		{name: "BEP2 multiple transfer", dexTxResponse: bep2MultipleExplorerTransferResponse, expected: []blockatlas.Tx{expectBEP2SingleExplorerTransfer}, address: addr2},
 	}
 
-	for _, testTxsInstance := range testTxsList {
-		t.Run(testTxsInstance.name, func(t *testing.T) {
-			var srcTxs []Tx
-			err := json.Unmarshal([]byte(testTxsInstance.apiResponse), &srcTxs)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var srcTx ExplorerTxs
+			err := json.Unmarshal([]byte(tt.dexTxResponse), &srcTx)
 			assert.Nil(t, err)
-			txs := NormalizeTxs(srcTxs, testTxsInstance.token, "")
-			assert.Equal(t, testTxsInstance.expected, txs, "transfer: tx don't equal")
+			actual := normalizeTx(srcTx, tt.address)
+			assert.Equal(t, tt.expected, actual, "tx don't equal")
 		})
 	}
 }
 
 func TestTokenSymbol(t *testing.T) {
-	assert.Equal(t, "UGAS", TokenSymbol("UGAS"))
-	assert.Equal(t, "UGAS", TokenSymbol("UGAS-B0C"))
+	assert.Equal(t, "UGAS", tokenSymbol("UGAS"))
+	assert.Equal(t, "UGAS", tokenSymbol("UGAS-B0C"))
+	assert.Equal(t, "cargabe", tokenSymbol("cargabe"))
+	assert.Equal(t, "CARBAGE", tokenSymbol("CARBAGE"))
+	assert.Equal(t, "", tokenSymbol(""))
 }
 
-var (
-	metaTx = blockatlas.Transfer{
-		Value:    "100000000",
-		Symbol:   "BNB",
-		Decimals: 8,
-	}
-	metaTx2 = blockatlas.Transfer{
-		Value:    "2",
-		Symbol:   "BNB",
-		Decimals: 8,
-	}
-	metaTokenTx = blockatlas.NativeTokenTransfer{
-		Value:    "326900000000",
-		TokenID:  "AERGO-46B",
-		Symbol:   "AERGO",
-		From:     "bnb1nm4n03x00gw0x6v784jzryyp6wxnjaxswr3xm8",
-		To:       "bnb1eff4hzx4lfsun3px5walchdy4vek4n0njcdzyn",
-		Decimals: 8,
-	}
-)
-
-func Test_normalizeTransfer(t *testing.T) {
-	testTx := baseTransferTx
-	testTx.Meta = metaTx
-	testTx2 := baseTransferTx
-	testTx2.Meta = metaTx2
-	testTokenTx := baseTransferTx
-	testTokenTx.Meta = metaTokenTx
-	type args struct {
-		tx      blockatlas.Tx
-		srcTx   string
-		token   string
-		address string
-	}
+func Test_getBase(t *testing.T) {
 	tests := []struct {
-		name  string
-		args  args
-		want  blockatlas.TxPage
-		want1 bool
+		name           string
+		srcTx          ExplorerTxs
+		expectStatus   blockatlas.Status
+		expectErrorMsg string
 	}{
-		{"test multiple tx 1", args{
-			tx:      baseTransferTx,
-			srcTx:   multipleTx,
-			token:   "BNB",
-			address: "",
-		}, blockatlas.TxPage{testTx}, true},
-		{"test multiple tx 2", args{
-			tx:      baseTransferTx,
-			srcTx:   multipleTwiceTx,
-			token:   "BNB",
-			address: "",
-		}, blockatlas.TxPage{testTx2}, true},
-		{"tx multiple token tx", args{
-			tx:      baseTransferTx,
-			srcTx:   multipleTx,
-			token:   "AERGO-46B",
-			address: "",
-		}, blockatlas.TxPage{testTokenTx}, true},
-		{"test multiple tx fail", args{
-			tx:      baseTransferTx,
-			srcTx:   multipleTwiceTx,
-			token:   "AERGO-46B",
-			address: "",
-		}, blockatlas.TxPage{}, false},
-		{"test multiple tx address fail", args{
-			tx:      baseTransferTx,
-			srcTx:   multipleTwiceTx,
-			token:   "AERGO-46B",
-			address: "tbnb1qxm48ndhmh7su0r7zgwmwkltuqgly57jdf8yf8",
-		}, blockatlas.TxPage{}, false},
+		{"Should have status completed", ExplorerTxs{Code: 0}, blockatlas.StatusCompleted, ""},
+		{"Should have status error and error message", ExplorerTxs{Code: 1}, blockatlas.StatusError, "error"},
 	}
+
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			var srcTx Tx
-			err := json.Unmarshal([]byte(tt.args.srcTx), &srcTx)
-			assert.Nil(t, err)
-			got, got1 := normalizeTransfer(tt.args.tx, srcTx, tt.args.token, tt.args.address)
-			assert.Equal(t, tt.want, got)
-			assert.Equal(t, tt.want1, got1)
+			base := getBase(tt.srcTx)
+			assert.Equal(t, base.Status, tt.expectStatus)
+			assert.Equal(t, base.Error, tt.expectErrorMsg)
 		})
 	}
 }
diff --git a/platform/harmony/stake_test.go b/platform/harmony/stake_test.go
new file mode 100644
index 0000000..202672b
--- /dev/null
+++ b/platform/harmony/stake_test.go
@@ -0,0 +1,257 @@
+package harmony
+
+import (
+	"encoding/json"
+	"github.com/stretchr/testify/assert"
+	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"strconv"
+	"testing"
+)
+
+const validatorSrc = `
+ {
+	"validator": {
+		"bls-public-keys": [
+			"29fb5d202e2f6f7955b425dc706fc0b29047067e51ba583fbb017c0f51186d5e1eaf6dd4059848be311ab5a49d625309"
+		],
+		"last-epoch-in-committee": 18,
+		"min-self-delegation": 10999000000000000000000,
+		"max-total-delegation": 100000000000000000000000000,
+		"rate": "0.100000000000000000",
+		"max-rate": "0.100000000000000000",
+		"max-change-rate": "0.100000000000000000",
+		"update-height": 88,
+		"name": "sieemma node",
+		"identity": "sieemma node by ankr",
+		"website": "www.ankr.com",
+		"security-contact": "info@ankr.com",
+		"details": "This validator is launched from app.ankr.com",
+		"creation-height": 88,
+		"address": "one1v8pukmelacy3xdap773rpg5pax3tmu40wmwr2j",
+		"delegations": [
+			{
+				"delegator-address": "one1v8pukmelacy3xdap773rpg5pax3tmu40wmwr2j",
+				"amount": 10999000000000000000000,
+				"reward": 2328233463148225437028,
+				"undelegations": []
+			}
+		]
+	},
+	"current-epoch-performance": {
+		"current-epoch-signing-percent": {
+			"current-epoch-signed": 3,
+			"current-epoch-to-sign": 3,
+			"num-beacon-blocks-until-next-epoch": 37,
+			"current-epoch-signing-percentage": "1.000000000000000000"
+		}
+	},
+	"metrics": {
+		"by-bls-key": [
+			{
+				"key": {
+					"bls-public-key": "29fb5d202e2f6f7955b425dc706fc0b29047067e51ba583fbb017c0f51186d5e1eaf6dd4059848be311ab5a49d625309",
+					"group-percent": "0.056856187290969900",
+					"effective-stake": "85000000000000000000000.000000000000000000",
+					"earning-account": "one1v8pukmelacy3xdap773rpg5pax3tmu40wmwr2j",
+					"overall-percent": "0.018193979933110368",
+					"shard-id": 1
+				},
+				"earned-reward": 4478494623655913952
+			}
+		]
+	},
+	"total-delegation": 10999000000000000000000,
+	"currently-in-committee": true,
+	"epos-status": "currently elected",
+	"epos-winning-stake": "85000000000000000000000.000000000000000000",
+	"booted-status": null,
+	"lifetime": {
+		"reward-accumulated": 2328233463148225437028,
+		"blocks": {
+			"to-sign": 525,
+			"signed": 504
+		},
+		"apr": "12.345"
+	}
+}`
+
+const delegationsSrc = `
+[
+	{
+		"validator_address": "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy",
+		"delegator_address": "one1pf75h0t4am90z8uv3y0dgunfqp4lj8wr3t5rsp",
+		"amount": 12345678900000000000,
+		"reward": 15854399877248931866418,
+		"Undelegations": []
+	}
+]`
+
+func TestNormalizeValidator(t *testing.T) {
+	var v Validator
+	_ = json.Unmarshal([]byte(validatorSrc), &v)
+	expected := blockatlas.Validator{
+		Status: v.Active,
+		ID:     v.Info.Address,
+		Details: blockatlas.StakingDetails{
+			Reward:        blockatlas.StakingReward{Annual: 12.345},
+			LockTime:      lockTime,
+			MinimumAmount: "1000",
+			Type:          blockatlas.DelegationTypeDelegate,
+		},
+	}
+
+	var apr float64
+	var err error
+	if apr, err = strconv.ParseFloat(v.Lifetime.Apr,64); err != nil {
+		apr = 0
+	}
+
+	result := normalizeValidator(v, apr)
+	assert.Equal(t, expected, result)
+}
+
+var validator1 = blockatlas.StakeValidator{
+	ID:     "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy",
+	Status: true,
+	Info: blockatlas.StakeValidatorInfo{
+		Name:        "Harmony One",
+		Description: "Stake and earn rewards with the most secure and stable validator. Operated by Harmony One Inc.",
+		Image:       "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/harmony/validators/assets/one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy/logo.png",
+		Website:     "https://harmony.one",
+	},
+	Details: blockatlas.StakingDetails{
+		Reward: blockatlas.StakingReward{
+			Annual: 10,
+		},
+		LockTime:      0,
+		MinimumAmount: "0",
+	},
+}
+
+var validatorMap = blockatlas.ValidatorMap{
+	"one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy": validator1,
+}
+
+func TestNormalizeDelegations(t *testing.T) {
+	var delegations []Delegation
+	err := json.Unmarshal([]byte(delegationsSrc), &delegations)
+	assert.NoError(t, err)
+	assert.NotNil(t, delegations)
+
+	expected := []blockatlas.Delegation{
+		{
+			Delegator: validator1,
+			Value:     "12345678900000000000",
+			Status:    blockatlas.DelegationStatusActive,
+		},
+	}
+	result := NormalizeDelegations(delegations, validatorMap)
+	assert.Equal(t, expected, result)
+}
+
+func TestHexToInt(t *testing.T) {
+	result, _ := hexToInt("0x604800")
+
+	assert.Equal(t,uint64(6309888), result)
+}
+
+func TestGetDetails(t *testing.T) {
+	var expected = blockatlas.StakingDetails{
+			Reward:        blockatlas.StakingReward{Annual: 10},
+			LockTime:      lockTime,
+			MinimumAmount: "1000",
+			Type:          blockatlas.DelegationTypeDelegate,
+		}
+
+	result := getDetails(10)
+	assert.Equal(t,expected, result)
+}
+
+
+func TestGetValidators(t *testing.T) {
+	var c Client
+
+	p := Platform{
+		client: c,
+	}
+
+	var validators = []Validator{
+		Validator{
+			Info: ValidatorInfo{
+				Address: "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy",
+			},
+			Active: true,
+			Lifetime: LifetimeInfo{Apr: "10"},
+		},
+	}
+
+	rpcCallStub = func (c *Client, result interface{}, method string, params interface{}) error {
+		jsonData, _ := json.Marshal(validators)
+		 _ = json.Unmarshal(jsonData, result)
+		return nil
+	}
+
+	result, _ := p.GetValidators()
+	assert.Equal(t, lockTime, result[0].Details.LockTime)
+	assert.Equal(t, float64(10), result[0].Details.Reward.Annual)
+}
+
+func TestGetDelegation(t *testing.T) {
+	var c Client
+
+	p := Platform{
+		client: c,
+	}
+
+	var delegations = []Delegation{
+		Delegation{
+			DelegatorAddress: "one1a0au0p33zrns49h3qw7prn02s4wphu0ggcqrhm",
+			ValidatorAddress: "one1a0au0p33zrns49h3qw7prn02s4wphu0ggcqrhm",
+			Amount:           100,
+		},
+	}
+
+	var validators = []Validator{
+		Validator{
+			Info: ValidatorInfo{
+				Address: "one1a0au0p33zrns49h3qw7prn02s4wphu0ggcqrhm",
+			},
+			Active: true,
+			Lifetime: LifetimeInfo{Apr: "10"},
+		},
+	}
+
+	rpcCallStub = func (c *Client, result interface{}, method string, params interface{}) error {
+		if (method == "hmy_getAllValidatorInformation") {
+			jsonData, _ := json.Marshal(validators)
+			_ = json.Unmarshal(jsonData, result)
+		} else {
+			jsonData, _ := json.Marshal(delegations)
+			_ = json.Unmarshal(jsonData, result)
+		}
+		return nil
+	}
+
+	result, _ := p.GetDelegations("one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy")
+	assert.Equal(t, delegations[0].DelegatorAddress, result[0].Delegator.ID)
+}
+
+
+func TestGeBalance(t *testing.T) {
+	var c Client
+
+	p := Platform{
+		client: c,
+	}
+
+	var balance = "0x100"
+
+	rpcCallStub = func (c *Client, result interface{}, method string, params interface{}) error {
+		jsonData, _ := json.Marshal(balance)
+		_ = json.Unmarshal(jsonData, result)
+		return nil
+	}
+
+	result, _ := p.UndelegatedBalance("one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy")
+	assert.Equal(t, "256",result)
+}
diff --git a/platform/nimiq/transaction_test.go b/platform/nimiq/transaction_test.go
index 8c0a5f0..f936434 100644
--- a/platform/nimiq/transaction_test.go
+++ b/platform/nimiq/transaction_test.go
@@ -6,6 +6,7 @@ import (
 	"github.com/trustwallet/blockatlas/coin"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 	"io/ioutil"
+	"math"
 	"path/filepath"
 	"runtime"
 	"sort"
@@ -68,7 +69,7 @@ var (
 		From:  "NQ74 SJ0Q 49T1 4XQL KABH 1RUC 8DPT 9F0U 9P0B",
 		To:    "NQ97 18BJ 33YV QGHQ BV2K 56V4 12CH J8TD S9S3",
 		Fee:   "300",
-		Date:  time.Now().Unix(),
+		Date:  666666, // special placholder value
 		Block: 0,
 		Meta: blockatlas.Transfer{
 			Value:    "100000",
@@ -79,6 +80,7 @@ var (
 )
 
 func TestNormalizeTx1(t *testing.T) {
+	now := time.Now().Unix()
 	tests := []struct {
 		name  string
 		srcTx string
@@ -96,6 +98,10 @@ func TestNormalizeTx1(t *testing.T) {
 				return
 			}
 			got := NormalizeTx(&srcTx)
+			// special handling for current date, if around now, replace with special value
+			if math.Abs(float64(got.Date - now)) < 30 {
+				got.Date = 666666
+			}
 			assert.Equal(t, tt.want, got)
 		})
 	}
diff --git a/services/observer/parser/parser_test.go b/services/observer/parser/parser_test.go
index 5f0e6a0..a6e87a3 100644
--- a/services/observer/parser/parser_test.go
+++ b/services/observer/parser/parser_test.go
@@ -1,6 +1,7 @@
 package parser
 
 import (
+	"context"
 	"errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/trustwallet/blockatlas/coin"
@@ -53,38 +54,38 @@ func TestFetchBlocks(t *testing.T) {
 		TxBatchLimit:          0,
 		Database:              nil,
 	}
-	blocks := FetchBlocks(params, 0, 100)
+	blocks := FetchBlocks(params, 0, 100, context.Background())
 	assert.Equal(t, len(blocks), 100)
 }
 
 func TestParser_ConvertToBatch(t *testing.T) {
 	blocks := []blockatlas.Block{block, block, block, block}
-	txs := ConvertToBatch(blocks)
+	txs := ConvertToBatch(blocks, context.Background())
 	assert.Equal(t, 4, len(txs))
 
 	empty := []blockatlas.Block{}
-	txsEmpty := ConvertToBatch(empty)
+	txsEmpty := ConvertToBatch(empty, context.Background())
 	assert.Equal(t, 0, len(txsEmpty))
 }
 
 func TestParser_add(t *testing.T) {
 	blocks := []blockatlas.Block{block, block, block, block}
-	txs := ConvertToBatch(blocks)
+	txs := ConvertToBatch(blocks, context.Background())
 
 	batch := transactionsBatch{
 		Mutex: sync.Mutex{},
 		Txs:   txs,
 	}
 
-	batch.fillBatch(txs)
+	batch.fillBatch(txs, context.Background())
 	assert.Equal(t, 8, len(batch.Txs))
 
-	batch.fillBatch(nil)
+	batch.fillBatch(nil, context.Background())
 	assert.Equal(t, 8, len(batch.Txs))
 }
 
 func TestParser_getBlockByNumberWithRetry(t *testing.T) {
-	block, err := getBlockByNumberWithRetry(3, time.Millisecond*1, getBlock, 1)
+	block, err := getBlockByNumberWithRetry(3, time.Millisecond*1, getBlock, 1, "", context.Background())
 	if err != nil {
 		t.Error(err)
 	}
@@ -96,7 +97,7 @@ func TestParser_getBlockByNumberWithRetry(t *testing.T) {
 
 func TestParser_getBlockByNumberWithRetry_Error(t *testing.T) {
 	now := time.Now()
-	block, err := getBlockByNumberWithRetry(2, time.Millisecond*2, getBlock, 0)
+	block, err := getBlockByNumberWithRetry(2, time.Millisecond*2, getBlock, 0, "", context.Background())
 	elapsed := time.Since(now)
 	if err == nil {
 		t.Error("getBlockByNumberWithRetry method need fail")
@@ -141,35 +142,35 @@ func (p *Platform) GetBlockByNumber(num int64) (*blockatlas.Block, error) {
 
 func TestGetTxBatches(t *testing.T) {
 	txs := make(blockatlas.Txs, 10000)
-	batches := getTxsBatches(txs, 1000)
+	batches := getTxsBatches(txs, 1000, context.Background())
 	assert.Len(t, batches, 10)
-	batches = getTxsBatches(txs, 100)
+	batches = getTxsBatches(txs, 100, context.Background())
 	assert.Len(t, batches, 100)
-	batches = getTxsBatches(txs, 500)
+	batches = getTxsBatches(txs, 500, context.Background())
 	assert.Len(t, batches, 20)
 
 	txs = make(blockatlas.Txs, 3800)
-	batches = getTxsBatches(txs, 100)
+	batches = getTxsBatches(txs, 100, context.Background())
 	assert.Len(t, batches, 38)
-	batches = getTxsBatches(txs, 1000)
+	batches = getTxsBatches(txs, 1000, context.Background())
 	assert.Len(t, batches, 4)
 
 	txs = make(blockatlas.Txs, 5000)
-	batches = getTxsBatches(txs, 10000)
+	batches = getTxsBatches(txs, 10000, context.Background())
 	assert.Len(t, batches, 1)
 
 	txs = make(blockatlas.Txs, 0)
-	batches = getTxsBatches(txs, 100)
+	batches = getTxsBatches(txs, 100, context.Background())
 	assert.Len(t, batches, 0)
 
 	txs = make(blockatlas.Txs, 0)
-	batches = getTxsBatches(txs, 100)
+	batches = getTxsBatches(txs, 100, context.Background())
 	assert.Len(t, batches, 0)
 
-	batches = getTxsBatches(nil, 100)
+	batches = getTxsBatches(nil, 100, context.Background())
 	assert.Len(t, batches, 0)
 
 	txs = make(blockatlas.Txs, 1000000)
-	batches = getTxsBatches(txs, 5000)
+	batches = getTxsBatches(txs, 5000, context.Background())
 	assert.Len(t, batches, 200)
 }
diff --git a/services/observer/subscriber/subscriber_test.go b/services/observer/subscriber/subscriber_test.go
new file mode 100644
index 0000000..713b879
--- /dev/null
+++ b/services/observer/subscriber/subscriber_test.go
@@ -0,0 +1,37 @@
+package subscriber
+
+import (
+	"github.com/stretchr/testify/assert"
+	"github.com/trustwallet/blockatlas/db/models"
+	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"testing"
+)
+
+func TestToSubscriptionData(t *testing.T) {
+	sub := blockatlas.Subscription{
+		Coin:    60,
+		Address: "A",
+		Id:      1,
+	}
+	sub2 := blockatlas.Subscription{
+		Coin:    60,
+		Address: "B",
+		Id:      2,
+	}
+
+	expectedModel := models.SubscriptionData{
+		SubscriptionId: 1,
+		Coin:           60,
+		Address:        "A",
+	}
+	expectedModel1 := models.SubscriptionData{
+		SubscriptionId: 2,
+		Coin:           60,
+		Address:        "B",
+	}
+
+	res := ToSubscriptionData([]blockatlas.Subscription{sub, sub2})
+	assert.Equal(t, 2, len(res))
+	assert.Equal(t, expectedModel, res[0])
+	assert.Equal(t, expectedModel1, res[1])
+}
diff --git a/tests/integration/db_test/subscriptions_test.go b/tests/integration/db_test/subscriptions_test.go
index 49a5e03..0cafda6 100644
--- a/tests/integration/db_test/subscriptions_test.go
+++ b/tests/integration/db_test/subscriptions_test.go
@@ -3,6 +3,7 @@
 package db_test
 
 import (
+	"context"
 	"github.com/stretchr/testify/assert"
 	"github.com/trustwallet/blockatlas/db/models"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
@@ -26,9 +27,9 @@ func TestDb_AddSubscriptionsBulk(t *testing.T) {
 		})
 	}
 
-	assert.Nil(t, database.AddSubscriptions(id, subscriptions))
+	assert.Nil(t, database.AddSubscriptions(id, subscriptions, context.Background()))
 	for i := 0; i < 100; i++ {
-		s, err := database.GetSubscriptionData(uint(i), []string{"testAddrtestAddrtestAddrtestAddrtestAddrtestAddrtestAddrtestAddrtestAddrtestAddr"})
+		s, err := database.GetSubscriptionData(uint(i), []string{"testAddrtestAddrtestAddrtestAddrtestAddrtestAddrtestAddrtestAddrtestAddrtestAddr"}, context.Background())
 		assert.Nil(t, err)
 		assert.Equal(t, id, s[0].SubscriptionId)
 	}
@@ -58,9 +59,9 @@ func TestDb_AddSubscriptions(t *testing.T) {
 		Address:        "testAddr3",
 	})
 
-	assert.Nil(t, database.AddSubscriptions(id, subscriptions))
+	assert.Nil(t, database.AddSubscriptions(id, subscriptions, context.Background()))
 
-	subs, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs)
 	assert.Equal(t, 1, len(subs))
@@ -68,7 +69,7 @@ func TestDb_AddSubscriptions(t *testing.T) {
 	assert.Equal(t, subscriptions[0].Coin, subs[0].Coin)
 	assert.Equal(t, subscriptions[0].Address, subs[0].Address)
 
-	subs, err = database.GetSubscriptionData(61, []string{"testAddr2"})
+	subs, err = database.GetSubscriptionData(61, []string{"testAddr2"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs)
 	assert.Equal(t, 1, len(subs))
@@ -76,7 +77,7 @@ func TestDb_AddSubscriptions(t *testing.T) {
 	assert.Equal(t, subscriptions[1].Coin, subs[0].Coin)
 	assert.Equal(t, subscriptions[1].Address, subs[0].Address)
 
-	subs, err = database.GetSubscriptionData(62, []string{"testAddr3"})
+	subs, err = database.GetSubscriptionData(62, []string{"testAddr3"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs)
 	assert.Equal(t, 1, len(subs))
@@ -108,9 +109,9 @@ func TestDb_AddSubscriptionsWithRewrite(t *testing.T) {
 		Address:        "testAddr",
 	})
 
-	assert.Nil(t, database.AddSubscriptions(id, subscriptions))
+	assert.Nil(t, database.AddSubscriptions(id, subscriptions, context.Background()))
 
-	subs60, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs60, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs60)
 	assert.Equal(t, 1, len(subs60))
@@ -118,7 +119,7 @@ func TestDb_AddSubscriptionsWithRewrite(t *testing.T) {
 	assert.Equal(t, subscriptions[0].Coin, subs60[0].Coin)
 	assert.Equal(t, subscriptions[0].Address, subs60[0].Address)
 
-	subs714, err := database.GetSubscriptionData(714, []string{"testAddr"})
+	subs714, err := database.GetSubscriptionData(714, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs714)
 	assert.Equal(t, 1, len(subs714))
@@ -126,7 +127,7 @@ func TestDb_AddSubscriptionsWithRewrite(t *testing.T) {
 	assert.Equal(t, subscriptions[1].Coin, subs714[0].Coin)
 	assert.Equal(t, subscriptions[1].Address, subs714[0].Address)
 
-	subs144, err := database.GetSubscriptionData(144, []string{"testAddr"})
+	subs144, err := database.GetSubscriptionData(144, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs144)
 	assert.Equal(t, 1, len(subs144))
@@ -152,9 +153,9 @@ func TestDb_AddSubscriptionsWithRewrite(t *testing.T) {
 		Address:        "testAddr2",
 	})
 
-	assert.Nil(t, database.AddSubscriptions(id, subscriptions))
+	assert.Nil(t, database.AddSubscriptions(id, subscriptions, context.Background()))
 
-	subs2N60, err := database.GetSubscriptionData(60, []string{"testAddr2"})
+	subs2N60, err := database.GetSubscriptionData(60, []string{"testAddr2"}, context.Background())
 	assert.Nil(t, err)
 	assert.Nil(t, err)
 	assert.NotNil(t, subs2N60)
@@ -163,7 +164,7 @@ func TestDb_AddSubscriptionsWithRewrite(t *testing.T) {
 	assert.Equal(t, subscriptions[3].Coin, subs2N60[0].Coin)
 	assert.Equal(t, subscriptions[3].Address, subs2N60[0].Address)
 
-	subs2N714, err := database.GetSubscriptionData(714, []string{"testAddr2"})
+	subs2N714, err := database.GetSubscriptionData(714, []string{"testAddr2"}, context.Background())
 	assert.Nil(t, err)
 	assert.Nil(t, err)
 	assert.NotNil(t, subs2N714)
@@ -172,7 +173,7 @@ func TestDb_AddSubscriptionsWithRewrite(t *testing.T) {
 	assert.Equal(t, subscriptions[4].Coin, subs2N714[0].Coin)
 	assert.Equal(t, subscriptions[4].Address, subs2N714[0].Address)
 
-	subs2N114, err := database.GetSubscriptionData(144, []string{"testAddr2"})
+	subs2N114, err := database.GetSubscriptionData(144, []string{"testAddr2"}, context.Background())
 	assert.Nil(t, err)
 	assert.Nil(t, err)
 	assert.NotNil(t, subs2N114)
@@ -217,7 +218,7 @@ func TestDb_FindSubscriptions(t *testing.T) {
 		Address: "ETCAddress",
 	})
 
-	assert.Nil(t, database.AddSubscriptions(id, subscriber.ToSubscriptionData(subscriptionsA)))
+	assert.Nil(t, database.AddSubscriptions(id, subscriber.ToSubscriptionData(subscriptionsA), context.Background()))
 
 	var subscriptionsB []blockatlas.Subscription
 
@@ -225,25 +226,25 @@ func TestDb_FindSubscriptions(t *testing.T) {
 		sub.Id = uint(2)
 		subscriptionsB = append(subscriptionsB, sub)
 	}
-	assert.Nil(t, database.AddSubscriptions(2, subscriber.ToSubscriptionData(subscriptionsB)))
+	assert.Nil(t, database.AddSubscriptions(2, subscriber.ToSubscriptionData(subscriptionsB), context.Background()))
 
-	returnedSubs, err := database.GetSubscriptionData(60, []string{"etherAddress"})
+	returnedSubs, err := database.GetSubscriptionData(60, []string{"etherAddress"}, context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, 2, len(returnedSubs))
 
-	returnedSubs, err = database.GetSubscriptionData(714, []string{"binanceAddress"})
+	returnedSubs, err = database.GetSubscriptionData(714, []string{"binanceAddress"}, context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, 2, len(returnedSubs))
 
-	returnedSubs, err = database.GetSubscriptionData(144, []string{"XLMAddress"})
+	returnedSubs, err = database.GetSubscriptionData(144, []string{"XLMAddress"}, context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, 2, len(returnedSubs))
 
-	returnedSubs, err = database.GetSubscriptionData(148, []string{"AtomAddress"})
+	returnedSubs, err = database.GetSubscriptionData(148, []string{"AtomAddress"}, context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, 2, len(returnedSubs))
 
-	returnedSubs, err = database.GetSubscriptionData(61, []string{"ETCAddress"})
+	returnedSubs, err = database.GetSubscriptionData(61, []string{"ETCAddress"}, context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, 2, len(returnedSubs))
 }
@@ -271,9 +272,9 @@ func TestDb_DeleteSubscriptions(t *testing.T) {
 		Address:        "testAddr3",
 	})
 
-	assert.Nil(t, database.AddSubscriptions(id, subscriptions))
+	assert.Nil(t, database.AddSubscriptions(id, subscriptions, context.Background()))
 
-	subs60, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs60, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs60)
 	assert.Equal(t, 1, len(subs60))
@@ -281,7 +282,7 @@ func TestDb_DeleteSubscriptions(t *testing.T) {
 	assert.Equal(t, subscriptions[0].Coin, subs60[0].Coin)
 	assert.Equal(t, subscriptions[0].Address, subs60[0].Address)
 
-	subs714, err := database.GetSubscriptionData(714, []string{"testAddr2"})
+	subs714, err := database.GetSubscriptionData(714, []string{"testAddr2"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs714)
 	assert.Equal(t, 1, len(subs714))
@@ -289,7 +290,7 @@ func TestDb_DeleteSubscriptions(t *testing.T) {
 	assert.Equal(t, subscriptions[1].Coin, subs714[0].Coin)
 	assert.Equal(t, subscriptions[1].Address, subs714[0].Address)
 
-	subs144, err := database.GetSubscriptionData(144, []string{"testAddr3"})
+	subs144, err := database.GetSubscriptionData(144, []string{"testAddr3"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs144)
 	assert.Equal(t, 1, len(subs144))
@@ -302,7 +303,7 @@ func TestDb_DeleteSubscriptions(t *testing.T) {
 
 	assert.Nil(t, database.DeleteSubscriptions(subsToDel))
 
-	subs714N2, err := database.GetSubscriptionData(714, []string{"testAddr2"})
+	subs714N2, err := database.GetSubscriptionData(714, []string{"testAddr2"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs714N2)
 	assert.Equal(t, 1, len(subs714N2))
@@ -310,7 +311,7 @@ func TestDb_DeleteSubscriptions(t *testing.T) {
 	assert.Equal(t, subscriptions[1].Coin, subs714N2[0].Coin)
 	assert.Equal(t, subscriptions[1].Address, subs714N2[0].Address)
 
-	subs144N2, err := database.GetSubscriptionData(144, []string{"testAddr3"})
+	subs144N2, err := database.GetSubscriptionData(144, []string{"testAddr3"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs144N2)
 	assert.Equal(t, 1, len(subs144N2))
@@ -318,7 +319,7 @@ func TestDb_DeleteSubscriptions(t *testing.T) {
 	assert.Equal(t, subscriptions[2].Coin, subs144N2[0].Coin)
 	assert.Equal(t, subscriptions[2].Address, subs144N2[0].Address)
 
-	subs60N2, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs60N2, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, subs60N2, 0)
 }
@@ -345,37 +346,37 @@ func TestDeleteAll(t *testing.T) {
 		Coin:           144,
 		Address:        "testAddr3",
 	})
-	assert.Nil(t, database.AddSubscriptions(id, subscriptions))
+	assert.Nil(t, database.AddSubscriptions(id, subscriptions, context.Background()))
 
-	subs60, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs60, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, subs60, 1)
 
-	subs714, err := database.GetSubscriptionData(714, []string{"testAddr2"})
+	subs714, err := database.GetSubscriptionData(714, []string{"testAddr2"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, subs714, 1)
 
-	subs144, err := database.GetSubscriptionData(144, []string{"testAddr3"})
+	subs144, err := database.GetSubscriptionData(144, []string{"testAddr3"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, subs144, 1)
 
-	assert.Nil(t, database.DeleteAllSubscriptions(1))
+	assert.Nil(t, database.DeleteAllSubscriptions(1, context.Background()))
 
-	subs60, err = database.GetSubscriptionData(60, []string{"testAddr"})
+	subs60, err = database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, subs60, 0)
 
-	subs714, err = database.GetSubscriptionData(714, []string{"testAddr2"})
+	subs714, err = database.GetSubscriptionData(714, []string{"testAddr2"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, subs714, 0)
 
-	subs144, err = database.GetSubscriptionData(144, []string{"testAddr3"})
+	subs144, err = database.GetSubscriptionData(144, []string{"testAddr3"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, subs144, 0)
 
-	assert.Nil(t, database.AddSubscriptions(id, subscriptions))
+	assert.Nil(t, database.AddSubscriptions(id, subscriptions, context.Background()))
 
-	subs60, err = database.GetSubscriptionData(60, []string{"testAddr"})
+	subs60, err = database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, subs60, 1)
 }
@@ -394,9 +395,9 @@ func TestDb_DuplicateEntries(t *testing.T) {
 		})
 	}
 
-	assert.Nil(t, database.AddSubscriptions(id, subscriptions))
+	assert.Nil(t, database.AddSubscriptions(id, subscriptions, context.Background()))
 
-	subs, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.NotNil(t, subs)
 	assert.Equal(t, 1, len(subs))
@@ -413,13 +414,13 @@ func TestDb_FindSubscriptions_Multiple(t *testing.T) {
 
 	for i := 1; i < 6; i++ {
 		subscriptions[0].SubscriptionId = uint(i)
-		assert.Nil(t, database.AddSubscriptions(uint(i), subscriptions))
+		assert.Nil(t, database.AddSubscriptions(uint(i), subscriptions, context.Background()))
 	}
 
 	subscriptions[0].SubscriptionId = uint(1)
-	assert.Nil(t, database.AddSubscriptions(uint(1), subscriptions))
+	assert.Nil(t, database.AddSubscriptions(uint(1), subscriptions, context.Background()))
 
-	subs, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, 5, len(subs))
 
@@ -437,16 +438,16 @@ func TestDb_AddToExisting(t *testing.T) {
 	})
 
 	subscriptions[0].SubscriptionId = uint(1)
-	assert.Nil(t, database.AddSubscriptions(uint(1), subscriptions))
+	assert.Nil(t, database.AddSubscriptions(uint(1), subscriptions, context.Background()))
 
-	subs, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, 1, len(subs))
 
 	assert.Equal(t, uint(1), subs[0].SubscriptionId)
 	assert.Nil(t, database.AddToExistingSubscription(uint(1), subscriptions))
 
-	subs2, err2 := database.GetSubscriptionData(60, []string{"testAddr"})
+	subs2, err2 := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err2)
 	assert.Equal(t, 1, len(subs2))
 
@@ -471,8 +472,8 @@ func TestDb_UpdatedAt(t *testing.T) {
 	})
 
 	subscriptions[0].SubscriptionId = uint(1)
-	assert.Nil(t, database.AddSubscriptions(uint(1), subscriptions))
-	subs, err := database.GetSubscriptionData(60, []string{"testAddr"})
+	assert.Nil(t, database.AddSubscriptions(uint(1), subscriptions, context.Background()))
+	subs, err := database.GetSubscriptionData(60, []string{"testAddr"}, context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, 1, len(subs))
 
@@ -488,7 +489,7 @@ func TestDb_UpdatedAt(t *testing.T) {
 		Address: "newtestAddr",
 	})
 
-	assert.Nil(t, database.AddSubscriptions(uint(1), subscriptions))
+	assert.Nil(t, database.AddSubscriptions(uint(1), subscriptions, context.Background()))
 
 	time.Sleep(time.Second)
 
diff --git a/tests/integration/db_test/tracker_test.go b/tests/integration/db_test/tracker_test.go
index 4e63587..5ba03bc 100644
--- a/tests/integration/db_test/tracker_test.go
+++ b/tests/integration/db_test/tracker_test.go
@@ -3,6 +3,7 @@
 package db_test
 
 import (
+	"context"
 	"github.com/stretchr/testify/assert"
 	"github.com/trustwallet/blockatlas/tests/integration/setup"
 	"testing"
@@ -11,15 +12,15 @@ import (
 func TestDb_SetBlock(t *testing.T) {
 	setup.CleanupPgContainer(database.Gorm)
 
-	assert.Nil(t, database.SetLastParsedBlockNumber("ethereum", 0))
+	assert.Nil(t, database.SetLastParsedBlockNumber("ethereum", 0, context.Background()))
 
-	block, err := database.GetLastParsedBlockNumber("ethereum")
+	block, err := database.GetLastParsedBlockNumber("ethereum", context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, block, int64(0))
 
-	assert.Nil(t, database.SetLastParsedBlockNumber("ethereum", 110))
+	assert.Nil(t, database.SetLastParsedBlockNumber("ethereum", 110, context.Background()))
 
-	newBlock, err := database.GetLastParsedBlockNumber("ethereum")
+	newBlock, err := database.GetLastParsedBlockNumber("ethereum", context.Background())
 	assert.Nil(t, err)
 	assert.Equal(t, newBlock, int64(110))
 }
diff --git a/tests/integration/observer_test/full_flow_test.go b/tests/integration/observer_test/full_flow_test.go
index aeaf88f..29cce3a 100644
--- a/tests/integration/observer_test/full_flow_test.go
+++ b/tests/integration/observer_test/full_flow_test.go
@@ -25,7 +25,7 @@ var (
 
 func TestFullFlow(t *testing.T) {
 	setup.CleanupPgContainer(database.Gorm)
-	err := database.AddSubscriptions(1, []models.SubscriptionData{{Coin: 60, Address: "testAddress", SubscriptionId: 1}})
+	err := database.AddSubscriptions(1, []models.SubscriptionData{{Coin: 60, Address: "testAddress", SubscriptionId: 1}}, context.Background())
 	assert.Nil(t, err)
 
 	ctx, cancel := context.WithCancel(context.Background())
diff --git a/tests/integration/observer_test/notifier_test.go b/tests/integration/observer_test/notifier_test.go
index 4737b8b..0f6a1ab 100644
--- a/tests/integration/observer_test/notifier_test.go
+++ b/tests/integration/observer_test/notifier_test.go
@@ -44,7 +44,7 @@ var (
 func TestNotifier(t *testing.T) {
 	setup.CleanupPgContainer(database.Gorm)
 
-	err := database.AddSubscriptions(1, []models.SubscriptionData{{Coin: 714, Address: "tbnb1ttyn4csghfgyxreu7lmdu3lcplhqhxtzced45a", SubscriptionId: 1}})
+	err := database.AddSubscriptions(1, []models.SubscriptionData{{Coin: 714, Address: "tbnb1ttyn4csghfgyxreu7lmdu3lcplhqhxtzced45a", SubscriptionId: 1}}, context.Background())
 	assert.Nil(t, err)
 
 	err = produceTxs(txs)
diff --git a/tests/integration/observer_test/subscriber_test.go b/tests/integration/observer_test/subscriber_test.go
index 94cd29d..b7e9ac1 100644
--- a/tests/integration/observer_test/subscriber_test.go
+++ b/tests/integration/observer_test/subscriber_test.go
@@ -57,7 +57,7 @@ func TestSubscriberAddSubscription(t *testing.T) {
 	}
 
 	for _, wanted := range wantedEvents {
-		result, err := database.GetSubscriptionData(wanted.Coin, []string{wanted.Address})
+		result, err := database.GetSubscriptionData(wanted.Coin, []string{wanted.Address}, context.Background())
 		assert.Nil(t, err)
 		assert.Equal(t, result[0].SubscriptionId, wanted.Id)
 		assert.Equal(t, result[0].Coin, wanted.Coin)
@@ -94,16 +94,16 @@ func TestSubscriber_UpdateSubscription(t *testing.T) {
 
 	database.AddSubscriptions(10, []models.SubscriptionData{
 		{Coin: 61, Address: "0x0000000000000000000000000000000000000000", SubscriptionId: 10},
-	})
+	}, context.Background())
 	database.AddSubscriptions(1, []models.SubscriptionData{
 		{Coin: 62, Address: "0x0000000000000000000000000000000000000000", SubscriptionId: 1},
-	})
+	}, context.Background())
 	database.AddSubscriptions(2, []models.SubscriptionData{
 		{Coin: 63, Address: "0x0000000000000000000000000000000000000000", SubscriptionId: 2},
-	})
+	}, context.Background())
 	database.AddSubscriptions(3, []models.SubscriptionData{
 		{Coin: 64, Address: "0x0000000000000000000000000000000000000000", SubscriptionId: 3},
-	})
+	}, context.Background())
 
 	for _, event := range givenEvents {
 		body, err := json.Marshal(event)
@@ -120,7 +120,7 @@ func TestSubscriber_UpdateSubscription(t *testing.T) {
 	}
 
 	for _, wanted := range wantedEvents {
-		result, err := database.GetSubscriptionData(wanted.Coin, []string{wanted.Address})
+		result, err := database.GetSubscriptionData(wanted.Coin, []string{wanted.Address}, context.Background())
 		assert.Nil(t, err)
 		assert.Equal(t, result[0].SubscriptionId, wanted.Id)
 		assert.Equal(t, result[0].Coin, wanted.Coin)
@@ -128,19 +128,19 @@ func TestSubscriber_UpdateSubscription(t *testing.T) {
 
 	}
 
-	abs61, err := database.GetSubscriptionData(61, []string{"0x0000000000000000000000000000000000000000"})
+	abs61, err := database.GetSubscriptionData(61, []string{"0x0000000000000000000000000000000000000000"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, abs61, 0)
 
-	abs62, err := database.GetSubscriptionData(62, []string{"0x0000000000000000000000000000000000000000"})
+	abs62, err := database.GetSubscriptionData(62, []string{"0x0000000000000000000000000000000000000000"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, abs62, 0)
 
-	abs63, err := database.GetSubscriptionData(63, []string{"0x0000000000000000000000000000000000000000"})
+	abs63, err := database.GetSubscriptionData(63, []string{"0x0000000000000000000000000000000000000000"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, abs63, 0)
 
-	abs64, err := database.GetSubscriptionData(64, []string{"0x0000000000000000000000000000000000000000"})
+	abs64, err := database.GetSubscriptionData(64, []string{"0x0000000000000000000000000000000000000000"}, context.Background())
 	assert.Nil(t, err)
 	assert.Len(t, abs64, 0)
 }
diff --git a/tests/integration/setup/postgres.go b/tests/integration/setup/postgres.go
index c52eb76..63ee3aa 100644
--- a/tests/integration/setup/postgres.go
+++ b/tests/integration/setup/postgres.go
@@ -39,7 +39,7 @@ func runPgContainerAndInitConnection() (*db.Instance, error) {
 		err    error
 	)
 	if err := pool.Retry(func() error {
-		dbConn, err = db.New(uri)
+		dbConn, err = db.New(uri, "test")
 		return err
 	}); err != nil {
 		return nil, err
diff --git a/tests/postman/token_data.json b/tests/postman/token_data.json
index da7a287..f53b1c7 100644
--- a/tests/postman/token_data.json
+++ b/tests/postman/token_data.json
@@ -29,10 +29,10 @@
     },
     {
         "handler": "binance",
-        "address": "bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m"
+        "address": "bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q"
     },
     {
         "handler": "classic",
         "address": "0xa12105efa0663147bddee178f6a741ac15676b79"
     }
-]
\ No newline at end of file
+]
diff --git a/tests/postman/transaction_data.json b/tests/postman/transaction_data.json
index 35682bd..5cf4224 100644
--- a/tests/postman/transaction_data.json
+++ b/tests/postman/transaction_data.json
@@ -85,9 +85,9 @@
     },
     {
         "handler": "binance",
-        "address": "bnb1mnf0g6zhy2rpy63w8ryc6yp77s0fmqfyzjtkvd",
+        "address": "bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q",
         "expectedTxNum": 1,
-        "expectedTxId": "DDA939C475755B2A00123CDA37A5EC442F502EA2983014D04F647C3CB9FB57A0"
+        "expectedTxId": "4577CB3B5B202696E9E0B093A6DA973C7DD9CBC6808DA1326872745C35F3C089"
     },
     {
         "handler": "tezos",