Repository: trustwallet/blockatlas
Pull request #832 (2020-02-11 21:57:52)

Fix Nimiq transaction requests
A misinterpretation of the Nimiq RPC API can cause BlockAtlas to return unordered transaction lists or less than 25 transactions even when more are available.
This PR fixes the `getTransactionByAddress` call and ensures transaction count & ordering.

 - [x] tests pass
 - [x] project runs
 - [x] response valid

vikmeup: @terorie can you finish changes and let's get this merged?

Patch:
diff --git a/platform/ethereum/collection.go b/platform/ethereum/collection.go
index ffd8cf5..251df61 100644
--- a/platform/ethereum/collection.go
+++ b/platform/ethereum/collection.go
@@ -167,6 +167,7 @@ func OldNormalizeCollectiblePage(c *Collection, srcPage []Collectible, coinIndex
 }
 
 func NormalizeCollection(c Collection, coinIndex uint, owner string) blockatlas.Collection {
+	normalizeSupportedContracts(&c)
 	if len(c.Contracts) == 0 {
 		return blockatlas.Collection{}
 	}
@@ -193,7 +194,19 @@ func NormalizeCollection(c Collection, coinIndex uint, owner string) blockatlas.
 	}
 }
 
+func normalizeSupportedContracts(c *Collection) {
+	supportedContracts := make([]PrimaryAssetContract, 0)
+	for _, contract := range c.Contracts {
+		if _, ok := supportedTypes[contract.Type]; !ok {
+			continue
+		}
+		supportedContracts = append(supportedContracts, contract)
+	}
+	c.Contracts = supportedContracts
+}
+
 func NormalizeCollectiblePage(c *Collection, srcPage []Collectible, coinIndex uint) (page blockatlas.CollectiblePage) {
+	normalizeSupportedContracts(c)
 	if len(c.Contracts) == 0 {
 		return
 	}
diff --git a/platform/nimiq/transaction.go b/platform/nimiq/transaction.go
index 3bfba60..e16a144 100644
--- a/platform/nimiq/transaction.go
+++ b/platform/nimiq/transaction.go
@@ -3,6 +3,7 @@ package nimiq
 import (
 	"github.com/trustwallet/blockatlas/coin"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"sort"
 	"time"
 )
 
@@ -39,6 +40,12 @@ func NormalizeTx(srcTx *Tx) blockatlas.Tx {
 
 // NormalizeTxs converts multiple Nimiq transactions
 func NormalizeTxs(srcTxs []Tx) []blockatlas.Tx {
+	sort.SliceStable(srcTxs, func(i, j int) bool {
+		return srcTxs[i].BlockNumber > srcTxs[j].BlockNumber
+	})
+	if len(srcTxs) > blockatlas.TxPerPage {
+		srcTxs = srcTxs[:blockatlas.TxPerPage]
+	}
 	txs := make([]blockatlas.Tx, len(srcTxs))
 	for i, srcTx := range srcTxs {
 		txs[i] = NormalizeTx(&srcTx)


Test patch:
diff --git a/platform/ethereum/collection_test.go b/platform/ethereum/collection_test.go
index a3b6be1..1a96c8a 100644
--- a/platform/ethereum/collection_test.go
+++ b/platform/ethereum/collection_test.go
@@ -246,3 +246,166 @@ func TestSearchCollection(t *testing.T) {
 	}
 
 }
+
+func TestNormalizeSupportedContracts(t *testing.T) {
+	var contracts []PrimaryAssetContract
+	err := json.Unmarshal([]byte(rawAssetContracts), &contracts)
+	assert.Nil(t, err)
+	var collection = Collection{}
+	collection.Contracts = contracts
+	normalizeSupportedContracts(&collection)
+	assert.Equal(t, len(collection.Contracts), 2, "normalizeSupportedContracts with incorrect len")
+	var expectedContracts []PrimaryAssetContract
+	err = json.Unmarshal([]byte(rawAssetContractsExpected), &expectedContracts)
+	assert.Nil(t, err)
+	assert.Equal(t, collection.Contracts, expectedContracts, "normalizeSupportedContracts expectedContracts")
+}
+
+const rawAssetContracts = `[
+    {
+        "address": "0xee85966b4974d3c6f71a2779cc3b6f53afbc2b68",
+        "asset_contract_type": "fungible",
+        "created_date": "2019-10-16T07:36:16.102163",
+        "name": "Rare Chest",
+        "nft_version": null,
+        "opensea_version": null,
+        "owner": 1610615,
+        "schema_name": "ERC20",
+        "symbol": "",
+        "total_supply": "1",
+        "description": "Gods Unchained is a free-to-play, turn-based competitive trading card game in which cards can be bought and sold on the OpenSea marketplace. Players use their collection to build decks of cards, and select a God to play with at the start of each match. The goal of the game is to reduce your opponent's life to zero. Each deck contains exactly 30 cards. On OpenSea, cards can be sold for a fixed price, auctioned, or sold in bundles.",
+        "external_link": "https://godsunchained.com/?refcode=0x5b3256965e7C3cF26E11FCAf296DfC8807C01073",
+        "image_url": "https://lh3.googleusercontent.com/yArciVdcDv3O2R-O8XCxx3YEYZdzpiCMdossjUgv0kpLIluUQ1bYN_dyEk5xcvBEOgeq0zNIoWOh7TL9DvUEv--OLQ=s60",
+        "default_to_fiat": false,
+        "dev_buyer_fee_basis_points": 0,
+        "dev_seller_fee_basis_points": 0,
+        "only_proxied_transfers": false,
+        "opensea_buyer_fee_basis_points": 0,
+        "opensea_seller_fee_basis_points": 250,
+        "buyer_fee_basis_points": 0,
+        "seller_fee_basis_points": 250,
+        "payout_address": null
+    },
+    {
+        "address": "0x20d4cec36528e1c4563c1bfbe3de06aba70b22b4",
+        "asset_contract_type": "fungible",
+        "created_date": "2019-10-16T08:06:42.727997",
+        "name": "Legendary Chest",
+        "nft_version": null,
+        "opensea_version": null,
+        "owner": 1610615,
+        "schema_name": "ERC20",
+        "symbol": "",
+        "total_supply": "1",
+        "description": "Gods Unchained is a free-to-play, turn-based competitive trading card game in which cards can be bought and sold on the OpenSea marketplace. Players use their collection to build decks of cards, and select a God to play with at the start of each match. The goal of the game is to reduce your opponent's life to zero. Each deck contains exactly 30 cards. On OpenSea, cards can be sold for a fixed price, auctioned, or sold in bundles.",
+        "external_link": "https://godsunchained.com/?refcode=0x5b3256965e7C3cF26E11FCAf296DfC8807C01073",
+        "image_url": "https://lh3.googleusercontent.com/yArciVdcDv3O2R-O8XCxx3YEYZdzpiCMdossjUgv0kpLIluUQ1bYN_dyEk5xcvBEOgeq0zNIoWOh7TL9DvUEv--OLQ=s60",
+        "default_to_fiat": false,
+        "dev_buyer_fee_basis_points": 0,
+        "dev_seller_fee_basis_points": 0,
+        "only_proxied_transfers": false,
+        "opensea_buyer_fee_basis_points": 0,
+        "opensea_seller_fee_basis_points": 250,
+        "buyer_fee_basis_points": 0,
+        "seller_fee_basis_points": 250,
+        "payout_address": null
+    },
+    {
+        "address": "0x0e3a2a1f2146d86a604adc220b4967a898d7fe07",
+        "asset_contract_type": "non-fungible",
+        "created_date": "2019-11-01T06:39:04.363034",
+        "name": "Gods Unchained Cards",
+        "nft_version": "3.0",
+        "opensea_version": null,
+        "owner": 1691695,
+        "schema_name": "ERC721",
+        "symbol": "",
+        "total_supply": "1",
+        "description": "Gods Unchained is a free-to-play, turn-based competitive trading card game in which cards can be bought and sold on the OpenSea marketplace. Players use their collection to build decks of cards, and select a God to play with at the start of each match. The goal of the game is to reduce your opponent's life to zero. Each deck contains exactly 30 cards. On OpenSea, cards can be sold for a fixed price, auctioned, or sold in bundles.",
+        "external_link": "https://godsunchained.com/?refcode=0x5b3256965e7C3cF26E11FCAf296DfC8807C01073",
+        "image_url": "https://lh3.googleusercontent.com/yArciVdcDv3O2R-O8XCxx3YEYZdzpiCMdossjUgv0kpLIluUQ1bYN_dyEk5xcvBEOgeq0zNIoWOh7TL9DvUEv--OLQ=s60",
+        "default_to_fiat": false,
+        "dev_buyer_fee_basis_points": 0,
+        "dev_seller_fee_basis_points": 0,
+        "only_proxied_transfers": false,
+        "opensea_buyer_fee_basis_points": 0,
+        "opensea_seller_fee_basis_points": 250,
+        "buyer_fee_basis_points": 0,
+        "seller_fee_basis_points": 250,
+        "payout_address": null
+    },
+    {
+        "address": "0x564cb55c655f727b61d9baf258b547ca04e9e548",
+        "asset_contract_type": "non-fungible",
+        "created_date": "2019-10-29T12:28:37.643714",
+        "name": "Gods Unchained",
+        "nft_version": "3.0",
+        "opensea_version": null,
+        "owner": 1691695,
+        "schema_name": "ERC721",
+        "symbol": "",
+        "total_supply": "205",
+        "description": "Gods Unchained is a free-to-play, turn-based competitive trading card game in which cards can be bought and sold on the OpenSea marketplace. Players use their collection to build decks of cards, and select a God to play with at the start of each match. The goal of the game is to reduce your opponent's life to zero. Each deck contains exactly 30 cards. On OpenSea, cards can be sold for a fixed price, auctioned, or sold in bundles.",
+        "external_link": "https://godsunchained.com/?refcode=0x5b3256965e7C3cF26E11FCAf296DfC8807C01073",
+        "image_url": "https://lh3.googleusercontent.com/yArciVdcDv3O2R-O8XCxx3YEYZdzpiCMdossjUgv0kpLIluUQ1bYN_dyEk5xcvBEOgeq0zNIoWOh7TL9DvUEv--OLQ=s60",
+        "default_to_fiat": false,
+        "dev_buyer_fee_basis_points": 0,
+        "dev_seller_fee_basis_points": 0,
+        "only_proxied_transfers": false,
+        "opensea_buyer_fee_basis_points": 0,
+        "opensea_seller_fee_basis_points": 250,
+        "buyer_fee_basis_points": 0,
+        "seller_fee_basis_points": 250,
+        "payout_address": null
+    }
+]`
+
+const rawAssetContractsExpected = `[{
+        "address": "0x0e3a2a1f2146d86a604adc220b4967a898d7fe07",
+        "asset_contract_type": "non-fungible",
+        "created_date": "2019-11-01T06:39:04.363034",
+        "name": "Gods Unchained Cards",
+        "nft_version": "3.0",
+        "opensea_version": null,
+        "owner": 1691695,
+        "schema_name": "ERC721",
+        "symbol": "",
+        "total_supply": "1",
+        "description": "Gods Unchained is a free-to-play, turn-based competitive trading card game in which cards can be bought and sold on the OpenSea marketplace. Players use their collection to build decks of cards, and select a God to play with at the start of each match. The goal of the game is to reduce your opponent's life to zero. Each deck contains exactly 30 cards. On OpenSea, cards can be sold for a fixed price, auctioned, or sold in bundles.",
+        "external_link": "https://godsunchained.com/?refcode=0x5b3256965e7C3cF26E11FCAf296DfC8807C01073",
+        "image_url": "https://lh3.googleusercontent.com/yArciVdcDv3O2R-O8XCxx3YEYZdzpiCMdossjUgv0kpLIluUQ1bYN_dyEk5xcvBEOgeq0zNIoWOh7TL9DvUEv--OLQ=s60",
+        "default_to_fiat": false,
+        "dev_buyer_fee_basis_points": 0,
+        "dev_seller_fee_basis_points": 0,
+        "only_proxied_transfers": false,
+        "opensea_buyer_fee_basis_points": 0,
+        "opensea_seller_fee_basis_points": 250,
+        "buyer_fee_basis_points": 0,
+        "seller_fee_basis_points": 250,
+        "payout_address": null
+    },
+    {
+        "address": "0x564cb55c655f727b61d9baf258b547ca04e9e548",
+        "asset_contract_type": "non-fungible",
+        "created_date": "2019-10-29T12:28:37.643714",
+        "name": "Gods Unchained",
+        "nft_version": "3.0",
+        "opensea_version": null,
+        "owner": 1691695,
+        "schema_name": "ERC721",
+        "symbol": "",
+        "total_supply": "205",
+        "description": "Gods Unchained is a free-to-play, turn-based competitive trading card game in which cards can be bought and sold on the OpenSea marketplace. Players use their collection to build decks of cards, and select a God to play with at the start of each match. The goal of the game is to reduce your opponent's life to zero. Each deck contains exactly 30 cards. On OpenSea, cards can be sold for a fixed price, auctioned, or sold in bundles.",
+        "external_link": "https://godsunchained.com/?refcode=0x5b3256965e7C3cF26E11FCAf296DfC8807C01073",
+        "image_url": "https://lh3.googleusercontent.com/yArciVdcDv3O2R-O8XCxx3YEYZdzpiCMdossjUgv0kpLIluUQ1bYN_dyEk5xcvBEOgeq0zNIoWOh7TL9DvUEv--OLQ=s60",
+        "default_to_fiat": false,
+        "dev_buyer_fee_basis_points": 0,
+        "dev_seller_fee_basis_points": 0,
+        "only_proxied_transfers": false,
+        "opensea_buyer_fee_basis_points": 0,
+        "opensea_seller_fee_basis_points": 250,
+        "buyer_fee_basis_points": 0,
+        "seller_fee_basis_points": 250,
+        "payout_address": null
+    }
+]`
diff --git a/platform/nimiq/tests/getTransactionsByAddress_50.json b/platform/nimiq/tests/getTransactionsByAddress_50.json
new file mode 100644
index 0000000..973f15b
--- /dev/null
+++ b/platform/nimiq/tests/getTransactionsByAddress_50.json
@@ -0,0 +1,62 @@
+[
+  {
+    "hash": "61454a43916e075f0e0a830888db7a37e9ffc42503fa3cd2d9eb85fe8067e435",
+    "blockHash": "ec2c6712a977d62e95e0d43eca02bf4161c86197e576073429c0cdc6b9cc1ce1",
+    "blockNumber": 507486,
+    "timestamp": 1554283830,
+    "confirmations": 450839,
+    "from": "99643a627e867dae6cf10ca73b4aff0345d3b72e",
+    "fromAddress": "NQ76 K5J3 LQKX GRXS UT7H 1JKK NJPY 0D2V 7DRE",
+    "to": "0d609a98033b7b85ab5178c6050ef9ef82d9006e",
+    "toAddress": "NQ64 1MG9 M603 7DVQ BASH F330 A3PR VX1D J03E",
+    "value": 199400,
+    "fee": 300,
+    "data": null,
+    "flags": 0
+  },
+  {
+    "hash": "bc38ac8f4fa67222e38e6120073e327a1cc7c38b8419502f6fd5dc3699853296",
+    "blockHash": "58c6afb786829419c90bdcdaf0ab01630b36105affc1718e3d2e772d91d73621",
+    "blockNumber": 952271,
+    "timestamp": 1581094339,
+    "confirmations": 6054,
+    "from": "fdcc85a8e604f23c7d2cd3c13a5d18dc566a6e7c",
+    "fromAddress": "NQ02 YP68 BA76 0KR3 QY9C SF0K LP8Q THB6 LTKU",
+    "to": "99643a627e867dae6cf10ca73b4aff0345d3b72e",
+    "toAddress": "NQ76 K5J3 LQKX GRXS UT7H 1JKK NJPY 0D2V 7DRE",
+    "value": 100000,
+    "fee": 138,
+    "data": null,
+    "flags": 0
+  },
+  {
+    "hash": "54de2a0c420f9c145320c9a020decb106b0b8989fe4be147144af79dc13e8a1c",
+    "blockHash": "25b73d90dd38ff61f6c8d713671ba576f735fa3f544d0ea9a5f74f00aea082ff",
+    "blockNumber": 933566,
+    "timestamp": 1579965313,
+    "confirmations": 24759,
+    "from": "fdcc85a8e604f23c7d2cd3c13a5d18dc566a6e7c",
+    "fromAddress": "NQ02 YP68 BA76 0KR3 QY9C SF0K LP8Q THB6 LTKU",
+    "to": "99643a627e867dae6cf10ca73b4aff0345d3b72e",
+    "toAddress": "NQ76 K5J3 LQKX GRXS UT7H 1JKK NJPY 0D2V 7DRE",
+    "value": 100000,
+    "fee": 138,
+    "data": null,
+    "flags": 0
+  },
+  {
+    "hash": "874494dc2640876ad1417822e230304050ef37924b06ee84a8d8584935f2707b",
+    "blockHash": "c583c5ee1b375d46d07a1289de036b7e10c426184d8d85de7e6d6eec66681167",
+    "blockNumber": 591542,
+    "timestamp": 1559352602,
+    "confirmations": 366783,
+    "from": "fdcc85a8e604f23c7d2cd3c13a5d18dc566a6e7c",
+    "fromAddress": "NQ02 YP68 BA76 0KR3 QY9C SF0K LP8Q THB6 LTKU",
+    "to": "99643a627e867dae6cf10ca73b4aff0345d3b72e",
+    "toAddress": "NQ76 K5J3 LQKX GRXS UT7H 1JKK NJPY 0D2V 7DRE",
+    "value": 100000,
+    "fee": 138,
+    "data": null,
+    "flags": 0
+  }
+]
diff --git a/platform/nimiq/transaction_test.go b/platform/nimiq/transaction_test.go
index edbc3a4..8c0a5f0 100644
--- a/platform/nimiq/transaction_test.go
+++ b/platform/nimiq/transaction_test.go
@@ -5,6 +5,10 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/trustwallet/blockatlas/coin"
 	"github.com/trustwallet/blockatlas/pkg/blockatlas"
+	"io/ioutil"
+	"path/filepath"
+	"runtime"
+	"sort"
 	"testing"
 	"time"
 )
@@ -96,3 +100,26 @@ func TestNormalizeTx1(t *testing.T) {
 		})
 	}
 }
+
+func TestNormalizeTxs_Ordering(t *testing.T) {
+	_, goFile, _, _ := runtime.Caller(0)
+	testFilePath := filepath.Join(filepath.Dir(goFile), "tests", "getTransactionsByAddress_50.json")
+	testFile, err := ioutil.ReadFile(testFilePath)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var srcTxs []Tx
+	if err := json.Unmarshal(testFile, &srcTxs); err != nil {
+		t.Fatal(err)
+	}
+	txs := NormalizeTxs(srcTxs)
+	if len(txs) != 4 {
+		t.Fatalf("Unexpected count: %d", len(txs))
+	}
+	sorted := sort.SliceIsSorted(txs, func(i, j int) bool {
+		return txs[i].Block > txs[j].Block
+	})
+	if !sorted {
+		t.Fatal("Transactions not sorted")
+	}
+}