Repository: trustwallet/blockatlas
Pull request #1350 (2021-01-07 05:17:37)

Consolidate bitcoin client with blockbook

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

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

```diff
@@          Coverage Diff           @@
##           master   #1350   +/-   ##
======================================
- Coverage      44%     44%   -1%     
======================================
  Files         145     144    -1     
  Lines        4405    4391   -14     
======================================
- Hits         1965    1957    -8     
+ Misses       2266    2262    -4     
+ Partials      174     172    -2     
```


| [Impacted Files](https://codecov.io/gh/trustwallet/blockatlas/pull/1350?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/blockatlas/marshal.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGtnL2Jsb2NrYXRsYXMvbWFyc2hhbC5nbw==) | `27% <0%> (ø)` | |
| [pkg/blockatlas/tx.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGtnL2Jsb2NrYXRsYXMvdHguZ28=) | `50% <ø> (ø)` | |
| [platform/bitcoin/base.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGxhdGZvcm0vYml0Y29pbi9iYXNlLmdv) | `0% <0%> (ø)` | |
| [platform/bitcoin/block.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGxhdGZvcm0vYml0Y29pbi9ibG9jay5nbw==) | `0% <0%> (ø)` | |
| [platform/elrond/client.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGxhdGZvcm0vZWxyb25kL2NsaWVudC5nbw==) | `0% <ø> (ø)` | |
| [platform/ethereum/blockbook/block.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGxhdGZvcm0vZXRoZXJldW0vYmxvY2tib29rL2Jsb2NrLmdv) | `0% <0%> (ø)` | |
| [platform/ethereum/blockbook/client.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGxhdGZvcm0vZXRoZXJldW0vYmxvY2tib29rL2NsaWVudC5nbw==) | `0% <0%> (ø)` | |
| [platform/ethereum/blockbook/model.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGxhdGZvcm0vZXRoZXJldW0vYmxvY2tib29rL21vZGVsLmdv) | `0% <0%> (ø)` | |
| [platform/ethereum/trustray/block.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGxhdGZvcm0vZXRoZXJldW0vdHJ1c3RyYXkvYmxvY2suZ28=) | `0% <ø> (ø)` | |
| [platform/harmony/block.go](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree#diff-cGxhdGZvcm0vaGFybW9ueS9ibG9jay5nbw==) | `0% <ø> (ø)` | |
| ... and [7 more](https://codecov.io/gh/trustwallet/blockatlas/pull/1350/diff?src=pr&el=tree-more) | |

------

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


Patch:
diff --git a/db/models/subscriptions.go b/db/models/subscriptions.go
index 816a2b2..f986ff2 100644
--- a/db/models/subscriptions.go
+++ b/db/models/subscriptions.go
@@ -9,7 +9,7 @@ type (
 	Subscription struct {
 		ID        uint `gorm:"primaryKey;"`
 		CreatedAt time.Time
-		Address   string `gorm:"uniqueIndex:idx_address; type:varchar(128); not null;"`
+		Address   string `gorm:"uniqueIndex:idx_address; type:varchar(256); not null;"`
 	}
 
 	SubscriptionsAssetAssociation struct {
diff --git a/go.mod b/go.mod
index ee963d5..5af9322 100644
--- a/go.mod
+++ b/go.mod
@@ -23,11 +23,11 @@ require (
 	github.com/stretchr/testify v1.6.1
 	github.com/swaggo/gin-swagger v1.3.0
 	github.com/swaggo/swag v1.7.0
-	github.com/trustwallet/golibs v0.0.29
+	github.com/trustwallet/golibs v0.0.30
 	github.com/trustwallet/golibs/network v0.0.0-20210105080443-03598d944b08
 	golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
 	golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
-	golang.org/x/sys v0.0.0-20210104204734-6f8348627aad // indirect
+	golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
 	gopkg.in/yaml.v2 v2.4.0
 	gorm.io/driver/postgres v1.0.6
 	gorm.io/gorm v1.20.9
diff --git a/go.sum b/go.sum
index 908cd6e..90d8417 100644
--- a/go.sum
+++ b/go.sum
@@ -417,6 +417,8 @@ github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6A
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/trustwallet/golibs v0.0.29 h1:ZHpV1co5OEWNxnMk1qhlczYcZiMuLI/4r1KuyrRJRN8=
 github.com/trustwallet/golibs v0.0.29/go.mod h1:otCh9cT8r7VlojZv5zACgUpUyAhNrd5kD5YRV6xW9Iw=
+github.com/trustwallet/golibs v0.0.30 h1:bsuwKSUX9dtn/368H/gnYd2uCF/rXbhw3uIZ9sh3wLY=
+github.com/trustwallet/golibs v0.0.30/go.mod h1:otCh9cT8r7VlojZv5zACgUpUyAhNrd5kD5YRV6xW9Iw=
 github.com/trustwallet/golibs/network v0.0.0-20210105080443-03598d944b08 h1:6MVjdC1zYVwiytclgHLxS6ojdYiM7gfVqMSDkjuulS4=
 github.com/trustwallet/golibs/network v0.0.0-20210105080443-03598d944b08/go.mod h1:RMMyjp4qUcVhWidjE+XYzQgOtSXuhIKSB5pnLCA3WqY=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
@@ -543,8 +545,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210104204734-6f8348627aad h1:MCsdmFSdEd4UEa5TKS5JztCRHK/WtvNei1edOj5RSRo=
-golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
+golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/pkg/blockatlas/marshal.go b/pkg/blockatlas/marshal.go
index 7cfe1a3..aa6d00a 100644
--- a/pkg/blockatlas/marshal.go
+++ b/pkg/blockatlas/marshal.go
@@ -101,7 +101,7 @@ func (a *Amount) UnmarshalJSON(data []byte) error {
 	}
 	str := string(n)
 	if !matchNumber.MatchString(str) {
-		return errors.New("not a regular decimal number")
+		return errors.New("not a regular decimal number: " + str)
 	}
 	if strings.ContainsRune(str, '.') {
 		str, _ = numbers.DecimalToSatoshis(str)
diff --git a/pkg/blockatlas/tx.go b/pkg/blockatlas/tx.go
index 9e6797e..4a97dfe 100644
--- a/pkg/blockatlas/tx.go
+++ b/pkg/blockatlas/tx.go
@@ -60,9 +60,8 @@ type (
 	KeyTitle        string
 
 	Block struct {
-		Number int64  `json:"number"`
-		ID     string `json:"id,omitempty"`
-		Txs    []Tx   `json:"txs"`
+		Number int64 `json:"number"`
+		Txs    []Tx  `json:"txs"`
 	}
 
 	// TxPage is a page of transactions
diff --git a/platform/bitcoin/base.go b/platform/bitcoin/base.go
index 14e85ec..742ed96 100644
--- a/platform/bitcoin/base.go
+++ b/platform/bitcoin/base.go
@@ -2,18 +2,19 @@ package bitcoin
 
 import (
 	"github.com/trustwallet/blockatlas/internal"
+	"github.com/trustwallet/blockatlas/platform/bitcoin/blockbook"
 	"github.com/trustwallet/golibs/coin"
 )
 
 type Platform struct {
-	client    Client
+	client    blockbook.Client
 	CoinIndex uint
 }
 
 func Init(coin uint, api string) *Platform {
 	return &Platform{
 		CoinIndex: coin,
-		client:    Client{internal.InitClient(api)},
+		client:    blockbook.Client{Request: internal.InitClient(api)},
 	}
 }
 
diff --git a/platform/bitcoin/block.go b/platform/bitcoin/block.go
index 9407fa8..12bce3d 100644
--- a/platform/bitcoin/block.go
+++ b/platform/bitcoin/block.go
@@ -1,60 +1,25 @@
 package bitcoin
 
 import (
-	"sync"
-
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 )
 
 func (p *Platform) CurrentBlockNumber() (int64, error) {
-	status, err := p.client.GetBlockNumber()
-	return status.Backend.Blocks, err
-}
-
-func (p *Platform) GetAllBlockPages(total, num int64) []Transaction {
-	txs := make([]Transaction, 0)
-	if total <= 1 {
-		return txs
-	}
-
-	start := int64(1)
-	var wg sync.WaitGroup
-	out := make(chan TransactionsList, int(total-start))
-	wg.Add(int(total - start))
-	for start < total {
-		start++
-		go func(page, num int64, out chan TransactionsList, wg *sync.WaitGroup) {
-			defer wg.Done()
-			block, err := p.client.GetTransactionsByBlock(num, page)
-			if err != nil {
-				return
-			}
-			out <- block
-		}(start, num, out, &wg)
-	}
-	wg.Wait()
-	close(out)
-	for r := range out {
-		txs = append(txs, r.TransactionList()...)
-	}
-	return txs
+	return p.client.GetCurrentBlockNumber()
 }
 
 func (p *Platform) GetBlockByNumber(num int64) (*blockatlas.Block, error) {
-	page := int64(1)
-	block, err := p.client.GetTransactionsByBlock(num, page)
+	block, err := p.client.GetAllTransactionsByBlockNumber(num)
 	if err != nil {
 		return nil, err
 	}
-	txPages := p.GetAllBlockPages(block.TotalPages, num)
-	txs := append(txPages, block.TransactionList()...)
 	var normalized []blockatlas.Tx
-	for _, tx := range txs {
+	for _, tx := range block {
 		normalized = append(normalized, normalizeTransaction(tx, p.CoinIndex))
 	}
 	return &blockatlas.Block{
 		Number: num,
-		ID:     block.Hash,
 		Txs:    normalized,
 	}, nil
+
 }
diff --git a/platform/ethereum/blockbook/block.go b/platform/bitcoin/blockbook/block.go
similarity index 53%
rename from platform/ethereum/blockbook/block.go
rename to platform/bitcoin/blockbook/block.go
index e630b9d..98e4d33 100644
--- a/platform/ethereum/blockbook/block.go
+++ b/platform/bitcoin/blockbook/block.go
@@ -1,25 +1,20 @@
 package blockbook
 
 import (
-	"strconv"
-
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 )
 
 func (c *Client) GetBlockByNumber(num int64, coinIndex uint) (*blockatlas.Block, error) {
-	block, err := c.GetBlock(num)
+	block, err := c.GetAllTransactionsByBlockNumber(num)
 	if err != nil {
 		return nil, err
 	}
-
-	txs := make([]blockatlas.Tx, 0, len(block.Transactions))
-	for _, srcTx := range block.Transactions {
-		tx := normalizeTx(&srcTx, coinIndex)
-		txs = append(txs, tx)
+	txs := make([]blockatlas.Tx, 0)
+	for _, srcTx := range block {
+		txs = append(txs, normalizeTx(&srcTx, coinIndex))
 	}
 	return &blockatlas.Block{
 		Number: num,
-		ID:     strconv.FormatInt(num, 10),
 		Txs:    txs,
 	}, nil
 }
diff --git a/platform/bitcoin/blockbook/client.go b/platform/bitcoin/blockbook/client.go
new file mode 100644
index 0000000..712dbf5
--- /dev/null
+++ b/platform/bitcoin/blockbook/client.go
@@ -0,0 +1,129 @@
+package blockbook
+
+import (
+	"fmt"
+	"net/url"
+	"strconv"
+	"sync"
+
+	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+
+	"github.com/trustwallet/golibs/client"
+)
+
+type Client struct {
+	client.Request
+}
+
+// Block
+
+func (c *Client) GetCurrentBlockNumber() (int64, error) {
+	var nodeInfo NodeInfo
+	err := c.Get(&nodeInfo, "api", nil)
+	if err != nil {
+		return 0, err
+	}
+	return nodeInfo.Blockbook.BestHeight, nil
+}
+
+// Tokens
+
+func (c *Client) GetTokens(address string) ([]Token, error) {
+	var res TransactionsList
+	path := fmt.Sprintf("api/v2/address/%s", address)
+	query := url.Values{"details": {"tokenBalances"}}
+	err := c.Get(&res, path, query)
+	return res.Tokens, err
+}
+
+// Transactions
+func (c *Client) GetAllTransactionsByBlockNumber(num int64) ([]Transaction, error) {
+	page := int64(1)
+	block, err := c.GetTransactionsByBlockNumber(num, page)
+	if err != nil {
+		return nil, err
+	}
+	txPages := c.getAllBlockPages(block.TotalPages, num)
+	txs := append(txPages, block.TransactionList()...)
+	return txs, nil
+}
+
+func (c *Client) GetTxs(address string) (TransactionsList, error) {
+	return c.getTransactionsForContract(address, "", blockatlas.TxPerPage)
+}
+
+func (c *Client) GetTxsWithContract(address, contract string) (TransactionsList, error) {
+	return c.getTransactionsForContract(address, contract, blockatlas.TxPerPage)
+}
+
+func (c *Client) GetTransactionsByBlockNumber(number int64, page int64) (block TransactionsList, err error) {
+	path := fmt.Sprintf("api/v2/block/%s", strconv.FormatInt(number, 10))
+	args := url.Values{
+		"page": {strconv.FormatInt(page, 10)},
+	}
+	err = c.Get(&block, path, args)
+	return block, err
+}
+
+func (c *Client) getTransactionsForContract(address, contract string, limit int) (transactions TransactionsList, err error) {
+	path := fmt.Sprintf("api/v2/address/%s", address)
+	err = c.Get(&transactions, path, url.Values{
+		"page":     {"1"},
+		"details":  {"txs"},
+		"pageSize": {strconv.Itoa(limit)},
+		"contract": {contract},
+	})
+	return transactions, err
+}
+
+func (c *Client) GetTransactionsByXpub(xpub string) (transactions TransactionsList, err error) {
+	path := fmt.Sprintf("api/v2/xpub/%s", xpub)
+	args := url.Values{
+		"pageSize": {strconv.Itoa(blockatlas.TxPerPage)},
+		"details":  {"txs"},
+		"tokens":   {"derived"},
+	}
+	err = c.Get(&transactions, path, args)
+	return transactions, err
+}
+
+func (c *Client) GetAddressesFromXpub(xpub string) (tokens []Token, err error) {
+	path := fmt.Sprintf("api/v2/xpub/%s", xpub)
+	args := url.Values{
+		"pageSize": {strconv.Itoa(blockatlas.TxPerPage)},
+		"details":  {"txs"},
+		"tokens":   {"derived"},
+	}
+	var transactions TransactionsList
+	err = c.Get(&transactions, path, args)
+	return transactions.Tokens, err
+}
+
+func (c *Client) getAllBlockPages(total, num int64) []Transaction {
+	txs := make([]Transaction, 0)
+	if total <= 1 {
+		return txs
+	}
+
+	start := int64(1)
+	var wg sync.WaitGroup
+	out := make(chan TransactionsList, int(total-start))
+	wg.Add(int(total - start))
+	for start < total {
+		start++
+		go func(page, num int64, out chan TransactionsList, wg *sync.WaitGroup) {
+			defer wg.Done()
+			block, err := c.GetTransactionsByBlockNumber(num, page)
+			if err != nil {
+				return
+			}
+			out <- block
+		}(start, num, out, &wg)
+	}
+	wg.Wait()
+	close(out)
+	for r := range out {
+		txs = append(txs, r.TransactionList()...)
+	}
+	return txs
+}
diff --git a/platform/bitcoin/blockbook/model.go b/platform/bitcoin/blockbook/model.go
new file mode 100644
index 0000000..0e27e31
--- /dev/null
+++ b/platform/bitcoin/blockbook/model.go
@@ -0,0 +1,131 @@
+package blockbook
+
+import (
+	"encoding/json"
+	"math/big"
+
+	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+
+	"github.com/trustwallet/golibs/tokentype"
+)
+
+type NodeInfo struct {
+	Blockbook *Blockbook `json:"blockbook"`
+}
+
+type Blockbook struct {
+	BestHeight int64 `json:"bestHeight"`
+}
+
+type TokenTransfer struct {
+	Decimals uint   `json:"decimals"`
+	From     string `json:"from"`
+	Name     string `json:"name"`
+	Symbol   string `json:"symbol"`
+	To       string `json:"to"`
+	Token    string `json:"token"`
+	Type     string `json:"type"`
+	Value    string `json:"value"`
+}
+
+// Token contains info about tokens held by an address
+type Token struct {
+	Balance  string         `json:"balance,omitempty"`
+	Contract string         `json:"contract"`
+	Decimals uint           `json:"decimals"`
+	Name     string         `json:"name"`
+	Symbol   string         `json:"symbol"`
+	Type     tokentype.Type `json:"type"`
+}
+
+// EthereumSpecific contains ethereum specific transaction data
+type EthereumSpecific struct {
+	Status   int      `json:"status"` // -1 pending, 0 Fail, 1 OK
+	Nonce    uint64   `json:"nonce"`
+	GasLimit *big.Int `json:"gasLimit"`
+	GasUsed  *big.Int `json:"gasUsed"`
+	GasPrice string   `json:"gasPrice"`
+	Data     string   `json:"data,omitempty"`
+}
+
+type TransactionsList struct {
+	Page         int64         `json:"page"`
+	TotalPages   int64         `json:"totalPages"`
+	ItemsOnPage  int64         `json:"itemsOnPage"`
+	Transactions []Transaction `json:"transactions,omitempty"`
+	Txs          interface{}   `json:"txs,omitempty"`
+	Tokens       []Token       `json:"tokens,omitempty"`
+	TxCount      int64         `json:"txCount,omitempty"`
+	Hash         string        `json:"hash,omitempty"`
+}
+
+func (tl *TransactionsList) TransactionList() []Transaction {
+	if tl.Transactions != nil {
+		return tl.Transactions
+	}
+	b, err := json.Marshal(tl.Txs)
+	if err != nil {
+		return tl.Transactions
+	}
+	var txs []Transaction
+	err = json.Unmarshal(b, &txs)
+	if err != nil {
+		return tl.Transactions
+	}
+	return txs
+}
+
+type Transaction struct {
+	ID               string            `json:"txid"`
+	Version          uint64            `json:"version"`
+	Vin              []Output          `json:"vin"`
+	Vout             []Output          `json:"vout"`
+	BlockHash        string            `json:"blockHash"`
+	BlockHeight      int64             `json:"blockHeight"`
+	Confirmations    uint64            `json:"confirmations"`
+	BlockTime        int64             `json:"blockTime"`
+	Value            string            `json:"value"`
+	ValueOut         string            `json:"valueOut"`
+	Fees             string            `json:"fees"`
+	TokenTransfers   []TokenTransfer   `json:"tokenTransfers,omitempty"`
+	EthereumSpecific *EthereumSpecific `json:"ethereumSpecific,omitempty"`
+}
+
+func (transaction Transaction) Amount() string {
+	if len(transaction.Value) == 0 {
+		return transaction.ValueOut
+	}
+	return transaction.Value
+}
+
+func (transaction Transaction) GetStatus() blockatlas.Status {
+	if transaction.Confirmations == 0 {
+		return blockatlas.StatusPending
+	}
+	return blockatlas.StatusCompleted
+}
+
+func (transaction Transaction) GetBlockHeight() uint64 {
+	if transaction.BlockHeight > 0 {
+		return uint64(transaction.BlockHeight)
+	}
+	return 0
+}
+
+type Output struct {
+	TxId         string       `json:"txid,omitempty"`
+	Value        string       `json:"value,omitempty"`
+	Addresses    []string     `json:"addresses,omitempty"`
+	ScriptPubKey ScriptPubKey `json:"scriptPubKey,omitempty"`
+}
+
+type ScriptPubKey struct {
+	Addresses []string `json:"addresses,omitempty"`
+}
+
+func (o Output) OutputAddress() []string {
+	if len(o.Addresses) == 0 {
+		return o.ScriptPubKey.Addresses
+	}
+	return o.Addresses
+}
diff --git a/platform/ethereum/blockbook/model_extension.go b/platform/bitcoin/blockbook/model_extension.go
similarity index 58%
rename from platform/ethereum/blockbook/model_extension.go
rename to platform/bitcoin/blockbook/model_extension.go
index 40d96de..787ce71 100644
--- a/platform/ethereum/blockbook/model_extension.go
+++ b/platform/bitcoin/blockbook/model_extension.go
@@ -19,21 +19,21 @@ func (s *EthereumSpecific) GetStatus() (blockatlas.Status, string) {
 	}
 }
 
-func (t *Transaction) FromAddress() string {
-	if len(t.Vin) > 0 && len(t.Vin[0].Addresses) > 0 {
-		return t.Vin[0].Addresses[0]
+func (transaction *Transaction) FromAddress() string {
+	if len(transaction.Vin) > 0 && len(transaction.Vin[0].Addresses) > 0 {
+		return transaction.Vin[0].Addresses[0]
 	}
 	return ""
 }
 
-func (t *Transaction) GetFee() string {
-	status, _ := t.EthereumSpecific.GetStatus()
+func (transaction *Transaction) GetFee() string {
+	status, _ := transaction.EthereumSpecific.GetStatus()
 	if status != blockatlas.StatusPending {
-		return t.Fees
+		return transaction.Fees
 	}
 
-	gasLimit := t.EthereumSpecific.GasLimit
-	gasPrice, ok := new(big.Int).SetString(t.EthereumSpecific.GasPrice, 10)
+	gasLimit := transaction.EthereumSpecific.GasLimit
+	gasPrice, ok := new(big.Int).SetString(transaction.EthereumSpecific.GasPrice, 10)
 	if gasLimit == nil || !ok {
 		return "0"
 	}
@@ -41,9 +41,9 @@ func (t *Transaction) GetFee() string {
 	return fee.String()
 }
 
-func (t *Transaction) ToAddress() string {
-	if len(t.Vout) > 0 && len(t.Vout[0].Addresses) > 0 {
-		return t.Vout[0].Addresses[0]
+func (transaction *Transaction) ToAddress() string {
+	if len(transaction.Vout) > 0 && len(transaction.Vout[0].Addresses) > 0 {
+		return transaction.Vout[0].Addresses[0]
 	}
 	return ""
 }
diff --git a/platform/ethereum/blockbook/token.go b/platform/bitcoin/blockbook/token.go
similarity index 100%
rename from platform/ethereum/blockbook/token.go
rename to platform/bitcoin/blockbook/token.go
diff --git a/platform/ethereum/blockbook/token_test.go b/platform/bitcoin/blockbook/token_test.go
similarity index 100%
rename from platform/ethereum/blockbook/token_test.go
rename to platform/bitcoin/blockbook/token_test.go
diff --git a/platform/ethereum/blockbook/transaction.go b/platform/bitcoin/blockbook/transaction.go
similarity index 97%
rename from platform/ethereum/blockbook/transaction.go
rename to platform/bitcoin/blockbook/transaction.go
index 3a641f1..23e40ff 100644
--- a/platform/ethereum/blockbook/transaction.go
+++ b/platform/bitcoin/blockbook/transaction.go
@@ -24,7 +24,7 @@ func (c *Client) GetTokenTxs(address, token string, coinIndex uint) (blockatlas.
 	return NormalizePage(page, address, token, coinIndex), nil
 }
 
-func NormalizePage(srcPage *Page, address, token string, coinIndex uint) (txs blockatlas.TxPage) {
+func NormalizePage(srcPage TransactionsList, address, token string, coinIndex uint) (txs blockatlas.TxPage) {
 	normalizedAddr, err := Address.EIP55Checksum(address)
 	if err != nil {
 		return
@@ -46,7 +46,7 @@ func NormalizePage(srcPage *Page, address, token string, coinIndex uint) (txs bl
 func normalizeTx(srcTx *Transaction, coinIndex uint) blockatlas.Tx {
 	status, errReason := srcTx.EthereumSpecific.GetStatus()
 	normalized := blockatlas.Tx{
-		ID:       srcTx.TxID,
+		ID:       srcTx.ID,
 		Coin:     coinIndex,
 		From:     srcTx.FromAddress(),
 		To:       srcTx.ToAddress(),
diff --git a/platform/bitcoin/client.go b/platform/bitcoin/client.go
deleted file mode 100644
index d6b05c5..0000000
--- a/platform/bitcoin/client.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package bitcoin
-
-import (
-	"fmt"
-	"net/url"
-	"strconv"
-
-	"github.com/trustwallet/blockatlas/pkg/blockatlas"
-	"github.com/trustwallet/golibs/client"
-)
-
-type Client struct {
-	client.Request
-}
-
-func (c *Client) GetTransactions(address string) (transactions TransactionsList, err error) {
-	path := fmt.Sprintf("api/v2/address/%s", address)
-	err = c.Get(&transactions, path, url.Values{
-		"details":  {"txs"},
-		"pageSize": {strconv.Itoa(blockatlas.TxPerPage)},
-	})
-	return transactions, err
-}
-
-func (c *Client) GetTransactionsByXpub(xpub string) (transactions TransactionsList, err error) {
-	path := fmt.Sprintf("api/v2/xpub/%s", xpub)
-	args := url.Values{
-		"pageSize": {strconv.Itoa(blockatlas.TxPerPage)},
-		"details":  {"txs"},
-		"tokens":   {"derived"},
-	}
-	err = c.Get(&transactions, path, args)
-	return transactions, err
-}
-
-func (c *Client) GetAddressesFromXpub(xpub string) (tokens []Token, err error) {
-	path := fmt.Sprintf("api/v2/xpub/%s", xpub)
-	args := url.Values{
-		"pageSize": {strconv.Itoa(blockatlas.TxPerPage)},
-		"details":  {"txs"},
-		"tokens":   {"derived"},
-	}
-	var transactions TransactionsList
-	err = c.Get(&transactions, path, args)
-	return transactions.Tokens, err
-}
-
-func (c *Client) GetTransactionsByBlock(number int64, page int64) (block TransactionsList, err error) {
-	path := fmt.Sprintf("api/v2/block/%s", strconv.FormatInt(number, 10))
-	args := url.Values{
-		"page": {strconv.FormatInt(page, 10)},
-	}
-	err = c.Get(&block, path, args)
-	return block, err
-}
-
-func (c *Client) GetBlockNumber() (status BlockchainStatus, err error) {
-	err = c.Get(&status, "api", nil)
-	return status, err
-}
diff --git a/platform/bitcoin/model.go b/platform/bitcoin/model.go
deleted file mode 100644
index 545a6b8..0000000
--- a/platform/bitcoin/model.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package bitcoin
-
-import "encoding/json"
-
-type TransactionsList struct {
-	Page         int64         `json:"page"`
-	TotalPages   int64         `json:"totalPages"`
-	ItemsOnPage  int64         `json:"itemsOnPage"`
-	Transactions []Transaction `json:"transactions,omitempty"`
-	Txs          interface{}   `json:"txs,omitempty"`
-	Tokens       []Token       `json:"tokens,omitempty"`
-	TxCount      int64         `json:"txCount,omitempty"`
-	Hash         string        `json:"hash,omitempty"`
-}
-
-func (tl *TransactionsList) TransactionList() []Transaction {
-	if tl.Transactions != nil {
-		return tl.Transactions
-	}
-	b, err := json.Marshal(tl.Txs)
-	if err != nil {
-		return tl.Transactions
-	}
-	var txs []Transaction
-	err = json.Unmarshal(b, &txs)
-	if err != nil {
-		return tl.Transactions
-	}
-	return txs
-}
-
-type Tx struct {
-	ID string `json:"id"`
-}
-
-type Transaction struct {
-	ID            string   `json:"txid"`
-	Version       uint64   `json:"version"`
-	Vin           []Output `json:"vin"`
-	Vout          []Output `json:"vout"`
-	BlockHash     string   `json:"blockHash"`
-	BlockHeight   int64    `json:"blockHeight"`
-	Confirmations uint64   `json:"confirmations"`
-	BlockTime     uint64   `json:"blockTime"`
-	Value         string   `json:"value"`
-	ValueOut      string   `json:"valueOut"`
-	Fees          string   `json:"fees"`
-}
-
-func (transaction Transaction) Amount() string {
-	if len(transaction.Value) == 0 {
-		return transaction.ValueOut
-	}
-	return transaction.Value
-}
-
-type Output struct {
-	TxId         string       `json:"txid,omitempty"`
-	Value        string       `json:"value,omitempty"`
-	Addresses    []string     `json:"addresses,omitempty"`
-	ScriptPubKey ScriptPubKey `json:"scriptPubKey,omitempty"`
-}
-
-type ScriptPubKey struct {
-	Addresses []string `json:"addresses,omitempty"`
-}
-
-func (o Output) OutputAddress() []string {
-	if len(o.Addresses) == 0 {
-		return o.ScriptPubKey.Addresses
-	}
-	return o.Addresses
-}
-
-type Token struct {
-	Type      string `json:"type"`
-	Name      string `json:"name"`
-	Path      string `json:"path"`
-	Transfers int    `json:"transfers"`
-	Balance   string `json:"balance"`
-}
-
-type BlockchainStatus struct {
-	Backend Backend `json:"backend"`
-}
-
-type Backend struct {
-	Chain  string `json:"chain"`
-	Blocks int64  `json:"blocks"`
-}
-
-func (transaction Transaction) GetBlockHeight() uint64 {
-	if transaction.BlockHeight > 0 {
-		return uint64(transaction.BlockHeight)
-	}
-	return 0
-}
diff --git a/platform/bitcoin/transaction.go b/platform/bitcoin/transaction.go
index b64b78b..bde4c44 100644
--- a/platform/bitcoin/transaction.go
+++ b/platform/bitcoin/transaction.go
@@ -3,6 +3,8 @@ package bitcoin
 import (
 	"sort"
 
+	"github.com/trustwallet/blockatlas/platform/bitcoin/blockbook"
+
 	mapset "github.com/deckarep/golang-set"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 	"github.com/trustwallet/golibs/coin"
@@ -46,7 +48,7 @@ func (p *Platform) getTxsByXpub(xpub string) ([]blockatlas.Tx, error) {
 }
 
 func (p *Platform) getTxsByAddress(address string) ([]blockatlas.Tx, error) {
-	sourceTxs, err := p.client.GetTransactions(address)
+	sourceTxs, err := p.client.GetTxs(address)
 	if err != nil {
 		return []blockatlas.Tx{}, err
 	}
@@ -56,7 +58,7 @@ func (p *Platform) getTxsByAddress(address string) ([]blockatlas.Tx, error) {
 	return txs, nil
 }
 
-func normalizeTxs(sourceTxs TransactionsList, coinIndex uint, addressSet mapset.Set) []blockatlas.Tx {
+func normalizeTxs(sourceTxs blockbook.TransactionsList, coinIndex uint, addressSet mapset.Set) []blockatlas.Tx {
 	var txs []blockatlas.Tx
 	for _, transaction := range sourceTxs.TransactionList() {
 		if tx, ok := normalizeTransfer(transaction, coinIndex, addressSet); ok {
@@ -66,7 +68,7 @@ func normalizeTxs(sourceTxs TransactionsList, coinIndex uint, addressSet mapset.
 	return txs
 }
 
-func normalizeTransfer(transaction Transaction, coinIndex uint, addressSet mapset.Set) (tx blockatlas.Tx, ok bool) {
+func normalizeTransfer(transaction blockbook.Transaction, coinIndex uint, addressSet mapset.Set) (tx blockatlas.Tx, ok bool) {
 	tx = normalizeTransaction(transaction, coinIndex)
 	direction := blockatlas.InferDirection(&tx, addressSet)
 	value := blockatlas.InferValue(&tx, direction, addressSet)
@@ -81,7 +83,7 @@ func normalizeTransfer(transaction Transaction, coinIndex uint, addressSet mapse
 	return tx, true
 }
 
-func normalizeTransaction(tx Transaction, coinIndex uint) blockatlas.Tx {
+func normalizeTransaction(tx blockbook.Transaction, coinIndex uint) blockatlas.Tx {
 	inputs := parseOutputs(tx.Vin)
 	outputs := parseOutputs(tx.Vout)
 	from := ""
@@ -104,10 +106,10 @@ func normalizeTransaction(tx Transaction, coinIndex uint) blockatlas.Tx {
 		Inputs:   inputs,
 		Outputs:  outputs,
 		Fee:      fees,
-		Date:     int64(tx.BlockTime),
+		Date:     tx.BlockTime,
 		Type:     blockatlas.TxTransfer,
 		Block:    tx.GetBlockHeight(),
-		Status:   tx.getStatus(),
+		Status:   tx.GetStatus(),
 		Sequence: 0,
 		Meta: blockatlas.Transfer{
 			Value:    amount,
@@ -117,7 +119,7 @@ func normalizeTransaction(tx Transaction, coinIndex uint) blockatlas.Tx {
 	}
 }
 
-func parseOutputs(outputs []Output) (addresses []blockatlas.TxOutput) {
+func parseOutputs(outputs []blockbook.Output) (addresses []blockatlas.TxOutput) {
 	set := make(map[string]*blockatlas.TxOutput)
 	var ordered []string
 	for _, output := range outputs {
@@ -140,10 +142,3 @@ func parseOutputs(outputs []Output) (addresses []blockatlas.TxOutput) {
 	}
 	return addresses
 }
-
-func (transaction *Transaction) getStatus() blockatlas.Status {
-	if transaction.Confirmations == 0 {
-		return blockatlas.StatusPending
-	}
-	return blockatlas.StatusCompleted
-}
diff --git a/platform/elrond/client.go b/platform/elrond/client.go
index d935fed..3d1911b 100644
--- a/platform/elrond/client.go
+++ b/platform/elrond/client.go
@@ -40,7 +40,6 @@ func (c *Client) GetBlockByNumber(height int64) (*blockatlas.Block, error) {
 
 	return &blockatlas.Block{
 		Number: int64(block.Nonce),
-		ID:     block.Hash,
 		Txs:    txs,
 	}, nil
 }
diff --git a/platform/ethereum/base.go b/platform/ethereum/base.go
index cebc5a3..7829984 100644
--- a/platform/ethereum/base.go
+++ b/platform/ethereum/base.go
@@ -2,7 +2,7 @@ package ethereum
 
 import (
 	"github.com/trustwallet/blockatlas/internal"
-	"github.com/trustwallet/blockatlas/platform/ethereum/blockbook"
+	"github.com/trustwallet/blockatlas/platform/bitcoin/blockbook"
 	"github.com/trustwallet/blockatlas/platform/ethereum/collection"
 	"github.com/trustwallet/blockatlas/platform/ethereum/trustray"
 	"github.com/trustwallet/golibs/coin"
diff --git a/platform/ethereum/blockbook/client.go b/platform/ethereum/blockbook/client.go
deleted file mode 100644
index c654652..0000000
--- a/platform/ethereum/blockbook/client.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package blockbook
-
-import (
-	"fmt"
-	"net/url"
-
-	"github.com/trustwallet/golibs/client"
-)
-
-type Client struct {
-	client.Request
-}
-
-func (c *Client) GetTxs(address string) (*Page, error) {
-	return c.getTransactions(address, "")
-}
-
-func (c *Client) GetTxsWithContract(address, contract string) (*Page, error) {
-	return c.getTransactions(address, contract)
-}
-
-func (c *Client) GetTokens(address string) ([]Token, error) {
-	return c.getTokens(address)
-}
-
-func (c *Client) GetCurrentBlockNumber() (int64, error) {
-	var nodeInfo NodeInfo
-	err := c.Get(&nodeInfo, "api", nil)
-	if err != nil {
-		return 0, err
-	}
-	return nodeInfo.Blockbook.BestHeight, nil
-}
-
-func (c *Client) GetBlock(num int64) (block Block, err error) {
-	path := fmt.Sprintf("api/v2/block/%d", num)
-	err = c.Get(&block, path, nil)
-	return
-}
-
-func (c *Client) getTransactions(address, contract string) (page *Page, err error) {
-	path := fmt.Sprintf("api/v2/address/%s", address)
-	query := url.Values{"page": {"1"}, "pageSize": {"25"}, "details": {"txs"}, "contract": {contract}}
-	err = c.Get(&page, path, query)
-	return
-}
-
-func (c *Client) getTokens(address string) ([]Token, error) {
-	var res Page
-	path := fmt.Sprintf("api/v2/address/%s", address)
-	query := url.Values{"details": {"tokenBalances"}}
-	err := c.Get(&res, path, query)
-
-	return res.Tokens, err
-}
diff --git a/platform/ethereum/blockbook/model.go b/platform/ethereum/blockbook/model.go
deleted file mode 100644
index f7645ee..0000000
--- a/platform/ethereum/blockbook/model.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package blockbook
-
-import (
-	"github.com/trustwallet/golibs/tokentype"
-	"math/big"
-)
-
-type Page struct {
-	Transactions []Transaction `json:"transactions,omitempty"`
-	Tokens       []Token       `json:"tokens,omitempty"`
-}
-
-type NodeInfo struct {
-	Blockbook *Blockbook `json:"blockbook"`
-	Backend   *Backend   `json:"backend"`
-}
-
-type Blockbook struct {
-	BestHeight int64 `json:"bestHeight"`
-}
-
-type Backend struct {
-	Blocks int64 `json:"blocks"`
-}
-
-type Block struct {
-	Transactions []Transaction `json:"txs"`
-}
-
-type Transaction struct {
-	TxID             string            `json:"txid"`
-	Vin              []Output          `json:"vin"`
-	Vout             []Output          `json:"vout"`
-	BlockHeight      int64             `json:"blockHeight"`
-	BlockTime        int64             `json:"blockTime"`
-	Value            string            `json:"value"`
-	Fees             string            `json:"fees"`
-	TokenTransfers   []TokenTransfer   `json:"tokenTransfers,omitempty"`
-	EthereumSpecific *EthereumSpecific `json:"ethereumSpecific,omitempty"`
-}
-
-type Output struct {
-	Value     string   `json:"value,omitempty"`
-	Addresses []string `json:"addresses"`
-}
-
-type TokenTransfer struct {
-	Decimals uint   `json:"decimals"`
-	From     string `json:"from"`
-	Name     string `json:"name"`
-	Symbol   string `json:"symbol"`
-	To       string `json:"to"`
-	Token    string `json:"token"`
-	Type     string `json:"type"`
-	Value    string `json:"value"`
-}
-
-// Token contains info about tokens held by an address
-type Token struct {
-	Balance  string         `json:"balance,omitempty"`
-	Contract string         `json:"contract"`
-	Decimals uint           `json:"decimals"`
-	Name     string         `json:"name"`
-	Symbol   string         `json:"symbol"`
-	Type     tokentype.Type `json:"type"`
-}
-
-// EthereumSpecific contains ethereum specific transaction data
-type EthereumSpecific struct {
-	Status   int      `json:"status"` // -1 pending, 0 Fail, 1 OK
-	Nonce    uint64   `json:"nonce"`
-	GasLimit *big.Int `json:"gasLimit"`
-	GasUsed  *big.Int `json:"gasUsed"`
-	GasPrice string   `json:"gasPrice"`
-	Data     string   `json:"data,omitempty"`
-}
diff --git a/platform/ethereum/trustray/block.go b/platform/ethereum/trustray/block.go
index 208432e..f0d614a 100644
--- a/platform/ethereum/trustray/block.go
+++ b/platform/ethereum/trustray/block.go
@@ -1,8 +1,6 @@
 package trustray
 
 import (
-	"strconv"
-
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
 )
 
@@ -17,7 +15,6 @@ func (c *Client) GetBlockByNumber(num int64, coinIndex uint) (*blockatlas.Block,
 	}
 	return &blockatlas.Block{
 		Number: num,
-		ID:     strconv.FormatInt(num, 10),
 		Txs:    txs,
 	}, nil
 }
diff --git a/platform/harmony/block.go b/platform/harmony/block.go
index c6e90cb..b3af79b 100644
--- a/platform/harmony/block.go
+++ b/platform/harmony/block.go
@@ -23,7 +23,6 @@ func (p *Platform) NormalizeBlock(block *BlockInfo) blockatlas.Block {
 		return blockatlas.Block{}
 	}
 	return blockatlas.Block{
-		ID:     block.Hash,
 		Number: int64(blockNumber),
 		Txs:    NormalizeTxs(block.Transactions),
 	}
diff --git a/platform/nimiq/block.go b/platform/nimiq/block.go
index e245847..819005b 100644
--- a/platform/nimiq/block.go
+++ b/platform/nimiq/block.go
@@ -21,7 +21,6 @@ func (p *Platform) GetBlockByNumber(num int64) (*blockatlas.Block, error) {
 func NormalizeBlock(srcBlock *Block) blockatlas.Block {
 	return blockatlas.Block{
 		Number: srcBlock.Number,
-		ID:     srcBlock.Hash,
 		Txs:    NormalizeTxs(srcBlock.Txs),
 	}
 }
diff --git a/platform/stellar/block.go b/platform/stellar/block.go
index 0de588d..2dcdc94 100644
--- a/platform/stellar/block.go
+++ b/platform/stellar/block.go
@@ -18,7 +18,6 @@ func (p *Platform) GetBlockByNumber(num int64) (*blockatlas.Block, error) {
 }
 func (p *Platform) NormalizeBlock(block *Block) blockatlas.Block {
 	return blockatlas.Block{
-		ID:     block.Ledger.Id,
 		Number: block.Ledger.Sequence,
 		Txs:    p.NormalizePayments(block.Payments),
 	}
diff --git a/platform/vechain/block.go b/platform/vechain/block.go
index bae6e1c..6566aa5 100644
--- a/platform/vechain/block.go
+++ b/platform/vechain/block.go
@@ -20,7 +20,6 @@ func (p *Platform) GetBlockByNumber(num int64) (*blockatlas.Block, error) {
 	}
 	return &blockatlas.Block{
 		Number: num,
-		ID:     block.Id,
 		Txs:    txs,
 	}, nil
 }
diff --git a/services/parser/parser.go b/services/parser/parser.go
index c3894b7..9d6d6aa 100644
--- a/services/parser/parser.go
+++ b/services/parser/parser.go
@@ -82,7 +82,14 @@ func parse(params Params) {
 
 	err = SaveLastParsedBlock(params, blocks)
 	if err != nil {
-		log.WithFields(log.Fields{"operation": "run SaveLastParsedBlock", "coin": params.Api.Coin().Handle}).Error(err)
+		log.WithFields(log.Fields{
+			"operation":       "run SaveLastParsedBlock",
+			"coin":            params.Api.Coin().Handle,
+			"blocks":          blocks,
+			"lastParsedBlock": lastParsedBlock,
+			"currentBlock":    currentBlock,
+			"tags":            raven.Tags{{Key: "coin", Value: params.Api.Coin().Handle}},
+		}).Error(err)
 		time.Sleep(params.ParsingBlocksInterval)
 		return
 	}
@@ -189,7 +196,16 @@ func FetchBlocks(params Params, lastParsedBlock, currentBlock int64) ([]blockatl
 		for err := range errorsChan {
 			errorsList = append(errorsList, err)
 		}
-		log.WithFields(log.Fields{"coin": params.Api.Coin().Handle, "count": len(errorsList), "blocks": errorsList}).Error("Fetch blocks errors")
+		log.WithFields(log.Fields{
+			"coin":   params.Api.Coin().Handle,
+			"count":  len(errorsList),
+			"blocks": errorsList,
+			"tags": raven.Tags{
+				{Key: "coin", Value: params.Api.Coin().Handle},
+			},
+		}).Error("Fetch Blocks Errors")
+
+		return []blockatlas.Block{}, fmt.Errorf("unable to fetch blocks: %d: %d", lastParsedBlock, currentBlock)
 	}
 
 	blocks := make([]blockatlas.Block, 0, len(blocksChan))
diff --git a/services/subscriber/subscriber.go b/services/subscriber/subscriber.go
index 98d91ff..84e8844 100644
--- a/services/subscriber/subscriber.go
+++ b/services/subscriber/subscriber.go
@@ -32,7 +32,7 @@ func RunSubscriber(database *db.Instance, delivery amqp.Delivery) error {
 	case AddSubscription:
 		err := database.CreateSubscriptions(subscriptions)
 		if err != nil {
-			log.WithFields(log.Fields{"service": Notifications, "operation": event.Operation, "subscriptions": len(subscriptions)}).Error(err)
+			log.WithFields(log.Fields{"service": Notifications, "operation": event.Operation, "subscriptions": subscriptions}).Error(err)
 			return err
 		}
 		log.WithFields(log.Fields{"service": Notifications, "operation": event.Operation, "subscriptions": len(subscriptions)}).Info("Add subscriptions")


Test patch:
diff --git a/platform/ethereum/blockbook/transaction_test.go b/platform/bitcoin/blockbook/transaction_test.go
similarity index 98%
rename from platform/ethereum/blockbook/transaction_test.go
rename to platform/bitcoin/blockbook/transaction_test.go
index 8593589..dc5b324 100644
--- a/platform/ethereum/blockbook/transaction_test.go
+++ b/platform/bitcoin/blockbook/transaction_test.go
@@ -365,14 +365,14 @@ func TestNormalizePage(t *testing.T) {
 		},
 	}
 	for _, tt := range tests {
-		var page Page
+		var page TransactionsList
 		var txPage blockatlas.TxPage
 		err := json.Unmarshal([]byte(tt.args.srcPage), &page)
 		assert.Nil(t, err)
 		err = json.Unmarshal([]byte(tt.want), &txPage)
 		assert.Nil(t, err)
 		t.Run(tt.name, func(t *testing.T) {
-			got := NormalizePage(&page, tt.args.address, tt.args.token, tt.args.coinIndex)
+			got := NormalizePage(page, tt.args.address, tt.args.token, tt.args.coinIndex)
 			gotJson, err := json.Marshal(got)
 			assert.Nil(t, err)
 			gotTxPage, err := json.Marshal(txPage)
diff --git a/platform/bitcoin/transaction_test.go b/platform/bitcoin/transaction_test.go
index 11a6818..e801b07 100644
--- a/platform/bitcoin/transaction_test.go
+++ b/platform/bitcoin/transaction_test.go
@@ -6,6 +6,8 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/trustwallet/blockatlas/platform/bitcoin/blockbook"
+
 	mapset "github.com/deckarep/golang-set"
 	"github.com/stretchr/testify/assert"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
@@ -239,7 +241,7 @@ func TestNormalizeTransfer(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		var transaction Transaction
+		var transaction blockbook.Transaction
 
 		rErr := json.Unmarshal([]byte(test.RawTx), &transaction)
 		if rErr != nil {
@@ -273,15 +275,15 @@ func TestNormalizeTransfer(t *testing.T) {
 
 func TestTransactionStatus(t *testing.T) {
 	tests := []struct {
-		Tx       Transaction
+		Tx       blockbook.Transaction
 		Expected blockatlas.Status
 	}{
-		{Transaction{Confirmations: 0}, blockatlas.StatusPending},
-		{Transaction{Confirmations: 1}, blockatlas.StatusCompleted},
+		{blockbook.Transaction{Confirmations: 0}, blockatlas.StatusPending},
+		{blockbook.Transaction{Confirmations: 1}, blockatlas.StatusCompleted},
 	}
 
 	for _, test := range tests {
-		assert.Equal(t, test.Expected, test.Tx.getStatus())
+		assert.Equal(t, test.Expected, test.Tx.GetStatus())
 	}
 }
 
@@ -367,7 +369,7 @@ func TestParseOutputs(t *testing.T) {
 		},
 	}
 	for _, tt := range tests {
-		var outputs []Output
+		var outputs []blockbook.Output
 		_ = json.Unmarshal([]byte(tt.outputs), &outputs)
 		want := parseOutputs(outputs)
 		t.Run(tt.name, func(t *testing.T) {