├── .codecov.yml ├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── config.yml ├── stale.yml └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.runner ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── api ├── api.go ├── endpoint │ ├── basic.go │ ├── block.go │ ├── collection.go │ ├── errors.go │ ├── staking.go │ ├── token.go │ └── transaction.go └── registry.go ├── app.json ├── cmd ├── api │ └── main.go ├── consumer │ └── main.go ├── parser │ └── main.go └── setup │ └── main.go ├── config.yml ├── config └── configuration.go ├── configmock.yml ├── db ├── asset.go ├── db.go ├── models │ ├── asset.go │ ├── asset_test.go │ ├── subscriptions.go │ └── tracker.go ├── subscription.go └── tracker.go ├── deployment ├── Procfile-consumer ├── Procfile-parser └── Procfile-web ├── docker-compose.yml ├── docs ├── docs.go ├── features.csv ├── swagger.json └── swagger.yaml ├── go.mod ├── go.sum ├── internal ├── init.go ├── metrics │ └── metrics.go └── mq.go ├── mock ├── README.md ├── datafiles.yaml ├── ext-api-data │ ├── aeternity-api_middleware_transactions_account_ak_2WGWYMgWy1opZxgA8AVzGCTavCQyUBtbKx5SrCX6E4kmDZMtJb.json │ ├── aion-api_getTransactionsByAddress__accountAddress_0xa04f0117864ccf5013861a89f08c6fc790284d72356c8a362025d31b855ed6ed_size_25.json │ ├── algorand-api_v1_account_4EZFQABCVQTHQCK3HQBIYGC4NV2VM42FZHEFTVH77ROG4ZGREC6Y7V5T2U_transactions.json │ ├── algorand-api_v1_block_5478346.json │ ├── binance-api_v1_account_bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q │ ├── binance-api_v1_tokens__limit_1000_offset_0.json │ ├── binance-api_v1_txs__address_bnb1z35wusfv8twfele77vddclka9z84ugywug48gn_txAsset_BNB.json │ ├── bitcoin-api_v2_address_bc1qrfr44n2j4czd5c9txwlnw0yj2h82x9566fglqj__details_txs.json │ ├── bitcoin-api_v2_xpub_zpub6ruK9k6YGm8BRHWvTiQcrEPnFkuRDJhR7mPYzV2LDvjpLa5CuGgrhCYVZjMGcLcFqv9b2WvsFtY2Gb3xq8NVq8qhk9veozrA2W9QaWtihrC__details_txs.json │ ├── bitcoincash-api_v2_address_bitcoincash_qq07l6rr5lsdm3m80qxw80ku2ex0tj76vvsxpvmgme__details_txs.json │ ├── bitcoincash-api_v2_xpub_xpub6Bq3UUphocwroXkhA9sn8ACnZpJNuwaBehgo7WbDi2DULYnvT72Uzgsv9cE5EiP8ThDYdMyZREfbpkUY4KZ88ZaUQxXciBcZ1soSi1d8xtX__details_txs.json │ ├── callisto-api_tokens__address_0xc3d5b69f65027ddf48f894e6e90121293a2f6615.json │ ├── callisto-api_transactions__address_0x3083a7ec44ca2b038d4be4b0798152f948f0f3d7.json │ ├── cosmos-api_auth_accounts_cosmos1dx27g0kzhwej0ekcf2k9hsktcxnmpl7fcehcvq.json │ ├── cosmos-api_minting_inflation.json │ ├── cosmos-api_staking_delegators_cosmos1dx27g0kzhwej0ekcf2k9hsktcxnmpl7fcehcvq_delegations.json │ ├── cosmos-api_staking_pool.json │ ├── cosmos-api_staking_validators__status_bonded.json │ ├── cosmos-api_txs__limit_25_message.sender_cosmos1dx27g0kzhwej0ekcf2k9hsktcxnmpl7fcehcvq_page_1.json │ ├── cosmos-api_txs__limit_25_page_1_transfer.recipient_cosmos1dx27g0kzhwej0ekcf2k9hsktcxnmpl7fcehcvq.json │ ├── dash-api_v2_address_XrcbsQdrFYEzbqA9nCJi8zDtnRZzNKkCtG__details_txs.json │ ├── dash-api_v2_xpub_xpub6CKAjCUKKPW7bzYEG5mzRsmzyTRp7XzauqFWNmpGVNqMqsSQpLMCN3ygEmD6ZEGVocNDrDhE7SeGot78noEWpwPDbJjfxREHC848sxNrUkD__details_txs.json │ ├── decred-api_v2_address_DsTxPUVFxXeNgu5fzozr4mTR4tqqMaKcvpY__details_txs.json │ ├── decred-api_v2_xpub_dpubZFf1tYMxcku9nvDRxnYdE4yrEESrkuQFRq5RwA4KoYQKpDSRszN2emePTwLgfQpd4mZHGrHbQkKPZdjH1BcopomXRnr5Gt43rjpNEfeuJLN__details_txs.json │ ├── digibyte-api_v2_address_DEs1RJKuASSjfphFJdxX9eidrjWewMZgAi__details_txs.json │ ├── digibyte-api_v2_xpub_zpub6ricE56nzsDeTAVo4w68vQQ3tRvR6C18JjKVsgbiRFjEawGV9SuS2gfkpm5qFxjbTNPPuvAA3cqRsxNHxFwVnpYD2Lawjtb3wowbFdwmjow__details_txs.json │ ├── doge-api_v2_address_D5dAUAx3Ezg1q4dRgzKTBsxp4VJietWkDh__details_txs.json │ ├── doge-api_v2_xpub_dgub8rceyfsEvGDexmvJcBqiKBrmuxWGgYJxHjtbouHTwTfQrCQcMjxyNf6vUPY4dUp23QtReFy6WGedutBk9XUaYNupUqVAZcweqGhfsudUELN__details_txs.json │ ├── eth-api_tokens__address_0x0875BCab22dE3d02402bc38aEe4104e1239374a7.json │ ├── eth-api_transactions__address_0x0875BCab22dE3d02402bc38aEe4104e1239374a7.json │ ├── eth-blockbook-api_v2_address_0x0875BCab22dE3d02402bc38aEe4104e1239374a7__details_tokenBalances.json │ ├── eth-blockbook-api_v2_address_0x0875BCab22dE3d02402bc38aEe4104e1239374a7__details_txs.json │ ├── eth-rpc__eth_call_data_0x0178b8bf2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047.json │ ├── eth-rpc__eth_call_data_0x0178b8bf2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047.request_json │ ├── eth-rpc__eth_call_data_0x0178b8bfa538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4.json │ ├── eth-rpc__eth_call_data_0x0178b8bfa538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4.request_json │ ├── eth-rpc__eth_call_data_0x0178b8bfee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835.json │ ├── eth-rpc__eth_call_data_0x0178b8bfee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835.request_json │ ├── eth-rpc__eth_call_data_0x3b3b57de2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047.json │ ├── eth-rpc__eth_call_data_0x3b3b57de2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047.request_json │ ├── eth-rpc__eth_call_data_0x3b3b57dea538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4.json │ ├── eth-rpc__eth_call_data_0x3b3b57dea538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4.request_json │ ├── eth-rpc__eth_call_data_0xf1cb7e062337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047000000000000000000000000000000000000000000000000000000000000003c.json │ ├── eth-rpc__eth_call_data_0xf1cb7e062337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047000000000000000000000000000000000000000000000000000000000000003c.request_json │ ├── eth-rpc__eth_call_data_0xf1cb7e06a538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4000000000000000000000000000000000000000000000000000000000000003c.json │ ├── eth-rpc__eth_call_data_0xf1cb7e06a538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4000000000000000000000000000000000000000000000000000000000000003c.request_json │ ├── eth-rpc__eth_call_data_0xf1cb7e06ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835000000000000000000000000000000000000000000000000000000000000003c.json │ ├── eth-rpc__eth_call_data_0xf1cb7e06ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835000000000000000000000000000000000000000000000000000000000000003c.request_json │ ├── ethclassic-api_tokens__address_0xa12105efa0663147bddee178f6a741ac15676b79.json │ ├── ethclassic-api_transactions__address_0x7d2d0e153026fb428b885d86de50768d4cfeac37.json │ ├── fio-api_v1_chain_get_pub_address.0001.json │ ├── fio-api_v1_chain_get_pub_address.0001.request_json │ ├── fio-api_v1_chain_get_pub_address.0002.json │ ├── fio-api_v1_chain_get_pub_address.0002.request_json │ ├── fio-api_v1_chain_get_pub_address.json │ ├── fio-api_v1_chain_get_pub_address.request_json │ ├── fio-api_v1_history_get_actions.0001.json │ ├── fio-api_v1_history_get_actions.0001.request_json │ ├── fio-api_v1_history_get_actions.0002.json │ ├── fio-api_v1_history_get_actions.0002.request_json │ ├── fio-api_v1_history_get_actions.json │ ├── fio-api_v1_history_get_actions.request_json │ ├── gochain-api_tokens__address_0x0Fd98FB42C439E5F6484f7E71Caa6661d81d0628.json │ ├── gochain-api_transactions__address_0xEd7F2e81B0264177e0df8f275f97Fd74Fa51A896.json │ ├── groestlcoin-api_v2_address_33Ym3fecmWaHD19jymYt6fGd9TqSDQFfQj__details_txs.json │ ├── groestlcoin-api_v2_xpub_zpub6rWUMiiVPxjWVHffT8x3AfcbyDu8SZJAiuKUTBmhxT7Bvqk1WitxndDStG1qHN6XzRM7JgsaRaVccRFW3AprWk4Fpaev1N6QSp1aNnP5JPf__details_txs.json │ ├── harmony-api.json │ ├── harmony-api.request_json │ ├── icon-api_address_txList__address_hxee691e7bccc4eb11fee922896e9f51490e62b12e_count_25.json │ ├── iotex-api_accounts_io1mwekae7qqwlr23220k5n9z3fmjxz72tuchra3m.json │ ├── iotex-api_accounts_io1vg808avg2ydye8djl2axmkc9j0xhzu6vdaw6g5.json │ ├── iotex-api_actions_addr_io1vg808avg2ydye8djl2axmkc9j0xhzu6vdaw6g5__count_25_start_32.json │ ├── iotex-api_staking_delegations_io1mwekae7qqwlr23220k5n9z3fmjxz72tuchra3m.json │ ├── iotex-api_staking_validators__status_bonded.json │ ├── kava-api_auth_accounts_kava1l8va9zyl50cpzv447c694k3jndelc9ygtfll2m.json │ ├── kava-api_minting_inflation.json │ ├── kava-api_staking_delegators_kava1l8va9zyl50cpzv447c694k3jndelc9ygtfll2m_delegations.json │ ├── kava-api_staking_pool.json │ ├── kava-api_staking_validators__status_bonded.json │ ├── kava-api_txs__limit_25_message.sender_kava1l8va9zyl50cpzv447c694k3jndelc9ygtfll2m_page_1.json │ ├── kava-api_txs__limit_25_page_1_transfer.recipient_kava1l8va9zyl50cpzv447c694k3jndelc9ygtfll2m.json │ ├── kin-api_accounts_GBHKUZ7C2SZ5N3X2S7O6TT6LNUWSEA2BXMSR5GTTSR6VZARSVAXIQNGH_payments__limit_25_order_desc.json │ ├── kin-api_transactions_b2131beb8e0c57dfd3309914fce08ac4e094c51dc918087171150dd5b996076a__.json │ ├── kin-api_transactions_eb070218bfde8c48af6071432bc9f19b7690b9dc50535cc135f67ea046e70bed.json │ ├── kusama-api_scan_transfers.json │ ├── kusama-api_scan_transfers.request_json │ ├── litecoin-api_v2_address_ltc1qpm594ntjq6ayqjngf6t9td2dxtey9d7985eept__details_txs.json │ ├── litecoin-api_v2_xpub_zpub6rpF5Uxuz4KKWePLorSz2QrHMmk1iiZvGUGgtSHpor8yiGekyRuWf5ZNmf6GUKB4v3ibQDuZp5v8RnjEGq58kR3WPtGPn8Lrg677MQ8YeKu__details_txs.json │ ├── nano-api.json │ ├── nano-api.request_json │ ├── nebulas-api_tx__a_n1RCYwrpLMpSpUCQ8QUDzGRg6B2PnY8R94a_p_0.json │ ├── nimiq-rpc.json │ ├── nimiq-rpc.request_json │ ├── ontology-api_v2_addresses_AUyL4TZ1zFEcSKDJrjFnD7vsq5iFZMZqT7_transactions__page_number_1_page_size_20.json │ ├── opensea-api_api_v1_assets___collection_unstoppable-domains_limit_300_owner_0x84E79D544B4b13bC3560069cfD56A9D5bbE7521d.json │ ├── opensea-api_api_v1_collections___asset_owner_0x84E79D544B4b13bC3560069cfD56A9D5bbE7521d_limit_1000.json │ ├── poa-api_tokens__address_0x0875BCab22dE3d02402bc38aEe4104e1239374a7.json │ ├── poa-api_transactions__address_0x55798eCbF17ce1241d543c22dCE46134c13b4bc0.json │ ├── qtum-api_v2_address_QZJbNrGT3cZ1J1AEHtgH3JWM7uLBNAejLZ__details_txs.json │ ├── qtum-api_v2_xpub_xpub6CvFuU1yPwHjMekXqgEZjcQy22ZWiKgRUY6yAneNNyk1trZhV6ZBFSY8Vt2wygTXTVHBkfi4n823vm79yiw42w6xTL2UjKyh2W9V88sXoNd__details_txs.json │ ├── ravencoin-api_v2_address_RGkwvrUors8DtmhKy5bddFwRCTZaunjpvo__details_txs.json │ ├── ravencoin-api_v2_xpub_xpub6BrkWQHMnuGcvKowEn2hpvnZ41SiCsu38mgFThKU3nMzPUN9r9C26puf18rfVdHH3nDwSkeMgsjVniNDKUk5arxekekGpNyVLsWihYAfC5B__details_txs.json │ ├── ripple-api_accounts_rMQ98K56yXJbDGv49ZSmW51sLn94Xe1mu1_transactions__descending_false_limit_25_type_Payment.json │ ├── stellar-api_accounts_GDKIJJIKXLOM2NRMPNQZUUYK24ZPVFC6426GZAEP3KUK6KEJLACCWNMX_payments__limit_25_order_desc.json │ ├── stellar-api_transactions_23fa4d3c787f22a1755afa4275761ffba516ec4f2e039440ec02a5522b388f2c.json │ ├── stellar-api_transactions_2912d519b2c2174b0147a9e02208f3ed14820228913142f8c6a5cd360783c029__.json │ ├── tezos-api_account_tz1foWxaV3VQyWqFbWTERS6YDJjPT6C7jPp8_op__limit_25_order_desc_type_transaction_delegation.json │ ├── theta-api_accounttx_0xac0eeb6ee3e32e2c74e14ac74155063e4f4f981f__isEqualType_true_limitNumber_100_pageNumber_1_type_2.json │ ├── thundertoken-api_tokens__address_0x0b230def08139f18a86536d9cfa150f04435414c.json │ ├── thundertoken-api_transactions__address_0x0b230def08139f18a86536d9cfa150f04435414c.json │ ├── tomochain-api_tokens__address_0x8b353021189375591723e7384262f45709a3c3dc.json │ ├── tomochain-api_transactions__address_0x17e4c16605e32adead5fa371bf6117df34ca0200.json │ ├── tron-api_v1_accounts_TFFriedwRtWdFuzerDDtkoQTZ29smDZ1MB.json │ ├── tron-api_v1_accounts_TFFriedwRtWdFuzerDDtkoQTZ29smDZ1MB_transactions__limit_25_order_by_block_timestamp_desc_token_id_.json │ ├── tron-api_v1_assets_1002000.json │ ├── tron-api_v1_assets_1002798.json │ ├── tron-api_v1_assets_1002814.json │ ├── tron-api_wallet_getaccount.json │ ├── tron-api_wallet_getaccount.request_json │ ├── tron-api_wallet_listwitnesses.json │ ├── unstoppabledomains_api_v1__dpantani.crypto.json │ ├── unstoppabledomains_api_v1__dpantani.zil.json │ ├── vechain-api_blocks_best.json │ ├── vechain-api_logs_transfer.json │ ├── vechain-api_logs_transfer.request_json │ ├── vechain-api_transactions_0x004aa0448e458105b098aea2a764a1d54ab95451bee488869f417b351857c3c5.json │ ├── vechain-api_transactions_0x702edd54bd4e13e0012798cc8b2dfa52f7150173945103d203fae26b8e3d2ed7.json │ ├── viacoin-api_v2_address_VdMPvn7vUTSzbYjiMDs1jku9wAh1Ri2Y1A__details_txs.json │ ├── viacoin-api_v2_xpub_zpub6qVn6ubhK9tfepuABqy8wBXXn3qUZTbpqyNBqLyqakqTrZZD9rXZ3L5MZ945g8Mu7vmMSbC7vfLtLatTgxAnVJ8ECCtwmKqCo6TJm2ZsFJK__details_txs.json │ ├── waves-api_transactions_address_3PJ4q4sqriJs2y7Z45wmbLrbmV9MDecbPxD_limit_25.json │ ├── zcash-api_v2_address_t1LwLWo1Mo3s4RPtUpeyUD1eYd47inL3bwX__details_txs.json │ ├── zcash-api_v2_xpub_xpub6CCXGBJ13akWuKSn4iU7CqQeXzLyDC2Y1Z83Mmg2xz11PX2EeZJJKRECz29iN4eHewRh8yfb7FpnCcjYbkqn6ynHnXW3jczPcJcenThfFeS__details_txs.json │ ├── zcoin-api_v2_address_a8EF4cpenEgEn9hm2NL5KfFK1UmSZZaQVn__details_txs.json │ ├── zcoin-api_v2_xpub_xpub6Cgu6WtTyo99pRtTabwscog2ncj4BUbTWzk7bt7habdLYwgnXLEWH3TuR1789QSTPVsPjLMa2KQzHffyZHTkLQQyRxeEBmWHaETS2btF5fK__details_txs.json │ ├── zelcash-api_v2_address_t1JKRwXGfKTGfPV1z48rvoLyabk31z3xwHa__details_txs.json │ ├── zelcash-api_v2_xpub_xpub6C5soBeFd2uZLCcEvsqaoGXuh9UposMMfk2jSiBKMN8rJKs9NLqjPK51gWv9mYBpUY95GtHYsofwpPRdB6FJ56cEaTJGCba5GKv55wPNZNf__details_txs.json │ └── zilliqa-api_addresses_zil1l8ddxvejeam70qang54wnqkgtmlu5mwlgzy64z_txs.json └── mockserver │ ├── mock-healthcheck.json │ ├── mockserver.go │ └── mockserver_test.go ├── pkg └── blockatlas │ ├── collectible.go │ ├── errors.go │ ├── models.go │ ├── platform.go │ ├── platform_test.go │ ├── staking.go │ ├── staking_test.go │ ├── string.go │ └── string_test.go ├── platform ├── aeternity │ ├── base.go │ ├── client.go │ ├── mocks │ │ └── transfer.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── aion │ ├── base.go │ ├── client.go │ ├── mocks │ │ └── transfer.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── algorand │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ └── transfer.json │ ├── model.go │ ├── stake.go │ ├── transaction.go │ └── transaction_test.go ├── binance │ ├── base.go │ ├── base_test.go │ ├── block.go │ ├── block_test.go │ ├── client.go │ ├── mocks │ │ ├── account_meta_response.json │ │ ├── block_multi_no_orders.json │ │ ├── block_multi_response.json │ │ ├── block_no_orders.json │ │ ├── block_response.json │ │ ├── node_info.json │ │ ├── tokens.json │ │ ├── tokens_response.json │ │ ├── txs.json │ │ ├── txs_ava_response.json │ │ └── txs_response.json │ ├── model.go │ ├── model_test.go │ ├── stake.go │ ├── staking │ │ ├── client.go │ │ └── model.go │ ├── token.go │ ├── token_test.go │ ├── transaction.go │ └── transaction_test.go ├── bitcoin │ ├── base.go │ ├── block.go │ ├── blockbook │ │ ├── block.go │ │ ├── client.go │ │ ├── mocks │ │ │ ├── blockbook_txs.json │ │ │ └── expected_txs.json │ │ ├── model.go │ │ ├── model_extension.go │ │ ├── token.go │ │ ├── token_test.go │ │ ├── transaction.go │ │ └── transaction_test.go │ ├── mocks │ │ ├── incoming_tx.json │ │ ├── outgoing_tx.json │ │ └── pending_tx.json │ ├── transaction.go │ └── transaction_test.go ├── cosmos │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── claim_1.json │ │ ├── claim_2.json │ │ ├── delegate_tx.json │ │ ├── delegation.json │ │ ├── transfer.json │ │ ├── transfer_failed.json │ │ ├── transfer_kava.json │ │ ├── unbonding.json │ │ ├── undelegate_tx.json │ │ └── validator.json │ ├── model.go │ ├── stake.go │ ├── stake_test.go │ ├── transaction.go │ └── transaction_test.go ├── elrond │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── scr_negative_value.json │ │ ├── tx.json │ │ ├── tx_2.json │ │ ├── tx_3.json │ │ ├── tx_4.json │ │ ├── tx_5.json │ │ └── tx_6.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── ethereum │ ├── base.go │ ├── block.go │ ├── bounce │ │ ├── client.go │ │ ├── client_test.go │ │ ├── collection.go │ │ ├── collection_test.go │ │ ├── mocks │ │ │ ├── artwork_nft.json │ │ │ └── pancake_bunny.json │ │ └── model.go │ ├── client.go │ ├── collection.go │ ├── opensea │ │ ├── client.go │ │ ├── collection.go │ │ ├── collection_test.go │ │ ├── mocks │ │ │ ├── opensea_collectible.json │ │ │ └── opensea_collections.json │ │ └── model.go │ ├── transaction.go │ └── transaction_test.go ├── filecoin │ ├── base.go │ ├── block.go │ ├── block_test.go │ ├── explorer │ │ ├── client.go │ │ └── models.go │ ├── mocks │ │ ├── ChainGetBlockMessages.json │ │ ├── ChainGetTipSetByHeight.json │ │ ├── ChainHead.json │ │ └── response.json │ ├── rpc │ │ ├── client.go │ │ └── models.go │ ├── transaction.go │ └── transaction_test.go ├── fio │ ├── actor.go │ ├── actor_test.go │ ├── base.go │ ├── client.go │ ├── model.go │ └── transaction.go ├── harmony │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── delegation.json │ │ ├── transfer.json │ │ └── validator.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── icon │ ├── base.go │ ├── client.go │ ├── mocks │ │ └── transfer.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── iotex │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ └── transfer.json │ ├── model.go │ ├── stake.go │ ├── transaction.go │ └── transaction_test.go ├── kava │ ├── base.go │ ├── block.go │ ├── client.go │ ├── model.go │ ├── stake.go │ ├── stake_test.go │ ├── transaction.go │ └── transaction_test.go ├── nano │ ├── base.go │ ├── client.go │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── near │ ├── base.go │ ├── client.go │ └── transaction.go ├── nebulas │ ├── base.go │ ├── client.go │ ├── mocks │ │ └── transfer.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── nimiq │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── getTransactionsByAddress_50.json │ │ ├── pending_tx.json │ │ └── tx.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── oasis │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── tx_with_error.json │ │ ├── tx_with_fee.json │ │ └── tx_without_fee.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── ontology │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── block_response.json │ │ ├── transfer_fee.json │ │ ├── transfer_ong.json │ │ ├── transfer_ont.json │ │ └── transfer_rewards.json │ ├── model.go │ ├── model_test.go │ ├── stake.go │ ├── transaction.go │ └── transaction_test.go ├── platform.go ├── polkadot │ ├── base.go │ ├── block.go │ ├── client.go │ ├── crypto.go │ ├── crypto_test.go │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── registry.go ├── ripple │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── payment.json │ │ ├── payment_2.json │ │ ├── payment_3.json │ │ ├── payment_4.json │ │ └── payment_failed.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── solana │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── GetTxsByAddress.json │ │ ├── getConfirmedSignaturesForAddress2.json │ │ └── getConfirmedTransaction.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── stellar │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── create_tx.json │ │ └── transfer_tx.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── tezos │ ├── baker.go │ ├── baker_test.go │ ├── base.go │ ├── block.go │ ├── block_test.go │ ├── client.go │ ├── mocks │ │ ├── account.json │ │ ├── delegation_response.json │ │ ├── rpc_block_1292516.json │ │ └── validator.json │ ├── model.go │ ├── model_test.go │ ├── rpc.go │ ├── rpc_mock.go │ ├── rpc_model.go │ ├── stake.go │ ├── stake_test.go │ ├── transaction.go │ └── transaction_test.go ├── theta │ ├── base.go │ ├── client.go │ ├── mocks │ │ ├── tfuel_transfer.json │ │ └── theta_transfer.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go ├── tron │ ├── address.go │ ├── address_test.go │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── delegation.json │ │ ├── delegation_2.json │ │ ├── token_txs_response.json │ │ ├── tokens │ │ │ ├── accounts_response.json │ │ │ ├── accounts_txs_response.json │ │ │ ├── asset_1000542_response.json │ │ │ ├── asset_1000567_response.json │ │ │ ├── asset_tr7nh_response.json │ │ │ ├── tokens_response.json │ │ │ ├── trc20_response.json │ │ │ ├── txs_empty_response.json │ │ │ └── txs_trc20_response.json │ │ ├── transfer.json │ │ └── txs_response.json │ ├── model.go │ ├── stake.go │ ├── stake_test.go │ ├── token.go │ ├── token_test.go │ ├── transaction.go │ └── transaction_test.go ├── vechain │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── incoming_vtho_receipt.json │ │ ├── incoming_vtho_tx.json │ │ ├── outgoing_vtho_receipt.json │ │ ├── outgoing_vtho_tx.json │ │ ├── reverted_receipt.json │ │ ├── reverted_tx.json │ │ ├── transfer.json │ │ └── tx_id.json │ ├── model.go │ ├── stake.go │ ├── transaction.go │ └── transaction_test.go ├── waves │ ├── base.go │ ├── block.go │ ├── client.go │ ├── mocks │ │ ├── different_txs.json │ │ └── transfer.json │ ├── model.go │ ├── transaction.go │ └── transaction_test.go └── zilliqa │ ├── base.go │ ├── block.go │ ├── crypto.go │ ├── crypto_test.go │ ├── mocks │ └── transfer.json │ ├── model.go │ ├── model_test.go │ ├── rpc │ ├── client.go │ ├── model.go │ └── model_test.go │ ├── transaction.go │ ├── transaction_test.go │ └── viewblock │ ├── client.go │ ├── model.go │ └── model_test.go ├── services ├── assets │ ├── client.go │ ├── model.go │ ├── model_test.go │ ├── validator.go │ └── validator_test.go ├── notifier │ ├── base.go │ ├── base_test.go │ ├── delivery.go │ ├── models.go │ └── models_test.go ├── parser │ ├── parser.go │ └── parser_test.go ├── subscriber │ └── subscriber.go └── tokenindexer │ ├── api.go │ ├── indexer.go │ ├── indexer_subscribe.go │ └── models.go └── tests ├── integration ├── db_test │ ├── db_run_test.go │ ├── tokenindexer_test.go │ └── tracker_test.go └── setup │ ├── mq.go │ ├── postgres.go │ └── setup.go └── postman └── blockatlas.postman_collection.json /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 70..100 3 | round: down 4 | precision: 0 5 | 6 | status: 7 | project: 8 | default: 9 | # basic 10 | target: 30% 11 | 12 | patch: no 13 | 14 | ignore: 15 | - "mock" 16 | - "config" 17 | - "tests" 18 | - "services" 19 | - "scripts" 20 | - "docs" 21 | - "db/models" 22 | - "Makefile" 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.go] 9 | indent_style = tab 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | mock/ext-api-dyson/* linguist-vendored 2 | *.json linguist-vendored 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hewigovens -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug 4 | title: '' 5 | labels: 'Bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Summary of Bug 11 | 12 | 13 | 14 | ## Expected Behavior 15 | 16 | 17 | 18 | ## Steps to Reproduce 19 | 20 | 21 | 22 | ____ 23 | 24 | ## More Info (for devs / optional) 25 | ##### Request details: 26 | ``` 27 | {} 28 | ``` 29 | 30 | ##### Response details: 31 | ``` 32 | {} 33 | ``` 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest new feature 4 | title: '' 5 | labels: 'New Feature' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Summary 11 | 12 | 13 | 14 | ## Problem Definition / Why? 15 | 16 | 19 | 20 | ## Where/how shows? 21 | 22 | 23 | 24 | ## Proposal 25 | 26 | 27 | 28 | ____ 29 | 30 | ## More Info (for devs / optional) 31 | ##### Request details: 32 | `v1/market/example` 33 | ``` 34 | {} 35 | ``` 36 | 37 | ##### Response details: 38 | ``` 39 | {} 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | todo: 2 | label: "Bot: Todo" 3 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 5 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | name: Unit 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.x 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: ^1.15.6 18 | id: go 19 | 20 | - name: Check out code 21 | uses: actions/checkout@v2 22 | 23 | - name: Get dependencies 24 | run: | 25 | go get -v -t -d ./... 26 | 27 | - name: Unit Test 28 | run: make test 29 | 30 | - name: Lint 31 | run: make lint 32 | 33 | - name: Upload coverage 34 | run: bash <(curl -s https://codecov.io/bash) -f coverage.txt 35 | 36 | integration: 37 | name: Integration 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Set up Go 1.x 41 | uses: actions/setup-go@v2 42 | with: 43 | go-version: ^1.13 44 | id: go 45 | 46 | - name: Check out code 47 | uses: actions/checkout@v2 48 | 49 | - name: Get dependencies 50 | run: | 51 | go get -v -t -d ./... 52 | 53 | - name: Integration Test 54 | run: make integration 55 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.13.6-alpine as builder 2 | 3 | ARG SERVICE 4 | 5 | RUN apk add --update --no-cache git build-base musl-dev linux-headers 6 | RUN mkdir /build 7 | WORKDIR /build 8 | COPY go.mod . 9 | COPY go.sum . 10 | RUN go mod download 11 | COPY . . 12 | RUN go build -o bin/blockatlas ./cmd/$SERVICE 13 | 14 | FROM alpine:latest 15 | COPY --from=builder /build/bin /bin/ 16 | COPY --from=builder /build/config.yml /config/ 17 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 18 | 19 | ENTRYPOINT ["/bin/blockatlas", "-c", "/config/config.yml"] 20 | -------------------------------------------------------------------------------- /Dockerfile.runner: -------------------------------------------------------------------------------- 1 | FROM golang:1.13.6-stretch 2 | ARG SERVICE 3 | COPY ./bin/$SERVICE /app/main 4 | COPY ./config.yml /config/ 5 | ENTRYPOINT ["/app/main", "-c", "/config/config.yml"] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Trust Wallet 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: bin/setup -c $HOME/config.yml 2 | web: bin/api -c $HOME/config.yml -p $PORT 3 | consumer_transactions: CONSUMER_SERVICE=transactions bin/consumer -c $HOME/config.yml 4 | consumer_subscriptions: CONSUMER_SERVICE=subscriptions bin/consumer -c $HOME/config.yml 5 | consumer_subscriptions_tokens: CONSUMER_SERVICE=subscriptions_tokens bin/consumer -c $HOME/config.yml 6 | consumer_tokens: CONSUMER_SERVICE=tokens bin/consumer -c $HOME/config.yml 7 | parser: bin/parser -c $HOME/config.yml 8 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/prometheus/client_golang/prometheus/promhttp" 6 | ginSwagger "github.com/swaggo/gin-swagger" 7 | "github.com/swaggo/gin-swagger/swaggerFiles" 8 | "github.com/trustwallet/blockatlas/config" 9 | _ "github.com/trustwallet/blockatlas/docs" 10 | "github.com/trustwallet/blockatlas/platform" 11 | "github.com/trustwallet/blockatlas/services/tokenindexer" 12 | ) 13 | 14 | func SetupPlatformAPI(router gin.IRouter) { 15 | for _, api := range platform.Platforms { 16 | RegisterTransactionsAPI(router, api) 17 | RegisterTokensAPI(router, api) 18 | RegisterStakeAPI(router, api) 19 | RegisterBlockAPI(router, api) 20 | } 21 | for _, api := range platform.CollectionsAPIs { 22 | RegisterCollectionsAPI(router, api) 23 | } 24 | 25 | RegisterBatchAPI(router) 26 | RegisterBasicAPI(router) 27 | } 28 | 29 | func SetupTokensIndexAPI(router gin.IRouter, instance tokenindexer.Instance) { 30 | RegisterTokensIndexAPI(router, instance) 31 | } 32 | 33 | func SetupSwaggerAPI(router gin.IRouter) { 34 | router.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 35 | } 36 | 37 | func SetupMetrics(router gin.IRouter) { 38 | router.GET(config.Default.Metrics.Path, gin.WrapH(promhttp.Handler())) 39 | } 40 | -------------------------------------------------------------------------------- /api/endpoint/basic.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/trustwallet/blockatlas/internal" 6 | "net/http" 7 | ) 8 | 9 | func GetStatus(c *gin.Context) { 10 | c.JSON(http.StatusOK, map[string]interface{}{ 11 | "status": true, 12 | "build": internal.Build, 13 | "date": internal.Date, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /api/endpoint/block.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/trustwallet/blockatlas/pkg/blockatlas" 10 | ) 11 | 12 | // @Summary Get Block 13 | // @ID block_v2 14 | // @Description Get Block information 15 | // @Accept json 16 | // @Produce json 17 | // @Tags Transactions 18 | // @Param coin path string true "the coin name" default(zilliqa) 19 | // @Param address path string true "the query address" default(850321) 20 | // @Failure 500 {object} ErrorResponse 21 | // @Router /v2/{coin}/blocks/{block} [get] 22 | func GetBlock(c *gin.Context, blockAPI blockatlas.BlockAPI) { 23 | blockString := c.Param("block") 24 | blockNumber, err := strconv.ParseUint(blockString, 10, 32) 25 | 26 | if err != nil || blockNumber < 1 { 27 | c.AbortWithStatusJSON(http.StatusBadRequest, errorResponse(errors.New("invalid block number"))) 28 | return 29 | } 30 | 31 | block, err := blockAPI.GetBlockByNumber(int64(blockNumber)) 32 | 33 | if err != nil { 34 | c.AbortWithStatusJSON(http.StatusNotFound, errorResponse(errors.New("block number not found"))) 35 | return 36 | } 37 | 38 | c.JSON(http.StatusOK, &block) 39 | } 40 | -------------------------------------------------------------------------------- /api/endpoint/errors.go: -------------------------------------------------------------------------------- 1 | package endpoint 2 | 3 | type ( 4 | ErrorResponse struct { 5 | Error ErrorDetails `json:"error"` 6 | } 7 | ErrorDetails struct { 8 | Message string `json:"message"` 9 | } 10 | 11 | ErrorCode int 12 | ) 13 | 14 | func errorResponse(err error) ErrorResponse { 15 | var message string 16 | if err != nil { 17 | message = err.Error() 18 | } 19 | return ErrorResponse{Error: ErrorDetails{ 20 | Message: message, 21 | }} 22 | } 23 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blockatlas", 3 | "buildpacks":[ 4 | { 5 | "url":"heroku/go" 6 | } 7 | ], 8 | "addons": [ 9 | "heroku-postgresql:hobby-dev", 10 | "cloudamqp" 11 | ], 12 | "environments": { 13 | "review": { 14 | "addons": [ 15 | { 16 | "plan":"heroku-postgresql:hobby-dev", 17 | "as":"POSTGRES" 18 | }, 19 | { 20 | "plan":"cloudamqp:lemur", 21 | "as":"OBSERVER_RABBITMQ" 22 | } 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/trustwallet/blockatlas/db/models" 8 | 9 | "gorm.io/gorm/logger" 10 | 11 | gocache "github.com/patrickmn/go-cache" 12 | "gorm.io/driver/postgres" 13 | "gorm.io/gorm" 14 | ) 15 | 16 | type Instance struct { 17 | Gorm *gorm.DB 18 | MemoryCache *gocache.Cache 19 | } 20 | 21 | func New(url string, log bool) (*Instance, error) { 22 | var logMode logger.LogLevel 23 | if log { 24 | logMode = logger.Info 25 | } 26 | 27 | cfg := &gorm.Config{Logger: logger.Default.LogMode(logMode), SkipDefaultTransaction: true} 28 | 29 | db, err := gorm.Open(postgres.Open(url), cfg) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | mc := gocache.New(gocache.NoExpiration, gocache.NoExpiration) 35 | i := &Instance{Gorm: db, MemoryCache: mc} 36 | 37 | return i, nil 38 | } 39 | 40 | func Setup(db *gorm.DB) error { 41 | return db.AutoMigrate( 42 | &models.Tracker{}, 43 | &models.Asset{}, 44 | &models.Subscription{}, 45 | &models.SubscriptionsAssetAssociation{}, 46 | ) 47 | } 48 | 49 | func (i *Instance) MemorySet(key string, data []byte, exp time.Duration) error { 50 | i.MemoryCache.Set(key, data, exp) 51 | return nil 52 | } 53 | 54 | func (i *Instance) MemoryGet(key string) ([]byte, error) { 55 | res, ok := i.MemoryCache.Get(key) 56 | if !ok { 57 | return nil, errors.New("not found") 58 | } 59 | return res.([]byte), nil 60 | } 61 | -------------------------------------------------------------------------------- /db/models/subscriptions.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type ( 8 | Subscription struct { 9 | ID uint `gorm:"primaryKey;"` 10 | Address string `gorm:"uniqueIndex; type:varchar(256); not null;"` 11 | } 12 | 13 | SubscriptionsAssetAssociation struct { 14 | CreatedAt time.Time `gorm:"index;"` 15 | UpdatedAt time.Time `gorm:"index;"` 16 | Subscription Subscription `gorm:"ForeignKey:SubscriptionId; not null"` 17 | SubscriptionId uint `gorm:"primary_key; autoIncrement:false; index"` 18 | 19 | Asset Asset `gorm:"ForeignKey:AssetId; not null"` 20 | AssetId uint `gorm:"primary_key; autoIncrement:false; index"` 21 | } 22 | ) 23 | -------------------------------------------------------------------------------- /db/models/tracker.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Tracker struct { 6 | UpdatedAt time.Time 7 | Coin string `gorm:"primary_key:true; type:varchar(64)"` 8 | Priority string `gorm:"default:normal"` 9 | Height int64 10 | Enabled bool `gorm:"default:true" sql:"index"` 11 | } 12 | -------------------------------------------------------------------------------- /db/tracker.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/db/models" 5 | "gorm.io/gorm/clause" 6 | ) 7 | 8 | func (i *Instance) GetLastParsedBlockNumbers() ([]models.Tracker, error) { 9 | var trackers []models.Tracker 10 | if err := i.Gorm. 11 | Find(&trackers).Error; err != nil { 12 | return trackers, nil 13 | } 14 | return trackers, nil 15 | } 16 | 17 | func (i *Instance) GetLastParsedBlockNumber(coin string) (models.Tracker, error) { 18 | var tracker models.Tracker 19 | if err := i.Gorm. 20 | Find(&tracker, "coin = ?", coin).Error; err != nil { 21 | return tracker, err 22 | } 23 | return tracker, nil 24 | } 25 | 26 | func (i *Instance) SetLastParsedBlockNumber(coin string, num int64) error { 27 | tracker := models.Tracker{ 28 | Coin: coin, 29 | Height: num, 30 | } 31 | return i.Gorm.Clauses(clause.OnConflict{ 32 | Columns: []clause.Column{ 33 | { 34 | Name: "coin", 35 | }, 36 | }, 37 | DoUpdates: clause.AssignmentColumns([]string{"height", "updated_at"}), 38 | }).Create(&tracker).Error 39 | } 40 | -------------------------------------------------------------------------------- /deployment/Procfile-consumer: -------------------------------------------------------------------------------- 1 | release: bin/setup -c $HOME/config.yml 2 | transactions: CONSUMER_SERVICE=transactions bin/consumer -c $HOME/config.yml 3 | subscriptions: CONSUMER_SERVICE=subscriptions bin/consumer -c $HOME/config.yml 4 | subscriptions_tokens: CONSUMER_SERVICE=subscriptions_tokens bin/consumer -c $HOME/config.yml 5 | tokens: CONSUMER_SERVICE=tokens bin/consumer -c $HOME/config.yml 6 | -------------------------------------------------------------------------------- /deployment/Procfile-parser: -------------------------------------------------------------------------------- 1 | release: bin/setup -c $HOME/config.yml 2 | parser: bin/parser -c $HOME/config.yml 3 | -------------------------------------------------------------------------------- /deployment/Procfile-web: -------------------------------------------------------------------------------- 1 | release: bin/setup -c $HOME/config.yml 2 | web: bin/api -c $HOME/config.yml -p $PORT 3 | -------------------------------------------------------------------------------- /docs/features.csv: -------------------------------------------------------------------------------- 1 | Chain\API,Transaction list,Block parsing,Staking ,Token list,Collectiable 2 | Bitcoin,✅,✅,N/A,N/A,N/A 3 | Ethereum,✅,✅,,✅,✅ 4 | ICON,✅,,N/A,N/A,N/A 5 | Cosmos,✅,✅,✅,, 6 | XRP,✅,✅,N/A,N/A,N/A 7 | Stellar,✅,✅,N/A,,N/A 8 | Nano,✅,,N/A,N/A,N/A 9 | Tron,✅,✅,✅,✅, 10 | FIO,✅,,N/A,N/A,N/A 11 | Nimiq,✅,✅,N/A,N/A,N/A 12 | Algorand,✅,✅,✅,,N/A 13 | IoTeX,✅,✅,✅,N/A,N/A 14 | Zilliqa,✅,✅,,N/A,N/A 15 | Polkadot,✅,✅,,,N/A 16 | Aion,✅,,,N/A,N/A 17 | Aeternity,✅,,N/A,N/A,N/A 18 | Kava,✅,✅,✅,, 19 | Filecoin,,✅,N/A,N/A,N/A 20 | Theta,✅,,N/A,N/A,N/A 21 | Solana,,,✅,,N/A 22 | Elrond,✅,✅,,N/A,N/A 23 | BNB,✅,✅,,✅, 24 | VeChain,✅,✅,N/A,N/A,N/A 25 | Harmony,✅,✅,✅,N/A,N/A 26 | Ontology,✅,✅,N/A,N/A,N/A 27 | Tezos,✅,✅,✅,,N/A 28 | Nebulas,✅,✅,N/A,N/A,N/A 29 | Waves,✅,✅,,N/A,N/A 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/trustwallet/blockatlas 2 | 3 | go 1.15 4 | 5 | // +heroku goVersion go1.15 6 | // +heroku install ./cmd/... 7 | 8 | require ( 9 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect 10 | github.com/Microsoft/go-winio v0.5.2 // indirect 11 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 12 | github.com/btcsuite/btcutil v1.0.2 13 | github.com/containerd/continuity v0.1.0 // indirect 14 | github.com/deckarep/golang-set v1.7.1 15 | github.com/getsentry/raven-go v0.2.0 16 | github.com/gin-contrib/cors v1.3.1 17 | github.com/gin-gonic/gin v1.7.7 18 | github.com/itchyny/timefmt-go v0.1.2 19 | github.com/mitchellh/mapstructure v1.4.1 20 | github.com/mr-tron/base58 v1.2.0 21 | github.com/opencontainers/runc v1.1.0 // indirect 22 | github.com/ory/dockertest v3.3.5+incompatible 23 | github.com/patrickmn/go-cache v2.1.0+incompatible 24 | github.com/prometheus/client_golang v0.9.4 25 | github.com/sirupsen/logrus v1.8.1 26 | github.com/spf13/viper v1.7.1 27 | github.com/streadway/amqp v1.0.0 28 | github.com/stretchr/testify v1.7.0 29 | github.com/swaggo/gin-swagger v1.3.0 30 | github.com/swaggo/swag v1.7.0 31 | github.com/trustwallet/golibs v0.1.8 32 | github.com/trustwallet/golibs/network v0.0.0-20210302024139-c340cb937103 33 | golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd 34 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a // indirect 35 | gopkg.in/yaml.v2 v2.4.0 36 | gorm.io/driver/postgres v1.0.8 37 | gorm.io/gorm v1.20.12 38 | ) 39 | -------------------------------------------------------------------------------- /internal/init.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/trustwallet/golibs/network/middleware" 7 | 8 | "github.com/gin-gonic/gin" 9 | log "github.com/sirupsen/logrus" 10 | 11 | "github.com/gin-contrib/cors" 12 | "github.com/trustwallet/blockatlas/config" 13 | "github.com/trustwallet/golibs/network/mq" 14 | 15 | "path/filepath" 16 | "time" 17 | ) 18 | 19 | var ( 20 | Build = "dev" 21 | Date = time.Now().String() 22 | ) 23 | 24 | func ParseArgs(defaultPort, defaultConfigPath string) (string, string) { 25 | var ( 26 | port, confPath string 27 | ) 28 | 29 | flag.StringVar(&port, "p", defaultPort, "port for api") 30 | flag.StringVar(&confPath, "c", defaultConfigPath, "config file for api") 31 | flag.Parse() 32 | 33 | return port, confPath 34 | } 35 | 36 | func InitConfig(confPath string) { 37 | confPath, err := filepath.Abs(confPath) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | config.Init(confPath) 43 | } 44 | 45 | func InitEngine(ginMode string) *gin.Engine { 46 | gin.SetMode(ginMode) 47 | engine := gin.New() 48 | 49 | engine.Use(cors.Default()) 50 | engine.Use(middleware.Logger()) 51 | 52 | return engine 53 | } 54 | 55 | func InitMQ(url string) { 56 | err := mq.Init(url) 57 | if err != nil { 58 | log.Fatal("Failed to init Rabbit MQ", err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "github.com/trustwallet/blockatlas/db" 8 | 9 | "github.com/prometheus/client_golang/prometheus" 10 | ) 11 | 12 | var ( 13 | namespace = "blockatlas" 14 | 15 | workerBlockParsing = prometheus.NewGaugeVec( 16 | prometheus.GaugeOpts{ 17 | Namespace: namespace, 18 | Subsystem: "worker", 19 | Name: "block_parsing", 20 | Help: "Last parsed block", 21 | }, 22 | []string{ 23 | "coin", 24 | "priority", 25 | "enabled", 26 | }, 27 | ) 28 | ) 29 | 30 | func setupUpdateTrackerMetrics(db *db.Instance) { 31 | go func() { 32 | for { 33 | trackers, err := db.GetLastParsedBlockNumbers() 34 | if err != nil { 35 | continue 36 | } 37 | for _, tracker := range trackers { 38 | labels := prometheus.Labels{"coin": tracker.Coin, "priority": tracker.Priority, "enabled": strconv.FormatBool(tracker.Enabled)} 39 | workerBlockParsing.With(labels).Set(float64(tracker.Height)) 40 | } 41 | time.Sleep(1 * time.Second) 42 | } 43 | }() 44 | } 45 | 46 | func Setup(db *db.Instance) { 47 | prometheus.DefaultRegisterer.Unregister(prometheus.NewGoCollector()) 48 | prometheus.DefaultRegisterer.Unregister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) 49 | 50 | prometheus.MustRegister(workerBlockParsing) 51 | 52 | setupUpdateTrackerMetrics(db) 53 | } 54 | -------------------------------------------------------------------------------- /internal/mq.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/streadway/amqp" 5 | "github.com/trustwallet/blockatlas/db" 6 | "github.com/trustwallet/golibs/network/mq" 7 | ) 8 | 9 | const ( 10 | // End consumer of published transactions. Not consumed on blockatlas 11 | TxNotifications mq.Queue = "txNotifications" 12 | // Address:coin subscriptions 13 | Subscriptions mq.Queue = "subscriptions" 14 | SubscriptionsTokens mq.Queue = "subscriptions_tokens" 15 | 16 | // Transactions to process, if match subscriptions, pushed to TxNotifications 17 | RawTransactions mq.Queue = "rawTransactions" 18 | RawTokens mq.Queue = "rawTokens" 19 | RawTransactionsExchange mq.Exchange = "raw_transactions" 20 | ) 21 | 22 | type ConsumerDatabase struct { 23 | Database *db.Instance 24 | Delivery func(*db.Instance, amqp.Delivery) error 25 | Tag string 26 | } 27 | 28 | func (c ConsumerDatabase) Callback(msg amqp.Delivery) error { 29 | return c.Delivery(c.Database, msg) 30 | } 31 | -------------------------------------------------------------------------------- /mock/README.md: -------------------------------------------------------------------------------- 1 | # Test data files 2 | 3 | Responses for mock tests are stored in data files. 4 | The mock uses these files to return canned responses. 5 | 6 | The data files can be updated, by reading and storing responses from the real external APIs. 7 | 8 | ## File list 9 | 10 | There is a file `mock/datafiles.yaml` containing API paths and corresponding data files. 11 | 12 | Example: 13 | 14 | ``` 15 | - file: mock/ext-api-data/tron-api_v1_assets_1002814.json 16 | mockURL: /mock/tron-api/v1/assets/1002814 17 | method: GET 18 | extURL: https://api.trongrid.io/v1/assets/1002814 19 | ``` 20 | 21 | Fields: 22 | 23 | * file: name of response data json file (relative to repository root). 24 | * mockURL: the API path for this call, in the mock server. Usually starts with '/mock/'. 25 | * method: GET or POST 26 | * extURL: Optional. The full URL of the external reap API. Used to refresh the data file, manually during development, or automatically. 27 | * reqFile: In case of POST requests, the json file containing POST request. Used to select which resposne to return, and when invoking external API. 28 | * reqField: Optional. Some POST requests cannot be matched by full request json matching, because they contain a changing field, typically call id. In this case one field can be selected (.e.g 'address'), and input is matched by the field only. 29 | 30 | -------------------------------------------------------------------------------- /mock/ext-api-data/algorand-api_v1_account_4EZFQABCVQTHQCK3HQBIYGC4NV2VM42FZHEFTVH77ROG4ZGREC6Y7V5T2U_transactions.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactions": [ 3 | { 4 | "type": "pay", 5 | "tx": "JZTKP6RRFGUDOZ7DQIR26DQWRDYZVYG2G3WDE4WLBJ77AMAEBFBA", 6 | "from": "5TSQNIL54GB545B3WLC6OVH653SHAELMHU6MSVNGTUNMOEHAMWG7EC3AA4", 7 | "fee": 1000, 8 | "first-round": 5478300, 9 | "last-round": 5478749, 10 | "noteb64": "sHLxsLBrP3o=", 11 | "round": 5478346, 12 | "payment": { 13 | "to": "4EZFQABCVQTHQCK3HQBIYGC4NV2VM42FZHEFTVH77ROG4ZGREC6Y7V5T2U", 14 | "amount": 1, 15 | "torewards": 2052177, 16 | "closerewards": 0 17 | }, 18 | "fromrewards": 0, 19 | "genesisID": "mainnet-v1.0", 20 | "genesishashb64": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /mock/ext-api-data/binance-api_v1_account_bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q: -------------------------------------------------------------------------------- 1 | {"account_number":273171,"address":"bnb1jeu6gscugy6l2wyatxthkh2hmer4hzevgcmf0q","balances":[{"free":"226.52883965","frozen":"0.00000000","locked":"0.00000000","symbol":"BNB"},{"free":"3649.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,1,132,202,113,77,165,176,46,204,240,131,18,251,120,168,140,204,43,27,32,135,157,30,25,86,154,105,108,64,211],"sequence":82} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/binance-api_v1_txs__address_bnb1z35wusfv8twfele77vddclka9z84ugywug48gn_txAsset_BNB.json: -------------------------------------------------------------------------------- 1 | {"tx":[{"txHash":"0CE23D9F143F7FAF192BB55F33C8FCBC1095D98410A750F63777987685E2C154","blockHeight":106690566,"txType":"TRANSFER","timeStamp":"2020-08-12T10:18:26.388Z","fromAddr":"bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m","toAddr":"bnb1z35wusfv8twfele77vddclka9z84ugywug48gn","value":"6018.97200000","txAsset":"RUNE-B1A","txFee":"0.00037500","proposalId":null,"txAge":8026,"orderId":null,"code":0,"data":null,"confirmBlocks":0,"memo":"","source":0,"sequence":736321}],"total":1} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/callisto-api_tokens__address_0xc3d5b69f65027ddf48f894e6e90121293a2f6615.json: -------------------------------------------------------------------------------- 1 | {"total":1,"docs":[{"address":"0xc3d5B69F65027dDF48f894E6e90121293a2F6615","name":"The Hitchhikers Guide to the Galaxy","decimals":18,"symbol":"H2G2"}]} -------------------------------------------------------------------------------- /mock/ext-api-data/cosmos-api_auth_accounts_cosmos1dx27g0kzhwej0ekcf2k9hsktcxnmpl7fcehcvq.json: -------------------------------------------------------------------------------- 1 | {"height":"1900975","result":{"type":"cosmos-sdk/Account","value":{"address":"cosmos1dx27g0kzhwej0ekcf2k9hsktcxnmpl7fcehcvq","coins":[],"public_key":{"type":"tendermint/PubKeySecp256k1","value":"A782zo6TI2H3DfHJ7X1WHOJz6p4fUYVRYhb/XqMTcVQt"},"account_number":"10373","sequence":"3"}}} -------------------------------------------------------------------------------- /mock/ext-api-data/cosmos-api_minting_inflation.json: -------------------------------------------------------------------------------- 1 | { 2 | "height": "1869960", 3 | "result": "0.070000000000000000" 4 | } -------------------------------------------------------------------------------- /mock/ext-api-data/cosmos-api_staking_delegators_cosmos1dx27g0kzhwej0ekcf2k9hsktcxnmpl7fcehcvq_delegations.json: -------------------------------------------------------------------------------- 1 | { 2 | "height": "1869960", 3 | "result": [ 4 | { 5 | "delegator_address": "cosmos1dx27g0kzhwej0ekcf2k9hsktcxnmpl7fcehcvq", 6 | "validator_address": "cosmosvaloper17h2x3j7u44qkrq0sk8ul0r2qr440rwgjkfg0gh", 7 | "shares": "2211271.000000000000000000", 8 | "balance": "2211271" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /mock/ext-api-data/cosmos-api_staking_pool.json: -------------------------------------------------------------------------------- 1 | { 2 | "height": "1869960", 3 | "result": { 4 | "not_bonded_tokens": "4596456385348", 5 | "bonded_tokens": "182417010067615" 6 | } 7 | } -------------------------------------------------------------------------------- /mock/ext-api-data/eth-blockbook-api_v2_address_0x0875BCab22dE3d02402bc38aEe4104e1239374a7__details_tokenBalances.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "0x0875BCab22dE3d02402bc38aEe4104e1239374a7", 3 | "balance": "182976771756327797", 4 | "unconfirmedBalance": "0", 5 | "unconfirmedTxs": 0, 6 | "txs": 263, 7 | "nonTokenTxs": 243, 8 | "nonce": "231", 9 | "tokens": [ 10 | { 11 | "type": "ERC20", 12 | "name": "Kyber Network Crystal", 13 | "contract": "0xdd974D5C2e2928deA5F71b9825b8b646686BD200", 14 | "transfers": 13, 15 | "symbol": "KNC", 16 | "decimals": 18, 17 | "balance": "41100" 18 | }, 19 | { 20 | "type": "ERC20", 21 | "name": "BNB", 22 | "contract": "0xB8c77482e45F1F44dE1745F52C74426C631bDD52", 23 | "transfers": 4, 24 | "symbol": "BNB", 25 | "decimals": 18, 26 | "balance": "100500" 27 | }, 28 | { 29 | "type": "ERC20", 30 | "name": "BNB", 31 | "contract": "0xB8c77482e45F1F44dE1745F52C74426C631bDD52", 32 | "transfers": 4, 33 | "symbol": "BNB", 34 | "decimals": 18, 35 | "balance": "0" 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x0178b8bf2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","id":257,"result":"0x0000000000000000000000001da022710df5002339274aadee8d58218e9d6ab5"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x0178b8bf2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047.request_json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x0178b8bf2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047","to":"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"},"latest"],"id":257} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x0178b8bfa538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","id":251,"result":"0x000000000000000000000000bd5f5ec7ed5f19b53726344540296c02584a5237"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x0178b8bfa538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4.request_json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x0178b8bfa538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4","to":"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"},"latest"],"id":251} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x0178b8bfee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","id":249,"result":"0x000000000000000000000000226159d592e2b063810a10ebf6dcbada94ed68b8"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x0178b8bfee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835.request_json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x0178b8bfee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835","to":"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"},"latest"],"id":249} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x3b3b57de2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","id":259,"result":"0x0000000000000000000000000c54eead78d555be3cbcd451424f9a27a7843935"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x3b3b57de2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047.request_json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x3b3b57de2337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047","to":"0x1da022710df5002339274aadee8d58218e9d6ab5"},"latest"],"id":259} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x3b3b57dea538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","id":253,"result":"0x000000000000000000000000d8a667312d5260f12a306ae7730c754d938da86c"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0x3b3b57dea538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4.request_json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x3b3b57dea538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4","to":"0xbd5f5ec7ed5f19b53726344540296c02584a5237"},"latest"],"id":253} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0xf1cb7e062337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047000000000000000000000000000000000000000000000000000000000000003c.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","id":258,"result":"0x"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0xf1cb7e062337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047000000000000000000000000000000000000000000000000000000000000003c.request_json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0xf1cb7e062337fcf9521666fd5114df2902fa9e6da5a6b004b5a192a0f55d2d9fab4f1047000000000000000000000000000000000000000000000000000000000000003c","to":"0x1da022710df5002339274aadee8d58218e9d6ab5"},"latest"],"id":258} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0xf1cb7e06a538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4000000000000000000000000000000000000000000000000000000000000003c.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","id":252,"result":"0x"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0xf1cb7e06a538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4000000000000000000000000000000000000000000000000000000000000003c.request_json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0xf1cb7e06a538cd174de170e8062c97c25c40a7ceb56aca81b12e6f5afc46f5a429999aa4000000000000000000000000000000000000000000000000000000000000003c","to":"0xbd5f5ec7ed5f19b53726344540296c02584a5237"},"latest"],"id":252} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0xf1cb7e06ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835000000000000000000000000000000000000000000000000000000000000003c.json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","id":328,"result":"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000014d8da6bf26964af9d7eed9e03e53415d37aa96045000000000000000000000000"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/eth-rpc__eth_call_data_0xf1cb7e06ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835000000000000000000000000000000000000000000000000000000000000003c.request_json: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0xf1cb7e06ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835000000000000000000000000000000000000000000000000000000000000003c","to":"0x226159d592e2b063810a10ebf6dcbada94ed68b8"},"latest"],"id":328} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/ethclassic-api_tokens__address_0xa12105efa0663147bddee178f6a741ac15676b79.json: -------------------------------------------------------------------------------- 1 | {"total":2,"docs":[{"address":"0xCA68fE57A0E9987F940Ebcc65fe5F96E7bC30128","name":"Litecoin Classic Token","decimals":8,"symbol":"LCT"},{"address":"0x2B682bd9d5c31E67a95cbdF0292017C02E51923C","name":"Ether Klown","decimals":6,"symbol":"KLOWN2"}]} -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_chain_get_pub_address.0001.json: -------------------------------------------------------------------------------- 1 | {"public_address":"0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a0001"} -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_chain_get_pub_address.0001.request_json: -------------------------------------------------------------------------------- 1 | { 2 | fio_address: 'trust@trustwallet', 3 | token_code: 'ETH', 4 | chain_code: 'ETH' 5 | } 6 | -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_chain_get_pub_address.0002.json: -------------------------------------------------------------------------------- 1 | {"public_address":"0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a0002"} -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_chain_get_pub_address.0002.request_json: -------------------------------------------------------------------------------- 1 | { 2 | fio_address: 'name@somefiodomain', 3 | token_code: 'ETH', 4 | chain_code: 'ETH' 5 | } 6 | -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_chain_get_pub_address.json: -------------------------------------------------------------------------------- 1 | {"public_address":"0xce5cB6c92Da37bbBa91Bd40D4C9D4D724A3a8F51"} -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_chain_get_pub_address.request_json: -------------------------------------------------------------------------------- 1 | {fio_address: "trust@trust", token_code: "ETH", chain_code: "ETH"} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_history_get_actions.0001.request_json: -------------------------------------------------------------------------------- 1 | { 2 | "account_name": "gmdncuvoqxfn" 3 | } -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_history_get_actions.0002.request_json: -------------------------------------------------------------------------------- 1 | { 2 | "account_name": "fio.treasury" 3 | } -------------------------------------------------------------------------------- /mock/ext-api-data/fio-api_v1_history_get_actions.request_json: -------------------------------------------------------------------------------- 1 | { 2 | "account_name": "ezsmbcy2opod" 3 | } -------------------------------------------------------------------------------- /mock/ext-api-data/gochain-api_tokens__address_0x0Fd98FB42C439E5F6484f7E71Caa6661d81d0628.json: -------------------------------------------------------------------------------- 1 | {"total":1,"docs":[{"address":"0x5f16Fa0B5c9d779a3C8d46859a27973Ff3511188","name":"pukkamex","decimals":18,"symbol":"PUX"}]} -------------------------------------------------------------------------------- /mock/ext-api-data/harmony-api.request_json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "hmy_getTransactionsHistory", 4 | "params": [ 5 | { 6 | "address": "one1e4mr7tp0a76wnhv9xd0wzentdjnjnsh3fwzgfv", 7 | "fullTx": true 8 | } 9 | ], 10 | "id": 1 11 | } -------------------------------------------------------------------------------- /mock/ext-api-data/iotex-api_accounts_io1mwekae7qqwlr23220k5n9z3fmjxz72tuchra3m.json: -------------------------------------------------------------------------------- 1 | { 2 | "accountMeta": { 3 | "address": "io1mwekae7qqwlr23220k5n9z3fmjxz72tuchra3m", 4 | "balance": "3472246872153132576758722", 5 | "nonce": "628", 6 | "pendingNonce": "629", 7 | "numActions": "730" 8 | } 9 | } -------------------------------------------------------------------------------- /mock/ext-api-data/iotex-api_accounts_io1vg808avg2ydye8djl2axmkc9j0xhzu6vdaw6g5.json: -------------------------------------------------------------------------------- 1 | { 2 | "accountMeta": { 3 | "address": "io1vg808avg2ydye8djl2axmkc9j0xhzu6vdaw6g5", 4 | "balance": "76393702651088989820", 5 | "nonce": "4", 6 | "pendingNonce": "5", 7 | "numActions": "94" 8 | } 9 | } -------------------------------------------------------------------------------- /mock/ext-api-data/iotex-api_staking_delegations_io1mwekae7qqwlr23220k5n9z3fmjxz72tuchra3m.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "delegator": { 4 | "id": "iotxplorerio", 5 | "status": true, 6 | "info": { 7 | "name": "iotxplorer", 8 | "description": "", 9 | "image": "https://imgc.iotex.io/dokc3pa1x/image/upload/v1551121446/delegates/iotxplorer/Group_2.png", 10 | "website": "https://twitter.com/iotxplorer" 11 | }, 12 | "details": { 13 | "reward": { 14 | "annual": 0 15 | }, 16 | "locktime": 259200, 17 | "minimum_amount": "100000000000000000000" 18 | } 19 | }, 20 | "value": "100000000000000000000", 21 | "status": "active" 22 | } 23 | ] -------------------------------------------------------------------------------- /mock/ext-api-data/kava-api_auth_accounts_kava1l8va9zyl50cpzv447c694k3jndelc9ygtfll2m.json: -------------------------------------------------------------------------------- 1 | {"height":"2283151","result":{"type":"cosmos-sdk/Account","value":{"address":"kava1l8va9zyl50cpzv447c694k3jndelc9ygtfll2m","coins":[],"public_key":{"type":"tendermint/PubKeySecp256k1","value":"AvgGasyPvRpG68UQ6IGPVKN+HrJix24tgwcYXIcIfsna"},"account_number":"284","sequence":"1"}}} -------------------------------------------------------------------------------- /mock/ext-api-data/kava-api_minting_inflation.json: -------------------------------------------------------------------------------- 1 | { 2 | "height": "2251949", 3 | "result": "0.058055354540680354" 4 | } -------------------------------------------------------------------------------- /mock/ext-api-data/kava-api_staking_delegators_kava1l8va9zyl50cpzv447c694k3jndelc9ygtfll2m_delegations.json: -------------------------------------------------------------------------------- 1 | { 2 | "height": "2251949", 3 | "result": [] 4 | } -------------------------------------------------------------------------------- /mock/ext-api-data/kava-api_staking_pool.json: -------------------------------------------------------------------------------- 1 | { 2 | "height": "2251949", 3 | "result": { 4 | "not_bonded_tokens": "1411599189144", 5 | "bonded_tokens": "80779823240237" 6 | } 7 | } -------------------------------------------------------------------------------- /mock/ext-api-data/kusama-api_scan_transfers.request_json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "HZaz6cUo8wJ9zjwoDtA3ZzYkrCLfDYU8b3uPmYttKFFvvRK", 3 | "row": 25 4 | } -------------------------------------------------------------------------------- /mock/ext-api-data/nano-api.request_json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "account_history", 3 | "account": "nano_36e7qfxrpixge3xxujtpc87c77mn9ubu3bhywfjkr1trnubtd4qswwydhn9z", 4 | "count": "25" 5 | } -------------------------------------------------------------------------------- /mock/ext-api-data/nimiq-rpc.request_json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "getTransactionsByAddress", 4 | "params": [ 5 | "NQ94HC9AK9D83FSJM6PT8XGNNMXLR0E53Y07", 6 | "25" 7 | ], 8 | "id": 345 9 | } 10 | -------------------------------------------------------------------------------- /mock/ext-api-data/poa-api_tokens__address_0x0875BCab22dE3d02402bc38aEe4104e1239374a7.json: -------------------------------------------------------------------------------- 1 | {"total":1,"docs":[{"address":"0xADFE00d92e5A16e773891F59780e6e54f40B532e","name":"Viktor Coin","decimals":0,"symbol":"VIK"}]} -------------------------------------------------------------------------------- /mock/ext-api-data/thundertoken-api_tokens__address_0x0b230def08139f18a86536d9cfa150f04435414c.json: -------------------------------------------------------------------------------- 1 | {"total":1,"docs":[{"address":"0x51BcA9300d034A7e6aB379399a317bDfA0D4835b","name":"The Third Identity","decimals":18,"symbol":"TTI"}]} -------------------------------------------------------------------------------- /mock/ext-api-data/tomochain-api_tokens__address_0x8b353021189375591723e7384262f45709a3c3dc.json: -------------------------------------------------------------------------------- 1 | {"total":2,"docs":[{"address":"0xaB7e4aE99D7bfff4de8322aB915e9066857227F0","name":"KONG","decimals":18,"symbol":"KONG"},{"address":"0xc7BdF5D257fF4EC078e12A3ABD34dFc329E55130","name":"AIS Token","decimals":18,"symbol":"AIS"}]} -------------------------------------------------------------------------------- /mock/ext-api-data/tron-api_v1_assets_1002000.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "meta": { 4 | "at": 1589387413939, 5 | "page_size": 1 6 | }, 7 | "data": [ 8 | { 9 | "id": "1002000", 10 | "abbr": "BTT", 11 | "description": "Official Token of BitTorrent Protocol", 12 | "name": "BitTorrent", 13 | "num": 1, 14 | "precision": 6, 15 | "total_supply": "990000000000000000", 16 | "trx_num": 1, 17 | "url": "www.bittorrent.com", 18 | "owner_address": "4137fa1a56eb8c503624701d776d95f6dae1d9f0d6", 19 | "start_time": 1548000000000, 20 | "end_time": 1548000001000 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /mock/ext-api-data/tron-api_v1_assets_1002798.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "meta": { 4 | "at": 1589366702515, 5 | "page_size": 1 6 | }, 7 | "data": [ 8 | { 9 | "id": "1002798", 10 | "abbr": "EPICAL", 11 | "description": "The token of the game Builder III", 12 | "name": "EPICAL", 13 | "num": 1000000000, 14 | "precision": 6, 15 | "total_supply": "10000000000000000", 16 | "trx_num": 1000000000, 17 | "url": "https://builder3.fun", 18 | "vote_score": 0, 19 | "owner_address": "41133b084f5225a9112f4e8527db26b381a32babd0", 20 | "start_time": 1574966426578, 21 | "end_time": 1574966486578 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /mock/ext-api-data/tron-api_v1_assets_1002814.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "meta": { 4 | "at": 1589387440079, 5 | "page_size": 1 6 | }, 7 | "data": [ 8 | { 9 | "id": "1002814", 10 | "abbr": "AX", 11 | "description": "The token of the game TrainX", 12 | "name": "AX", 13 | "num": 1000000000, 14 | "precision": 6, 15 | "total_supply": "10000000000000000", 16 | "trx_num": 1000000000, 17 | "url": "https://trainx.fun", 18 | "vote_score": 0, 19 | "owner_address": "418e267ead411aaaf671be100a7afe587d4eab0d71", 20 | "start_time": 1576483805985, 21 | "end_time": 1576483865985 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /mock/ext-api-data/tron-api_wallet_getaccount.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/tron-api_wallet_getaccount.request_json: -------------------------------------------------------------------------------- 1 | {"address":"TFFriedwRtWdFuzerDDtkoQTZ29smDZ1MB","visible":true} 2 | -------------------------------------------------------------------------------- /mock/ext-api-data/unstoppabledomains_api_v1__dpantani.crypto.json: -------------------------------------------------------------------------------- 1 | { 2 | "addresses": { 3 | "BCH": "qzpjlfnzudeu83krv0yk0r2kys67qptj6ys6eg6dms", 4 | "BNB": "bnb1h4vyuuytu4rm86ust29wwlevt95du52383cctm", 5 | "BTC": "bc1qd7eystu9xl53hkyxm4kyg7h5yk4p436sqx6f27", 6 | "ETH": "0x5574Cd97432cEd0D7Caf58ac3c4fEDB2061C98fB", 7 | "LTC": "ltc1qz6nd472gx5gl3urfeldkrhg3h83c8tp2m7m6sd", 8 | "XRP": "rUvXBttEXhdwaKjEM2MxbtswHU6AMhUTgJ", 9 | "ZIL": "zil1vdntvlk47j9kh9a85klqcd9rvgze06ruhmna64", 10 | "DOGE": "DP9VmQyDMyB1TWwgXkyRpBa7rTfPYgMvjy" 11 | }, 12 | "whois": {}, 13 | "ipfs": { 14 | "html": "QmUG2riiUkALEGB3AzgsJtN5KbgFcVb3p8qvc96gcsfzHo", 15 | "redirect_domain": "https://abbfe6z95qov3d40hf6j30g7auo7afhp.mypinata.cloud/ipfs/QmUG2riiUkALEGB3AzgsJtN5KbgFcVb3p8qvc96gcsfzHo" 16 | }, 17 | "gundb": {}, 18 | "meta": { 19 | "owner": "0x5574cd97432ced0d7caf58ac3c4fedb2061c98fb", 20 | "type": "CNS", 21 | "ttl": 0 22 | } 23 | } -------------------------------------------------------------------------------- /mock/ext-api-data/unstoppabledomains_api_v1__dpantani.zil.json: -------------------------------------------------------------------------------- 1 | { 2 | "addresses": { 3 | "BCH": "qzpjlfnzudeu83krv0yk0r2kys67qptj6ys6eg6dms", 4 | "BNB": "bnb1h4vyuuytu4rm86ust29wwlevt95du52383cctm", 5 | "BTC": "bc1qd7eystu9xl53hkyxm4kyg7h5yk4p436sqx6f27", 6 | "ETH": "0x5574Cd97432cEd0D7Caf58ac3c4fEDB2061C98fB", 7 | "LTC": "ltc1qz6nd472gx5gl3urfeldkrhg3h83c8tp2m7m6sd", 8 | "XRP": "rUvXBttEXhdwaKjEM2MxbtswHU6AMhUTgJ", 9 | "ZIL": "zil1vdntvlk47j9kh9a85klqcd9rvgze06ruhmna64", 10 | "DOGE": "DP9VmQyDMyB1TWwgXkyRpBa7rTfPYgMvjy" 11 | }, 12 | "whois": {}, 13 | "ipfs": { 14 | "html": "QmUG2riiUkALEGB3AzgsJtN5KbgFcVb3p8qvc96gcsfzHo", 15 | "redirect_domain": "https://abbfe6z95qov3d40hf6j30g7auo7afhp.mypinata.cloud/ipfs/QmUG2riiUkALEGB3AzgsJtN5KbgFcVb3p8qvc96gcsfzHo" 16 | }, 17 | "gundb": {}, 18 | "meta": { 19 | "owner": "0xe4da405976f315ab91ff9a43a51972cc94739aa8", 20 | "type": "ZNS", 21 | "ttl": 0 22 | } 23 | } -------------------------------------------------------------------------------- /mock/ext-api-data/vechain-api_blocks_best.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": 5887932, 3 | "id": "0x0059d7bc873407f863b77f731f8e3c9335dc16aae1c53c0f18e780773d42a7f0", 4 | "size": 542, 5 | "parentID": "0x0059d7bb2f7ba5a8482d0b9937788f212f9b6b1de08818466985f943dbc8b4c6", 6 | "timestamp": 1589366680, 7 | "gasLimit": 38953771, 8 | "beneficiary": "0xeb0c565f69557481c6c7fa347cae273128a0996e", 9 | "gasUsed": 66003, 10 | "totalScore": 566756408, 11 | "txsRoot": "0xc4b8844cdc5dcd6b48627205ef30d7e09041d0e7ba013057690778e33e881127", 12 | "txsFeatures": 1, 13 | "stateRoot": "0xf572f33e7d55a193c7ef31f6cda0a0d51b339085e2296b6e05a81b593aa66e6e", 14 | "receiptsRoot": "0x65bf45677f275349a9a9eeebdfdcb1ffd8760c98cd273fb909453b444265d081", 15 | "signer": "0x8eaefdf7d25c001e7e59363c33d7f5ad47970086", 16 | "isTrunk": true, 17 | "transactions": [ 18 | "0x36720eaff4ac46835d3300bab1c2a6e9f45907c3151ff36c247dd7c287ba8125" 19 | ] 20 | } -------------------------------------------------------------------------------- /mock/ext-api-data/vechain-api_logs_transfer.request_json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "offset": 0, 4 | "limit": 15 5 | }, 6 | "criteriaSet": [ 7 | { 8 | "sender": "0xB5e883349e68aB59307d1604555AC890fAC47128" 9 | }, 10 | { 11 | "recipient": "0xB5e883349e68aB59307d1604555AC890fAC47128" 12 | } 13 | ], 14 | "range": { 15 | "unit": "block", 16 | "from": 0, 17 | "to": 5466405 18 | }, 19 | "order": "desc" 20 | } -------------------------------------------------------------------------------- /mock/ext-api-data/vechain-api_transactions_0x004aa0448e458105b098aea2a764a1d54ab95451bee488869f417b351857c3c5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0x004aa0448e458105b098aea2a764a1d54ab95451bee488869f417b351857c3c5", 3 | "chainTag": 74, 4 | "blockRef": "0x0042249a647f63e7", 5 | "expiration": 720, 6 | "clauses": [ 7 | { 8 | "to": "0x00bae5ed35736e4ef17af1be0c6f50e0fb73d685", 9 | "value": "0x38f6ea18e810b6f00", 10 | "data": "0x" 11 | } 12 | ], 13 | "gasPriceCoef": 0, 14 | "gas": 21000, 15 | "origin": "0xb5e883349e68ab59307d1604555ac890fac47128", 16 | "delegator": null, 17 | "nonce": "0x6fa76caac4c18bd", 18 | "dependsOn": null, 19 | "size": 130, 20 | "meta": { 21 | "blockID": "0x0042249bee56223e0ed7a9c7fcfffe8e61b9fd95d29d24843c558ff2c46ea094", 22 | "blockNumber": 4334747, 23 | "blockTimestamp": 1573795570 24 | } 25 | } -------------------------------------------------------------------------------- /mock/ext-api-data/vechain-api_transactions_0x702edd54bd4e13e0012798cc8b2dfa52f7150173945103d203fae26b8e3d2ed7.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0x702edd54bd4e13e0012798cc8b2dfa52f7150173945103d203fae26b8e3d2ed7", 3 | "chainTag": 74, 4 | "blockRef": "0x004313a393a18efb", 5 | "expiration": 720, 6 | "clauses": [ 7 | { 8 | "to": "0x2c7a8d5cce0d5e6a8a31233b7dc3dae9aae4b405", 9 | "value": "0x12b1815d00738000", 10 | "data": "0x" 11 | } 12 | ], 13 | "gasPriceCoef": 0, 14 | "gas": 21000, 15 | "origin": "0xb5e883349e68ab59307d1604555ac890fac47128", 16 | "delegator": null, 17 | "nonce": "0x8cff29df64a414f8", 18 | "dependsOn": null, 19 | "size": 129, 20 | "meta": { 21 | "blockID": "0x004313a4bd4286e821b684cc1749deb3df12fa2a8114435fbd35baa155e82016", 22 | "blockNumber": 4395940, 23 | "blockTimestamp": 1574410670 24 | } 25 | } -------------------------------------------------------------------------------- /mock/ext-api-data/zilliqa-api_addresses_zil1l8ddxvejeam70qang54wnqkgtmlu5mwlgzy64z_txs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "hash": "0xa5d0e0cefd6f114e9659f15016f9f4a303a8367eb373beb6909ee44f7f39834d", 4 | "blockHeight": 459052, 5 | "from": "zil10lx2eurx5hexaca0lshdr75czr025cevqu83uz", 6 | "to": "zil1l8ddxvejeam70qang54wnqkgtmlu5mwlgzy64z", 7 | "value": "1000000000000", 8 | "fee": "1000000000", 9 | "timestamp": 1583384813191, 10 | "signature": "0x00AF9733DF8FAEFA0FCC9BBAD21DB30A4815749C19CBFFA5A3BCE75A199E4C84FB4728CB946A2F043B8FCA338C8EEC26855CD7B88D8925E69F87CADF5D072343", 11 | "direction": "in", 12 | "nonce": 25, 13 | "receiptSuccess": true, 14 | "events": [] 15 | }, 16 | { 17 | "hash": "0xe29a7e17402c0c067af4c285dedc79114fe62f23d39843c15756db4641f1a00d", 18 | "blockHeight": 414452, 19 | "from": "zil1l8ddxvejeam70qang54wnqkgtmlu5mwlgzy64z", 20 | "to": "zil1l8ddxvejeam70qang54wnqkgtmlu5mwlgzy64z", 21 | "value": "10000000000", 22 | "fee": "1000000000", 23 | "timestamp": 1580891803587, 24 | "signature": "0xA5F2C86098A37149CDDD0884009FF1032714DDAC07B2831C87E1FEEB16B8D9B5EB5D310D042003B3AA27A31BE16FD62CD41E5B1F5108475FEB3EB5B8524DA3F4", 25 | "direction": "self", 26 | "nonce": 47, 27 | "receiptSuccess": true, 28 | "events": [] 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /mock/mockserver/mock-healthcheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": true, 3 | "msg": "Mockserver is alive" 4 | } 5 | -------------------------------------------------------------------------------- /mock/mockserver/mockserver_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMatchQueryParams(t *testing.T) { 8 | tests := [][]string{ 9 | { 10 | "a=1&b=20", 11 | "a=1&b=20", 12 | "true", 13 | }, 14 | { 15 | "a=1&b=20", 16 | "a=1&b=20&c=3", 17 | "true", 18 | }, 19 | { 20 | "a=1&b=20", 21 | "b=20&a=1", 22 | "true", 23 | }, 24 | { 25 | "a=1&b=20", 26 | "a=1&b=500", 27 | "false", 28 | }, 29 | { 30 | "a=1&b=20", 31 | "a=123&b=20", 32 | "false", 33 | }, 34 | { 35 | "a=1&b=20", 36 | "a=1", 37 | "false", 38 | }, 39 | { 40 | "a=1&b=20", 41 | "b=20", 42 | "false", 43 | }, 44 | { 45 | "", 46 | "c=500", 47 | "true", 48 | }, 49 | { 50 | "", 51 | "", 52 | "true", 53 | }, 54 | } 55 | for _, tt := range tests { 56 | inputExpected := tt[0] 57 | inputActual := tt[1] 58 | expectedResult := true 59 | if tt[2] == "false" { 60 | expectedResult = false 61 | } 62 | result := matchQueryParams(inputExpected, inputActual) 63 | if result != expectedResult { 64 | t.Errorf("Did not match, inputExpected %v inputActual %v expectedResult %v", inputExpected, inputActual, expectedResult) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/blockatlas/collectible.go: -------------------------------------------------------------------------------- 1 | package blockatlas 2 | 3 | import "fmt" 4 | 5 | func GenCollectibleId(contract, tokenId string) string { 6 | return fmt.Sprintf("%s-%s", contract, tokenId) 7 | } 8 | -------------------------------------------------------------------------------- /pkg/blockatlas/errors.go: -------------------------------------------------------------------------------- 1 | package blockatlas 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrSourceConn signals that the connection to the source API failed 7 | ErrSourceConn = errors.New("connection to servers failed") 8 | 9 | // ErrInvalidAddr signals that the requested address is invalid 10 | ErrInvalidAddr = errors.New("invalid address") 11 | 12 | // ErrNotFound signals that the resource has not been found 13 | ErrNotFound = errors.New("not found") 14 | 15 | // ErrInvalidKey signals that the requested key is invalid 16 | ErrInvalidKey = errors.New("invalid key") 17 | ) 18 | -------------------------------------------------------------------------------- /pkg/blockatlas/models.go: -------------------------------------------------------------------------------- 1 | package blockatlas 2 | 3 | import "encoding/json" 4 | 5 | type ( 6 | ResultsResponse struct { 7 | Results interface{} `json:"docs"` 8 | } 9 | ) 10 | 11 | func MapJsonObject(from interface{}, to interface{}) error { 12 | bytes, err := json.Marshal(from) 13 | if err != nil { 14 | return err 15 | } 16 | return json.Unmarshal(bytes, to) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/blockatlas/platform_test.go: -------------------------------------------------------------------------------- 1 | package blockatlas 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPlatforms_GetPlatformList(t *testing.T) { 9 | var p Platform 10 | tests := []struct { 11 | name string 12 | ps Platforms 13 | want []Platform 14 | }{ 15 | { 16 | "test 1", 17 | Platforms{ 18 | "test1": p, 19 | "test2": p, 20 | "test3": p, 21 | }, 22 | []Platform{p, p, p}, 23 | }, { 24 | "test 2", 25 | Platforms{ 26 | "test1": p, 27 | "test2": p, 28 | }, 29 | []Platform{p, p}, 30 | }, { 31 | "test 3", 32 | Platforms{ 33 | "test1": p, 34 | }, 35 | []Platform{p}, 36 | }, 37 | } 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | if got := tt.ps.GetPlatformList(); !reflect.DeepEqual(got, tt.want) { 41 | t.Errorf("GetPlatformList() = %v, want %v", got, tt.want) 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/blockatlas/staking_test.go: -------------------------------------------------------------------------------- 1 | package blockatlas 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestStakeValidators_ToMap(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | sv StakeValidators 12 | want ValidatorMap 13 | }{ 14 | {"test 1 validator", StakeValidators{{ID: "test1"}}, ValidatorMap{"test1": {ID: "test1"}}}, 15 | {"test 2 validators", StakeValidators{{ID: "test1"}, {ID: "test2"}}, ValidatorMap{"test1": {ID: "test1"}, "test2": {ID: "test2"}}}, 16 | {"test 3 validators", StakeValidators{{ID: "test1"}, {ID: "test2"}, {ID: "test3"}}, ValidatorMap{"test1": {ID: "test1"}, "test2": {ID: "test2"}, "test3": {ID: "test3"}}}, 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | if got := tt.sv.ToMap(); !reflect.DeepEqual(got, tt.want) { 21 | t.Errorf("ToMap() = %v, want %v", got, tt.want) 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/blockatlas/string.go: -------------------------------------------------------------------------------- 1 | package blockatlas 2 | 3 | func GetValidParameter(first, second string) string { 4 | if len(first) > 0 { 5 | return first 6 | } 7 | return second 8 | } 9 | -------------------------------------------------------------------------------- /pkg/blockatlas/string_test.go: -------------------------------------------------------------------------------- 1 | package blockatlas 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetValidParameter(t *testing.T) { 9 | tests := []struct { 10 | first string 11 | second string 12 | result string 13 | }{ 14 | {"trust", "wallet", "trust"}, 15 | {"", "wallet", "wallet"}, 16 | {"trust", "", "trust"}, 17 | } 18 | for i, tt := range tests { 19 | t.Run(fmt.Sprintf("GetValidParameter %d", i), func(t *testing.T) { 20 | s := GetValidParameter(tt.first, tt.second) 21 | if s != tt.result { 22 | t.Errorf("got %q, want %q", s, tt.result) 23 | } 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /platform/aeternity/base.go: -------------------------------------------------------------------------------- 1 | package aeternity 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Aeternity() 21 | } 22 | -------------------------------------------------------------------------------- /platform/aeternity/client.go: -------------------------------------------------------------------------------- 1 | package aeternity 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/trustwallet/golibs/client" 9 | ) 10 | 11 | type Client struct { 12 | client.Request 13 | } 14 | 15 | func (c *Client) GetTxs(address string, limit int) ([]Transaction, error) { 16 | query := url.Values{ 17 | "limit": {strconv.Itoa(limit)}, 18 | } 19 | uri := fmt.Sprintf("middleware/transactions/account/%s", address) 20 | var transactions []Transaction 21 | if err := c.Get(&transactions, uri, query); err != nil { 22 | return nil, err 23 | } 24 | 25 | var result []Transaction 26 | for _, tx := range transactions { 27 | if tx.TxValue.Type == "SpendTx" { 28 | result = append(result, tx) 29 | } 30 | } 31 | return result, nil 32 | } 33 | -------------------------------------------------------------------------------- /platform/aeternity/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "block_hash": "mh_sJqfsWuuhA7vXDJLYFVtpagCSTmfmhzdqKWFR4pU5LK4D8W8T", 3 | "block_height": 113579, 4 | "hash": "th_oJfBC6KZKaKsL4WXTq1ZtFiSE8Wp2PQYEnwyZqtudyHcU3Qg6", 5 | "signatures": [ 6 | "sg_F3Ecfu5g6FcPyHrgZue96hVHthnXW7CbuDUEoKwWqWvbE84xb3ifB57AGTaH1WzDr4x1cnv4biLqTorjq9ZqhzCFVJC5c" 7 | ], 8 | "time": 1563848658206, 9 | "tx": { 10 | "amount": 252550000000000000000, 11 | "fee": 20500000000000, 12 | "nonce": 251291, 13 | "payload": "ba_SGVsbG8sIE1pbmVyISAvWW91cnMgQmVlcG9vbC4vKXcQag==", 14 | "recipient_id": "ak_ZWrS6xGhzxBasKmMbVSACfRioWqPyM5jNqMpBQ5ngP75RS6pS", 15 | "sender_id": "ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv", 16 | "type": "SpendTx", 17 | "version": 1 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /platform/aeternity/model.go: -------------------------------------------------------------------------------- 1 | package aeternity 2 | 3 | import "encoding/json" 4 | 5 | type Transaction struct { 6 | Hash string `json:"hash"` 7 | BlockHeight uint64 `json:"block_height"` 8 | Timestamp int64 `json:"time"` 9 | TxValue Tx `json:"tx"` 10 | } 11 | 12 | type Tx struct { 13 | Sender string `json:"sender_id"` 14 | Recipient string `json:"recipient_id"` 15 | Amount json.Number `json:"amount"` 16 | Fee json.Number `json:"fee"` 17 | Type string `json:"type"` 18 | Payload string `json:"payload"` 19 | Nonce uint64 `json:"nonce"` 20 | } 21 | -------------------------------------------------------------------------------- /platform/aion/base.go: -------------------------------------------------------------------------------- 1 | package aion 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Coins[coin.AION] 21 | } 22 | -------------------------------------------------------------------------------- /platform/aion/client.go: -------------------------------------------------------------------------------- 1 | package aion 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | 7 | "github.com/trustwallet/golibs/client" 8 | ) 9 | 10 | type Client struct { 11 | client.Request 12 | } 13 | 14 | func (c *Client) GetTxsOfAddress(address string, num int) (txPage *TxPage, err error) { 15 | query := url.Values{ 16 | "accountAddress": {address}, 17 | "size": {strconv.Itoa(num)}, 18 | } 19 | err = c.Get(&txPage, "getTransactionsByAddress", query) 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /platform/aion/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "blockHash": "68364cfa1873c42f3c2ef659349ca101c4c691a0385fd1c1677f92a96f7332ca", 3 | "nrgPrice": 10000000000, 4 | "toAddr": "a09b8c4c40bd7a81e969b8f6f291074206196a99948b03c6a469892931a3c258", 5 | "contractAddr": "", 6 | "data": "", 7 | "year": 2019, 8 | "transactionIndex": 7, 9 | "nonce": "170c", 10 | "transactionHash": "af3c2f5087fc3332154dc9d11c27e312f30ff829dbc5436aec8cc4342c7dc384", 11 | "transactionTimestamp": 1554862205533375, 12 | "nrgConsumed": 21000, 13 | "month": 4, 14 | "blockNumber": 2880919, 15 | "blockTimestamp": 1554862228, 16 | "transactionLog": "[]", 17 | "fromAddr": "a07981da70ce919e1db5f051c3c386eb526e6ce8b9e2bfd56e3f3d754b0a17f3", 18 | "day": 10, 19 | "value": 11.903810405853733, 20 | "txError": "" 21 | } 22 | -------------------------------------------------------------------------------- /platform/aion/model.go: -------------------------------------------------------------------------------- 1 | package aion 2 | 3 | import "encoding/json" 4 | 5 | type TxPage struct { 6 | Content []Tx 7 | } 8 | 9 | type Tx struct { 10 | BlockHash string `json:"blockHash"` 11 | ToAddr string `json:"toAddr"` 12 | ContractAddr string `json:"contractAddr"` 13 | TransactionHash string `json:"transactionHash"` 14 | TransactionTimestamp int64 `json:"transactionTimestamp"` 15 | NrgConsumed int `json:"nrgConsumed"` 16 | BlockNumber uint64 `json:"blockNumber"` 17 | BlockTimestamp int64 `json:"blockTimestamp"` 18 | FromAddr string `json:"fromAddr"` 19 | Value json.Number `json:"value"` 20 | } 21 | -------------------------------------------------------------------------------- /platform/algorand/base.go: -------------------------------------------------------------------------------- 1 | package algorand 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/coin" 5 | ) 6 | 7 | type Platform struct { 8 | client Client 9 | } 10 | 11 | func Init(api, apiKey string) *Platform { 12 | return &Platform{ 13 | client: InitClient(api, apiKey), 14 | } 15 | } 16 | 17 | func (p *Platform) Coin() coin.Coin { 18 | return coin.Algorand() 19 | } 20 | -------------------------------------------------------------------------------- /platform/algorand/block.go: -------------------------------------------------------------------------------- 1 | package algorand 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.GetLatestBlock() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | txs, err := p.client.GetTxsInBlock(num) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | return &types.Block{ 16 | Number: num, 17 | Txs: NormalizeTxs(txs), 18 | }, nil 19 | } 20 | -------------------------------------------------------------------------------- /platform/algorand/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactions": [ 3 | { 4 | "close-rewards": 0, 5 | "closing-amount": 0, 6 | "confirmed-round": 2031351, 7 | "fee": 1000, 8 | "first-valid": 2031300, 9 | "genesis-hash": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=", 10 | "genesis-id": "mainnet-v1.0", 11 | "id": "C2LK3CGBPIGERLPFUXE6INSBJGHOXU7YZMEGELWMVSBASFJYOOQQ", 12 | "intra-round-offset": 57, 13 | "last-valid": 2031749, 14 | "note": "6OZ0TFd0HPw=", 15 | "payment-transaction": { 16 | "amount": 1, 17 | "close-amount": 0, 18 | "receiver": "4EZFQABCVQTHQCK3HQBIYGC4NV2VM42FZHEFTVH77ROG4ZGREC6Y7V5T2U" 19 | }, 20 | "receiver-rewards": 3237690, 21 | "round-time": 1569123058, 22 | "sender": "5TSQNIL54GB545B3WLC6OVH653SHAELMHU6MSVNGTUNMOEHAMWG7EC3AA4", 23 | "sender-rewards": 0, 24 | "signature": { 25 | "sig": "J1G/vapWXJJjuFcsUPut9ffHrFnXsg1GRQlLyqhTOC0V78zCw3OIAYgeg6k/xiX5NDLLrgy4aYF1hhsEXGZ2Dg==" 26 | }, 27 | "tx-type": "pay" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /platform/algorand/model.go: -------------------------------------------------------------------------------- 1 | package algorand 2 | 3 | type TransactionType string 4 | 5 | const ( 6 | TransactionTypePay TransactionType = "pay" 7 | ) 8 | 9 | type Account struct { 10 | Amount uint64 `json:"amount"` 11 | Pendingrewards uint64 `json:"pendingrewards"` 12 | Address string `json:"address"` 13 | Round uint64 `json:"round"` 14 | Amountwithoutpendingrewards uint64 `json:"amountwithoutpendingrewards"` 15 | Rewards uint64 `json:"rewards"` 16 | Status string `json:"status"` 17 | } 18 | 19 | type TransactionsResponse struct { 20 | Transactions []Transaction `json:"transactions"` 21 | } 22 | 23 | type BlockResponse struct { 24 | Timestamp uint64 `json:"timestamp"` 25 | Transactions []Transaction `json:"transactions"` 26 | } 27 | 28 | type Transaction struct { 29 | Type TransactionType `json:"tx-type"` 30 | Hash string `json:"id"` 31 | From string `json:"sender"` 32 | Fee uint64 `json:"fee"` 33 | Round uint64 `json:"confirmed-round"` 34 | Payment TransactionPayment `json:"payment-transaction"` 35 | Timestamp uint64 `json:"round-time"` 36 | } 37 | 38 | type TransactionPayment struct { 39 | Receiver string `json:"receiver"` 40 | Amount uint64 `json:"amount"` 41 | } 42 | 43 | type Status struct { 44 | Block string `json:"message"` 45 | } 46 | -------------------------------------------------------------------------------- /platform/algorand/stake.go: -------------------------------------------------------------------------------- 1 | package algorand 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/services/assets" 5 | "strconv" 6 | 7 | "github.com/trustwallet/blockatlas/pkg/blockatlas" 8 | ) 9 | 10 | func (p *Platform) GetActiveValidators() (blockatlas.StakeValidators, error) { 11 | validators, err := assets.GetValidatorsMap(p) 12 | if err != nil { 13 | return nil, err 14 | } 15 | result := make(blockatlas.StakeValidators, 0, len(validators)) 16 | for _, v := range validators { 17 | result = append(result, v) 18 | } 19 | return result, nil 20 | } 21 | 22 | func (p *Platform) GetDetails() blockatlas.StakingDetails { 23 | return blockatlas.StakingDetails{ 24 | Reward: blockatlas.StakingReward{Annual: 6.1}, 25 | MinimumAmount: "0", 26 | LockTime: 0, 27 | Type: blockatlas.DelegationTypeAuto, 28 | } 29 | } 30 | 31 | func (p *Platform) UndelegatedBalance(address string) (string, error) { 32 | acc, err := p.client.GetAccount(address) 33 | if err != nil { 34 | return "0", err 35 | } 36 | return strconv.FormatUint(acc.Amount, 10), nil 37 | } 38 | 39 | func (p *Platform) GetValidators() (blockatlas.ValidatorPage, error) { 40 | return blockatlas.ValidatorPage{}, nil 41 | } 42 | 43 | func (p *Platform) GetDelegations(address string) (blockatlas.DelegationsPage, error) { 44 | return blockatlas.DelegationsPage{}, nil 45 | } 46 | -------------------------------------------------------------------------------- /platform/algorand/transaction.go: -------------------------------------------------------------------------------- 1 | package algorand 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/trustwallet/golibs/coin" 7 | "github.com/trustwallet/golibs/types" 8 | ) 9 | 10 | func (p *Platform) GetTxsByAddress(address string) (types.Txs, error) { 11 | txs, err := p.client.GetTxsOfAddress(address) 12 | if err != nil { 13 | return nil, err 14 | } 15 | return NormalizeTxs(txs), nil 16 | } 17 | 18 | func NormalizeTxs(txs []Transaction) types.Txs { 19 | result := make(types.Txs, 0) 20 | 21 | for _, tx := range txs { 22 | if normalized, ok := Normalize(tx); ok { 23 | result = append(result, normalized) 24 | } 25 | } 26 | 27 | return result 28 | } 29 | 30 | func Normalize(tx Transaction) (result types.Tx, ok bool) { 31 | 32 | if tx.Type != TransactionTypePay { 33 | return result, false 34 | } 35 | 36 | return types.Tx{ 37 | ID: tx.Hash, 38 | Coin: coin.ALGORAND, 39 | From: tx.From, 40 | To: tx.Payment.Receiver, 41 | Fee: types.Amount(strconv.Itoa(int(tx.Fee))), 42 | Date: int64(tx.Timestamp), 43 | Block: tx.Round, 44 | Status: types.StatusCompleted, 45 | Type: types.TxTransfer, 46 | Meta: types.Transfer{ 47 | Value: types.Amount(strconv.Itoa(int(tx.Payment.Amount))), 48 | Symbol: coin.Algorand().Symbol, 49 | Decimals: coin.Algorand().Decimals, 50 | }, 51 | }, true 52 | } 53 | -------------------------------------------------------------------------------- /platform/binance/base.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/platform/binance/staking" 5 | "github.com/trustwallet/golibs/coin" 6 | ) 7 | 8 | type Platform struct { 9 | client Client 10 | stakingClient staking.Client 11 | } 12 | 13 | func Init(api, apiKey, stakingApi string) *Platform { 14 | p := Platform{ 15 | client: InitClient(api, apiKey), 16 | stakingClient: staking.InitClient(stakingApi), 17 | } 18 | return &p 19 | } 20 | 21 | func (p *Platform) Coin() coin.Coin { 22 | return coin.Binance() 23 | } 24 | -------------------------------------------------------------------------------- /platform/binance/block.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | block, err := p.client.FetchLatestBlockNumber() 7 | if err != nil { 8 | return 0, err 9 | } 10 | return block, nil 11 | } 12 | 13 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 14 | transactionInBlockResponse, err := p.client.FetchTransactionsInBlock(num) 15 | if err != nil { 16 | return nil, err 17 | } 18 | block := normalizeBlock(transactionInBlockResponse) 19 | return &block, nil 20 | } 21 | -------------------------------------------------------------------------------- /platform/binance/block_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPlatform_CurrentBlockNumber(t *testing.T) { 12 | server := httptest.NewServer(createMockedAPI()) 13 | defer server.Close() 14 | p := Init(server.URL, "", "") 15 | number, err := p.CurrentBlockNumber() 16 | assert.Nil(t, err) 17 | assert.Equal(t, int64(104867535), number) 18 | } 19 | 20 | func TestPlatform_GetBlockByNumber(t *testing.T) { 21 | server := httptest.NewServer(createMockedAPI()) 22 | defer server.Close() 23 | p := Init(server.URL, "", "") 24 | block, err := p.GetBlockByNumber(104867508) 25 | assert.Nil(t, err) 26 | res, err := json.Marshal(block) 27 | assert.Nil(t, err) 28 | assert.JSONEq(t, wantedBlockNoOrders, string(res)) 29 | 30 | blockMulti, err := p.GetBlockByNumber(105529271) 31 | assert.Nil(t, err) 32 | resMulti, err := json.Marshal(blockMulti) 33 | assert.Nil(t, err) 34 | assert.JSONEq(t, wantedBlockMultiNoOrders, string(resMulti)) 35 | } 36 | -------------------------------------------------------------------------------- /platform/binance/mocks/account_meta_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "account_number": 398176, 3 | "address": "bnb1w7puzjxu05ktc5zvpnzkndt6tyl720nsutzvpg", 4 | "balances": [ 5 | { 6 | "free": "366.87270502", 7 | "frozen": "0.00000000", 8 | "locked": "2226.00000000", 9 | "symbol": "AVA-645" 10 | }, 11 | { 12 | "free": "6.51198688", 13 | "frozen": "0.00000000", 14 | "locked": "0.00000000", 15 | "symbol": "BNB" 16 | }, 17 | { 18 | "free": "850.41375978", 19 | "frozen": "0.00000000", 20 | "locked": "3220.00483000", 21 | "symbol": "BUSD-BD1" 22 | } 23 | ], 24 | "flags": 0, 25 | "public_key": [ 26 | 2, 27 | 241, 28 | 253, 29 | 162, 30 | 68, 31 | 84, 32 | 67, 33 | 180, 34 | 235, 35 | 15, 36 | 238, 37 | 212, 38 | 39, 39 | 75, 40 | 236, 41 | 33, 42 | 202, 43 | 249, 44 | 109, 45 | 68, 46 | 247, 47 | 56, 48 | 104, 49 | 66, 50 | 240, 51 | 219, 52 | 8, 53 | 22, 54 | 245, 55 | 187, 56 | 84, 57 | 46, 58 | 54 59 | ], 60 | "sequence": 1595533 61 | } 62 | -------------------------------------------------------------------------------- /platform/binance/mocks/block_multi_no_orders.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": 105529271, 3 | "txs": [ 4 | { 5 | "id": "CC7C3EF1407373FDA74B005E64683AB5865126DE93A5FAF755FF5CC948992067", 6 | "coin": 714, 7 | "from": "bnb15qced76xere38hmmpe644u5kd8v4lzl9gsex9w", 8 | "to": "bnb15qced76xere38hmmpe644u5kd8v4lzl9gsex9w", 9 | "fee": "60000", 10 | "date": 1596746326, 11 | "block": 105529271, 12 | "status": "completed", 13 | "sequence": 2300, 14 | "type": "transfer", 15 | "memo": "0", 16 | "metadata": { "value": "1", "symbol": "BNB", "decimals": 8 } 17 | }, 18 | { 19 | "id": "CC7C3EF1407373FDA74B005E64683AB5865126DE93A5FAF755FF5CC948992067", 20 | "coin": 714, 21 | "from": "bnb1t38ccns9var4ac4yj2ylmu99r9ecmggr8ye5e5", 22 | "to": "bnb136ns6lfw4zs5hg4n85vdthaad7hq5m4gtkgf23", 23 | "fee": "0", 24 | "date": 1596746326, 25 | "block": 105529271, 26 | "status": "completed", 27 | "sequence": 2300, 28 | "type": "transfer", 29 | "memo": "0", 30 | "metadata": { "value": "39421249", "symbol": "BNB", "decimals": 8 } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /platform/binance/mocks/block_no_orders.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": 104867508, 3 | "txs": [ 4 | { 5 | "id": "9B87D17581F2AC73D2999EDE56535E50D9D4DB75150A92A90122190F77D47755", 6 | "coin": 714, 7 | "from": "bnb1c4czpzvn0ttdcpnv3cy2858l2m9frxdfgk4jr0", 8 | "to": "bnb1g2ukzn702napq3levm54m2z3p2gam7upern9aq", 9 | "fee": "37500", 10 | "date": 1596472337, 11 | "block": 104867508, 12 | "status": "completed", 13 | "sequence": 6, 14 | "type": "transfer", 15 | "memo": "", 16 | "metadata": { "value": "24481570", "symbol": "BNB", "decimals": 8 } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /platform/binance/mocks/node_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "node_info": { 3 | "protocol_version": { "p2p": 7, "block": 10, "app": 0 }, 4 | "id": "46ba46d5b6fcb61b7839881a75b081123297f7cf", 5 | "listen_addr": "10.212.32.84:27146", 6 | "network": "Binance-Chain-Tigris", 7 | "version": "0.32.3", 8 | "channels": "3640202122233038", 9 | "moniker": "Ararat", 10 | "other": { "tx_index": "on", "rpc_address": "tcp://0.0.0.0:27147" } 11 | }, 12 | "sync_info": { 13 | "latest_block_hash": "507BB016F306906569F12883617A4231AB51DAF5FA5004C8F70B17CDF73A8B40", 14 | "latest_app_hash": "A96FA3DB1FAC12D325845FFEE679EC52CB944BE4B343BC016CE4707FA63EE2BE", 15 | "latest_block_height": 104867535, 16 | "latest_block_time": "2020-08-03T16:32:29.834625465Z", 17 | "catching_up": false 18 | }, 19 | "validator_info": { 20 | "address": "B7707D9F593C62E85BB9E1A2366D12A97CD5DFF2", 21 | "pub_key": [ 22 | 113, 23 | 242, 24 | 215, 25 | 184, 26 | 236, 27 | 28, 28 | 139, 29 | 153, 30 | 166, 31 | 83, 32 | 66, 33 | 155, 34 | 1, 35 | 24, 36 | 205, 37 | 32, 38 | 31, 39 | 121, 40 | 79, 41 | 64, 42 | 157, 43 | 15, 44 | 234, 45 | 77, 46 | 101, 47 | 177, 48 | 182, 49 | 98, 50 | 242, 51 | 176, 52 | 0, 53 | 99 54 | ], 55 | "voting_power": 1000000000000 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /platform/binance/mocks/tokens.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Travala.com Token", 4 | "symbol": "AVA", 5 | "decimals": 8, 6 | "token_id": "AVA-645", 7 | "coin": 714, 8 | "type": "BEP2" 9 | }, 10 | { 11 | "name": "Binance Chain Native Token", 12 | "symbol": "BNB", 13 | "decimals": 8, 14 | "token_id": "BNB", 15 | "coin": 714, 16 | "type": "BEP2" 17 | }, 18 | { 19 | "name": "Binance USD", 20 | "symbol": "BUSD", 21 | "decimals": 8, 22 | "token_id": "BUSD-BD1", 23 | "coin": 714, 24 | "type": "BEP2" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /platform/binance/staking/client.go: -------------------------------------------------------------------------------- 1 | package staking 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/trustwallet/golibs/client" 7 | "github.com/trustwallet/golibs/network/middleware" 8 | ) 9 | 10 | type Client struct { 11 | client.Request 12 | } 13 | 14 | func InitClient(url string) Client { 15 | c := Client{client.InitClient(url, middleware.SentryErrorHandler)} 16 | return c 17 | } 18 | 19 | func (c *Client) GetValidators() (validators Validators, err error) { 20 | params := url.Values{ 21 | "limit": {"100"}, 22 | "offset": {"0"}, 23 | } 24 | err = c.Get(&validators, "/v1/staking/chains/bsc/validators", params) 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /platform/binance/staking/model.go: -------------------------------------------------------------------------------- 1 | package staking 2 | 3 | type Validators struct { 4 | Total int `json:"total"` 5 | Validators []Validator `json:"validators"` 6 | } 7 | 8 | type Validator struct { 9 | Validator string `json:"validator"` 10 | Status int `json:"status"` 11 | APR float64 `json:"apr"` 12 | } 13 | -------------------------------------------------------------------------------- /platform/binance/token.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/types" 5 | ) 6 | 7 | func (p *Platform) GetTokenListByAddress(address string) ([]types.Token, error) { 8 | account, err := p.client.FetchAccountMeta(address) 9 | if err != nil || len(account.Balances) == 0 { 10 | return nil, nil 11 | } 12 | tokens, err := p.client.FetchTokens() 13 | if err != nil { 14 | return nil, err 15 | } 16 | return normalizeTokens(account.Balances, tokens), nil 17 | } 18 | 19 | func (p *Platform) GetTokenListIdsByAddress(address string) ([]string, error) { 20 | assets, err := p.GetTokenListByAddress(address) 21 | if err != nil { 22 | return []string{}, err 23 | } 24 | return types.GetAssetsIds(assets), nil 25 | } 26 | -------------------------------------------------------------------------------- /platform/binance/token_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPlatform_GetTokenListByAddress(t *testing.T) { 12 | server := httptest.NewServer(createMockedAPI()) 13 | defer server.Close() 14 | p := Init(server.URL, "", "") 15 | 16 | tokens, err := p.GetTokenListByAddress("bnb1w7puzjxu05ktc5zvpnzkndt6tyl720nsutzvpg") 17 | assert.Nil(t, err) 18 | res, err := json.Marshal(tokens) 19 | assert.Nil(t, err) 20 | assert.JSONEq(t, wantedTokens, string(res)) 21 | 22 | tokens, err = p.GetTokenListByAddress("bnb1w7puzjxu05ktc5zvpnzkndt6tyl720nsutzvpg") 23 | assert.Nil(t, err) 24 | res, err = json.Marshal(tokens) 25 | assert.Nil(t, err) 26 | assert.JSONEq(t, wantedTokens, string(res)) 27 | 28 | tokens, err = p.GetTokenListByAddress("bnb1w7puzjxu05ktc5zvpnzkndt6tyl720nsutzvpg") 29 | assert.Nil(t, err) 30 | res, err = json.Marshal(tokens) 31 | assert.Nil(t, err) 32 | assert.JSONEq(t, wantedTokens, string(res)) 33 | } 34 | -------------------------------------------------------------------------------- /platform/binance/transaction.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/coin" 5 | "github.com/trustwallet/golibs/types" 6 | ) 7 | 8 | func (p *Platform) GetTxsByAddress(address string) (types.Txs, error) { 9 | txsFromClient, err := p.client.FetchTransactionsByAddressAndTokenID(address, coin.Binance().Symbol) 10 | if err != nil { 11 | return nil, err 12 | } 13 | return normalizeTransactions(txsFromClient), nil 14 | } 15 | 16 | func (p *Platform) GetTokenTxsByAddress(address, token string) (types.Txs, error) { 17 | txsFromClient, err := p.client.FetchTransactionsByAddressAndTokenID(address, token) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return normalizeTransactions(txsFromClient), nil 22 | } 23 | -------------------------------------------------------------------------------- /platform/binance/transaction_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPlatform_GetTxsByAddress(t *testing.T) { 12 | server := httptest.NewServer(createMockedAPI()) 13 | defer server.Close() 14 | p := Init(server.URL, "", "") 15 | txs, err := p.GetTxsByAddress("bnb136ns6lfw4zs5hg4n85vdthaad7hq5m4gtkgf23") 16 | assert.Nil(t, err) 17 | res, err := json.Marshal(txs) 18 | assert.Nil(t, err) 19 | assert.JSONEq(t, wantedTxs, string(res)) 20 | } 21 | 22 | func TestPlatform_GetTokenTxsByAddress(t *testing.T) { 23 | server := httptest.NewServer(createMockedAPI()) 24 | defer server.Close() 25 | p := Init(server.URL, "", "") 26 | txs, err := p.GetTokenTxsByAddress("bnb1w7puzjxu05ktc5zvpnzkndt6tyl720nsutzvpg", "AVA-645") 27 | assert.Nil(t, err) 28 | res, err := json.Marshal(txs) 29 | assert.Nil(t, err) 30 | assert.Len(t, res, 2) 31 | } 32 | -------------------------------------------------------------------------------- /platform/bitcoin/base.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/platform/bitcoin/blockbook" 5 | "github.com/trustwallet/golibs/client" 6 | "github.com/trustwallet/golibs/coin" 7 | "github.com/trustwallet/golibs/network/middleware" 8 | ) 9 | 10 | type Platform struct { 11 | client blockbook.Client 12 | CoinIndex uint 13 | } 14 | 15 | func Init(coin uint, api string) *Platform { 16 | return &Platform{ 17 | CoinIndex: coin, 18 | client: blockbook.Client{Request: client.InitClient(api, middleware.SentryErrorHandler)}, 19 | } 20 | } 21 | 22 | func (p *Platform) Coin() coin.Coin { 23 | return coin.Coins[p.CoinIndex] 24 | } 25 | 26 | func (p *Platform) GetAddressesFromXpub(xpub string) ([]string, error) { 27 | tokens, err := p.client.GetAddressesFromXpub(xpub) 28 | addresses := make([]string, 0) 29 | for _, token := range tokens { 30 | addresses = append(addresses, token.Name) 31 | } 32 | return addresses, err 33 | } 34 | -------------------------------------------------------------------------------- /platform/bitcoin/block.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.GetCurrentBlockNumber() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | block, err := p.client.GetAllTransactionsByBlockNumber(num) 11 | if err != nil { 12 | return nil, err 13 | } 14 | var normalized types.Txs 15 | for _, tx := range block { 16 | normalized = append(normalized, normalizeTransaction(tx, p.CoinIndex)) 17 | } 18 | return &types.Block{ 19 | Number: num, 20 | Txs: normalized, 21 | }, nil 22 | 23 | } 24 | -------------------------------------------------------------------------------- /platform/bitcoin/blockbook/block.go: -------------------------------------------------------------------------------- 1 | package blockbook 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/trustwallet/golibs/types" 7 | ) 8 | 9 | const ( 10 | transactionError = "Internal server error: GetTransaction 0x" 11 | ) 12 | 13 | func (c *Client) GetBlockByNumber(num int64, coinIndex uint) (*types.Block, error) { 14 | block, err := c.GetAllTransactionsByBlockNumber(num) 15 | if err != nil { 16 | err2, ok := err.(*ClientError) 17 | if ok && strings.HasPrefix(err2.Error(), transactionError) { 18 | return &types.Block{Number: num, Txs: types.Txs{}}, nil 19 | } 20 | return nil, err 21 | } 22 | txs := make(types.Txs, 0) 23 | for _, srcTx := range block { 24 | txs = append(txs, normalizeTx(&srcTx, coinIndex)) 25 | } 26 | return &types.Block{ 27 | Number: num, 28 | Txs: txs, 29 | }, nil 30 | } 31 | -------------------------------------------------------------------------------- /platform/bitcoin/blockbook/token.go: -------------------------------------------------------------------------------- 1 | package blockbook 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/types" 5 | ) 6 | 7 | func (c *Client) GetTokenList(address string, coinIndex uint) ([]types.Token, error) { 8 | tokens, err := c.GetTokens(address) 9 | if err != nil { 10 | return nil, err 11 | } 12 | return NormalizeTokens(tokens, coinIndex), nil 13 | } 14 | 15 | func NormalizeTokens(tokens []Token, coinIndex uint) []types.Token { 16 | assets := make([]types.Token, 0) 17 | for _, srcToken := range tokens { 18 | if srcToken.Balance == "0" || srcToken.Balance == "" { 19 | continue 20 | } 21 | token := NormalizeToken(&srcToken, coinIndex) 22 | assets = append(assets, token) 23 | } 24 | return assets 25 | } 26 | 27 | func NormalizeToken(srcToken *Token, coinIndex uint) types.Token { 28 | return types.Token{ 29 | Name: srcToken.Name, 30 | Symbol: srcToken.Symbol, 31 | TokenID: srcToken.Contract, 32 | Coin: coinIndex, 33 | Decimals: srcToken.Decimals, 34 | Type: types.GetEthereumTokenTypeByIndex(coinIndex), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /platform/cosmos/base.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | CoinIndex uint 12 | } 13 | 14 | func Init(coin uint, api string) *Platform { 15 | return &Platform{ 16 | CoinIndex: coin, 17 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 18 | } 19 | } 20 | 21 | func (p *Platform) Coin() coin.Coin { 22 | return coin.Coins[p.CoinIndex] 23 | } 24 | -------------------------------------------------------------------------------- /platform/cosmos/block.go: -------------------------------------------------------------------------------- 1 | package cosmos 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 6 | srcTxs, err := p.client.GetBlockByNumber(num) 7 | if err != nil { 8 | return nil, err 9 | } 10 | 11 | txs := p.NormalizeTxs(srcTxs.Txs) 12 | return &types.Block{ 13 | Number: num, 14 | Txs: txs, 15 | }, nil 16 | } 17 | 18 | func (p *Platform) CurrentBlockNumber() (int64, error) { 19 | return p.client.CurrentBlockNumber() 20 | } 21 | -------------------------------------------------------------------------------- /platform/cosmos/mocks/delegation.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "delegation": { 4 | "delegator_address": "cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0", 5 | "validator_address": "cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys", 6 | "shares": "109999.000001746056062372" 7 | } 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /platform/cosmos/mocks/unbonding.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "delegator_address": "cosmos135qla4294zxarqhhgxsx0sw56yssa3z0f78pm0", 4 | "validator_address": "cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys", 5 | "entries": [ 6 | { 7 | "creation_height": "0", 8 | "completion_time": "2020-01-01T06:54:18.441436491Z", 9 | "initial_balance": "109999", 10 | "balance": "109999" 11 | } 12 | ] 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /platform/cosmos/mocks/validator.json: -------------------------------------------------------------------------------- 1 | { 2 | "operator_address": "cosmosvaloper1lktjhnzkpkz3ehrg8psvmwhafg56kfss3q3t8m", 3 | "consensus_pubkey": { 4 | "type": "tendermint/PubKeyEd25519", 5 | "value": "z/Dg9WU/rlIB+LaQVMMHW/a7rvalfIcyz3VdOwfvguc=" 6 | }, 7 | "status": 3, 8 | "tokens": "597103578752", 9 | "delegator_shares": "597103578752.000000000000000000", 10 | "description": { 11 | "moniker": "Umbrella ☔", 12 | "identity": "A530AC4D75991FE2", 13 | "website": "https://umbrellavalidator.com", 14 | "details": "One of the winners of Cosmos Game of Stakes, and HackAtom3." 15 | }, 16 | "unbonding_time": "1970-01-01T00:00:00Z", 17 | "commission": { 18 | "commission_rates": { 19 | "rate": "0.070400000000000000", 20 | "max_rate": "1.000000000000000000", 21 | "max_change_rate": "0.100000000000000000" 22 | }, 23 | "update_time": "2020-09-01T18:40:38.071399488Z" 24 | }, 25 | "min_self_delegation": "1" 26 | } 27 | -------------------------------------------------------------------------------- /platform/elrond/base.go: -------------------------------------------------------------------------------- 1 | package elrond 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | CoinIndex uint 12 | } 13 | 14 | func Init(coin uint, api string) *Platform { 15 | return &Platform{ 16 | CoinIndex: coin, 17 | client: Client{client.InitJSONClient(api, middleware.SentryErrorHandler)}, 18 | } 19 | } 20 | 21 | func (p *Platform) Coin() coin.Coin { 22 | return coin.Coins[p.CoinIndex] 23 | } 24 | -------------------------------------------------------------------------------- /platform/elrond/block.go: -------------------------------------------------------------------------------- 1 | package elrond 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.CurrentBlockNumber() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | return p.client.GetBlockByNumber(num) 11 | } 12 | -------------------------------------------------------------------------------- /platform/elrond/mocks/scr_negative_value.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "a96ed4816f5312d00361cc16037e19e0cffa4765a79a808d54d89db58fd76aa6", 3 | "value": "-2500000000000000000000", 4 | "receiver": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l", 5 | "sender": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l", 6 | "gasPrice": 1000000000, 7 | "signature": "", 8 | "status": "success" 9 | } 10 | -------------------------------------------------------------------------------- /platform/elrond/mocks/tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "30d404cc7a42b0158b95f6adfbf9a517627d60f6c7e497c1442dfdb6460285df", 3 | "nonce": 0, 4 | "round": 35462, 5 | "value": "82516976060558456822", 6 | "receiver": "erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0", 7 | "sender": "4294967295", 8 | "data": "ok", 9 | "signature": "", 10 | "timestamp": 1587715632, 11 | "status": "Success", 12 | "fee": "1000" 13 | } 14 | -------------------------------------------------------------------------------- /platform/elrond/mocks/tx_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "30d404cc7a42b0158b95f6adfbf9a517627d60f6c7e497c1442dfdb6460285df", 3 | "nonce": 1, 4 | "round": 100, 5 | "value": "2000", 6 | "receiver": "erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0", 7 | "sender": "erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0", 8 | "data": "money", 9 | "signature": "", 10 | "timestamp": 1588757256, 11 | "status": "Pending", 12 | "fee": "1500" 13 | } 14 | -------------------------------------------------------------------------------- /platform/elrond/mocks/tx_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "30d404cc7a42b0158b95f6adfbf9a517627d60f6c7e497c1442dfdb6460285df", 3 | "nonce": 19, 4 | "round": 200, 5 | "value": "2", 6 | "receiver": "erd1v0ce6rapup6rwma5sltyv05xhp33u543nex75a7j39vsz9m6squq6mxm7y", 7 | "sender": "erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0", 8 | "data": "test", 9 | "signature": "", 10 | "timestamp": 1588757256, 11 | "status": "Fail", 12 | "fee": "0", 13 | "gasPrice": 5, 14 | "gasLimit": 1000 15 | } 16 | -------------------------------------------------------------------------------- /platform/elrond/mocks/tx_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "30d404cc7a42b0158b95f6adfbf9a517627d60f6c7e497c1442dfdb6460285df", 3 | "nonce": 19, 4 | "round": 200, 5 | "value": "2", 6 | "receiver": "erd1v0ce6rapup6rwma5sltyv05xhp33u543nex75a7j39vsz9m6squq6mxm7y", 7 | "sender": "erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0", 8 | "data": "test", 9 | "signature": "", 10 | "timestamp": 1588757256, 11 | "status": "pending", 12 | "fee": "5000" 13 | } 14 | -------------------------------------------------------------------------------- /platform/elrond/mocks/tx_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "30d404cc7a42b0158b95f6adfbf9a517627d60f6c7e497c1442dfdb6460285df", 3 | "nonce": 19, 4 | "round": 200, 5 | "value": "2", 6 | "receiver": "erd1v0ce6rapup6rwma5sltyv05xhp33u543nex75a7j39vsz9m6squq6mxm7y", 7 | "sender": "erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0", 8 | "data": "test", 9 | "signature": "", 10 | "timestamp": 1588757256, 11 | "status": "success", 12 | "fee": "5000" 13 | } 14 | -------------------------------------------------------------------------------- /platform/elrond/mocks/tx_6.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "30d404cc7a42b0158b95f6adfbf9a517627d60f6c7e497c1442dfdb6460285df", 3 | "nonce": 25, 4 | "value": "2", 5 | "receiver": "erd1v0ce6rapup6rwma5sltyv05xhp33u543nex75a7j39vsz9m6squq6mxm7y", 6 | "sender": "erd10yagg2vme2jns9zqf9xn8kl86fkc6dr063vnuj0mz2kk2jw0qwuqmfmaw0", 7 | "data": "test", 8 | "signature": "", 9 | "status": "success", 10 | "fee": "0", 11 | "gasPrice": 5, 12 | "gasLimit": 1000 13 | } 14 | -------------------------------------------------------------------------------- /platform/ethereum/base.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/platform/bitcoin/blockbook" 5 | "github.com/trustwallet/blockatlas/platform/ethereum/bounce" 6 | "github.com/trustwallet/blockatlas/platform/ethereum/opensea" 7 | "github.com/trustwallet/golibs/client" 8 | "github.com/trustwallet/golibs/coin" 9 | "github.com/trustwallet/golibs/network/middleware" 10 | ) 11 | 12 | type Platform struct { 13 | CoinIndex uint 14 | client EthereumClient 15 | collectible CollectibleClient 16 | } 17 | 18 | func InitWithBlockbook(coinType uint, blockbookApi string) *Platform { 19 | return &Platform{ 20 | CoinIndex: coinType, 21 | client: &blockbook.Client{Request: client.InitClient(blockbookApi, middleware.SentryErrorHandler)}, 22 | } 23 | } 24 | 25 | func InitWithOpenSea(coinType uint, blockbookApi, collectionApi, collectionKey string) *Platform { 26 | platform := InitWithBlockbook(coinType, blockbookApi) 27 | platform.collectible = opensea.InitClient(collectionApi, collectionKey) 28 | return platform 29 | } 30 | 31 | func InitWithBounce(coinType uint, blockbookApi, collectionApi string) *Platform { 32 | platform := InitWithBlockbook(coinType, blockbookApi) 33 | platform.collectible = bounce.InitClient(collectionApi) 34 | return platform 35 | } 36 | 37 | func (p *Platform) Coin() coin.Coin { 38 | return coin.Coins[p.CoinIndex] 39 | } 40 | -------------------------------------------------------------------------------- /platform/ethereum/block.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.GetCurrentBlockNumber() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | return p.client.GetBlockByNumber(num, p.CoinIndex) 11 | } 12 | -------------------------------------------------------------------------------- /platform/ethereum/bounce/client_test.go: -------------------------------------------------------------------------------- 1 | package bounce 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_normalizeUrl(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | uri string 11 | want string 12 | }{ 13 | { 14 | name: "Test pancake bunny token uri", 15 | uri: "ipfs://QmYu9WwPNKNSZQiTCDfRk7aCR472GURavR9M1qosDmqpev/swapsies.json", 16 | want: "https://ipfs.io/ipfs/QmYu9WwPNKNSZQiTCDfRk7aCR472GURavR9M1qosDmqpev/swapsies.json", 17 | }, 18 | { 19 | name: "Test url with ipfs prefix", 20 | uri: "ipfs://ipfs/QmS3hmJqpHpvnCocqv9FTZbcSGDnvuFv4qWY3qnwkMpB9x", 21 | want: "https://ipfs.io/ipfs/QmS3hmJqpHpvnCocqv9FTZbcSGDnvuFv4qWY3qnwkMpB9x", 22 | }, 23 | } 24 | for _, tt := range tests { 25 | t.Run(tt.name, func(t *testing.T) { 26 | if got := normalizeUrl(tt.uri); got != tt.want { 27 | t.Errorf("normalizeUrl() = %v, want %v", got, tt.want) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /platform/ethereum/bounce/mocks/artwork_nft.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 63039, 3 | "contract_addr": "0x5Bc94e9347F3b9Be8415bDfd24af16666704E44f", 4 | "contract_name": "", 5 | "token_type": "721", 6 | "token_id": "450", 7 | "owner_addr": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1", 8 | "balance": "1", 9 | "token_uri": "https://www.bakeryswap.org/api/v1/artworks/fb4576253e3d45cebf0ac4c8df8f1743", 10 | "name": null, 11 | "description": null, 12 | "image": null, 13 | "metadata": { 14 | "description": "Animal Series", 15 | "image": "https://d3ggs2vjn5heyw.cloudfront.net/static/nfts/artworks/d9dc679ec0614eb78b479aed21694305.jpg", 16 | "name": "Hungry", 17 | "properties": { 18 | "artist": "srnArt", 19 | "public_profile_link": "https://twitter.com/srn_art?s=09" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /platform/ethereum/bounce/mocks/pancake_bunny.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 48963, 3 | "contract_addr": "0xDf7952B35f24aCF7fC0487D01c8d5690a60DBa07", 4 | "contract_name": "Pancake Bunnies", 5 | "token_type": "721", 6 | "token_id": "409", 7 | "owner_addr": "0x7d8bf18c7ce84b3e175b339c4ca93aed1dd166f1", 8 | "balance": "1", 9 | "token_uri": "ipfs://QmYu9WwPNKNSZQiTCDfRk7aCR472GURavR9M1qosDmqpev/swapsies.json", 10 | "name": null, 11 | "description": null, 12 | "image": null, 13 | "metadata": { 14 | "attributes": { 15 | "bunnyId": "0" 16 | }, 17 | "description": "These bunnies love nothing more than swapping pancakes. Especially on BSC.", 18 | "image": "ipfs://QmXdHqg3nywpNJWDevJQPtkz93vpfoHcZWQovFz2nmtPf5/swapsies.png", 19 | "name": "Swapsies" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /platform/ethereum/bounce/model.go: -------------------------------------------------------------------------------- 1 | package bounce 2 | 3 | type Response struct { 4 | Code int `json:"code"` 5 | Msg string `json:"msg"` 6 | } 7 | 8 | type Collectible struct { 9 | ContractAddr string `json:"contract_addr"` 10 | ContractName string `json:"contract_name,omitempty"` 11 | TokenID string `json:"token_id"` 12 | OwnerAddr string `json:"owner_addr"` 13 | TokenURI string `json:"token_uri"` 14 | } 15 | 16 | type CollectibleList struct { 17 | Collectibles []Collectible `json:"tokens"` 18 | } 19 | 20 | type CollectibleResponse struct { 21 | Response 22 | Data CollectibleList `json:"data"` 23 | } 24 | 25 | type Collection struct { 26 | ContractAddr string `json:"contract_addr"` 27 | ContractName string `json:"contract_name,omitempty"` 28 | TokenType string `json:"token_type"` 29 | TokenID string `json:"token_id"` 30 | OwnerAddr string `json:"owner_addr"` 31 | Balance string `json:"balance"` 32 | TokenURI string `json:"token_uri"` 33 | } 34 | 35 | type CollectionList struct { 36 | Collections []Collection `json:"nfts721"` 37 | } 38 | 39 | type CollectionResponse struct { 40 | Response 41 | Data CollectionList `json:"data"` 42 | } 43 | 44 | type CollectionInfo struct { 45 | Name string `json:"name"` 46 | Description string `json:"description"` 47 | Image string `json:"image"` 48 | } 49 | -------------------------------------------------------------------------------- /platform/ethereum/client.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | type EthereumClient interface { 6 | GetTransactions(address string, coinIndex uint) (types.Txs, error) 7 | GetTokenTxs(address, token string, coinIndex uint) (types.Txs, error) 8 | GetTokenList(address string, coinIndex uint) ([]types.Token, error) 9 | GetCurrentBlockNumber() (int64, error) 10 | GetBlockByNumber(num int64, coinIndex uint) (*types.Block, error) 11 | } 12 | 13 | type CollectibleClient interface { 14 | GetCollections(owner string, coinIndex uint) (types.CollectionPage, error) 15 | GetCollectibles(owner, collectionID string, coinIndex uint) (types.CollectiblePage, error) 16 | } 17 | -------------------------------------------------------------------------------- /platform/ethereum/collection.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/types" 5 | ) 6 | 7 | func (p *Platform) GetCollections(owner string) (types.CollectionPage, error) { 8 | return p.collectible.GetCollections(owner, p.CoinIndex) 9 | } 10 | 11 | func (p *Platform) GetCollectibles(owner, collectionID string) (types.CollectiblePage, error) { 12 | return p.collectible.GetCollectibles(owner, collectionID, p.CoinIndex) 13 | } 14 | -------------------------------------------------------------------------------- /platform/ethereum/opensea/client.go: -------------------------------------------------------------------------------- 1 | package opensea 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | 7 | "github.com/trustwallet/golibs/client" 8 | "github.com/trustwallet/golibs/network/middleware" 9 | ) 10 | 11 | type Client struct { 12 | client.Request 13 | } 14 | 15 | func InitClient(api string, apiKey string) *Client { 16 | c := Client{client.InitClient(api, middleware.SentryErrorHandler)} 17 | c.Headers["X-API-KEY"] = apiKey 18 | return &c 19 | } 20 | 21 | func (c Client) GetCollectionsByOwner(owner string) (page []Collection, err error) { 22 | query := url.Values{ 23 | "asset_owner": {owner}, 24 | "limit": {"1000"}, 25 | } 26 | err = c.Get(&page, "api/v1/collections", query) 27 | return 28 | } 29 | 30 | func (c Client) GetCollectiblesByCollectionId(owner string, collectionId string) ([]Collectible, error) { 31 | query := url.Values{ 32 | "owner": {owner}, 33 | "collection": {collectionId}, 34 | "limit": {strconv.Itoa(300)}, 35 | } 36 | 37 | var page CollectiblePage 38 | err := c.Get(&page, "api/v1/assets", query) 39 | return page.Collectibles, err 40 | } 41 | -------------------------------------------------------------------------------- /platform/ethereum/transaction.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/types" 5 | ) 6 | 7 | func (p *Platform) GetTxsByAddress(address string) (types.Txs, error) { 8 | return p.client.GetTransactions(address, p.CoinIndex) 9 | } 10 | 11 | func (p *Platform) GetTokenTxsByAddress(address string, token string) (types.Txs, error) { 12 | return p.client.GetTokenTxs(address, token, p.CoinIndex) 13 | } 14 | 15 | func (p *Platform) GetTokenListByAddress(address string) ([]types.Token, error) { 16 | return p.client.GetTokenList(address, p.CoinIndex) 17 | } 18 | 19 | func (p *Platform) GetTokenListIdsByAddress(address string) ([]string, error) { 20 | assets, err := p.GetTokenListByAddress(address) 21 | if err != nil { 22 | return []string{}, err 23 | } 24 | return types.GetAssetsIds(assets), nil 25 | } 26 | -------------------------------------------------------------------------------- /platform/filecoin/base.go: -------------------------------------------------------------------------------- 1 | package filecoin 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/platform/filecoin/explorer" 5 | "github.com/trustwallet/blockatlas/platform/filecoin/rpc" 6 | "github.com/trustwallet/golibs/client" 7 | "github.com/trustwallet/golibs/coin" 8 | "github.com/trustwallet/golibs/network/middleware" 9 | ) 10 | 11 | type Platform struct { 12 | client rpc.Client 13 | explorer explorer.Client 14 | } 15 | 16 | func Init(api, explorerApi string) *Platform { 17 | p := &Platform{ 18 | client: rpc.Client{Request: client.InitClient(api, middleware.SentryErrorHandler)}, 19 | explorer: explorer.Client{Request: client.InitClient(explorerApi, middleware.SentryErrorHandler)}, 20 | } 21 | return p 22 | } 23 | 24 | func (p *Platform) Coin() coin.Coin { 25 | return coin.Filecoin() 26 | } 27 | -------------------------------------------------------------------------------- /platform/filecoin/explorer/client.go: -------------------------------------------------------------------------------- 1 | package explorer 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/trustwallet/golibs/client" 9 | ) 10 | 11 | type Client struct { 12 | client.Request 13 | } 14 | 15 | func (c Client) GetMessagesByAddress(address string, pageSize int) (res Response, err error) { 16 | path := fmt.Sprintf("api/v1/address/%s/messages", address) 17 | query := url.Values{"pageSize": {strconv.Itoa(pageSize)}} 18 | err = c.Get(&res, path, query) 19 | if err != nil { 20 | return res, err 21 | } 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /platform/filecoin/explorer/models.go: -------------------------------------------------------------------------------- 1 | package explorer 2 | 3 | type Response struct { 4 | TotalCount int `json:"totalCount"` 5 | Messages []Message `json:"messages"` 6 | } 7 | 8 | type Receipt struct { 9 | ExitCode int `json:"exitCode"` 10 | } 11 | 12 | type Message struct { 13 | Cid string `json:"cid"` 14 | Height uint64 `json:"height"` 15 | Timestamp int64 `json:"timestamp"` 16 | From string `json:"from"` 17 | To string `json:"to"` 18 | Nonce uint64 `json:"nonce"` 19 | Value string `json:"value"` 20 | Method string `json:"method"` 21 | Receipt Receipt `json:"receipt"` 22 | } 23 | -------------------------------------------------------------------------------- /platform/filecoin/rpc/client.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import "github.com/trustwallet/golibs/client" 4 | 5 | type Client struct { 6 | client.Request 7 | } 8 | 9 | func (c Client) GetBlockHeight() (ChainHeadResponse, error) { 10 | var result ChainHeadResponse 11 | err := c.RpcCall(&result, "Filecoin.ChainHead", nil) 12 | if err != nil { 13 | return ChainHeadResponse{}, err 14 | } 15 | return result, nil 16 | } 17 | 18 | func (c Client) GetTipSetByHeight(height int64) (ChainHeadResponse, error) { 19 | var result ChainHeadResponse 20 | params := []interface{}{ 21 | height, nil, 22 | } 23 | err := c.RpcCall(&result, "Filecoin.ChainGetTipSetByHeight", params) 24 | if err != nil { 25 | return ChainHeadResponse{}, err 26 | } 27 | return result, nil 28 | } 29 | 30 | func (c Client) GetBlockMessage(cid string) (BlockMessageResponse, error) { 31 | var result BlockMessageResponse 32 | params := []interface{}{ 33 | map[string]interface{}{ 34 | "/": cid, 35 | }, 36 | } 37 | err := c.RpcCall(&result, "Filecoin.ChainGetBlockMessages", params) 38 | if err != nil { 39 | return BlockMessageResponse{}, err 40 | } 41 | return result, nil 42 | } 43 | -------------------------------------------------------------------------------- /platform/filecoin/rpc/models.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | type ChainHeadResponse struct { 4 | Cids []struct { 5 | Cid string `json:"/"` 6 | } `json:"Cids"` 7 | Blocks []struct { 8 | Timestamp int `json:"Timestamp"` 9 | } 10 | Height int `json:"Height"` 11 | } 12 | 13 | type BlockMessageResponse struct { 14 | SecpkMessages []SecpkMessage `json:"SecpkMessages"` 15 | } 16 | 17 | type SecpkMessage struct { 18 | Message Message `json:"Message"` 19 | } 20 | 21 | type Message struct { 22 | Version int `json:"Version"` 23 | To string `json:"To"` 24 | From string `json:"From"` 25 | Nonce int `json:"Nonce"` 26 | Value string `json:"Value"` 27 | GasLimit int `json:"GasLimit"` 28 | GasFeeCap string `json:"GasFeeCap"` 29 | GasPremium string `json:"GasPremium"` 30 | Method int `json:"Method"` 31 | Params interface{} `json:"Params"` 32 | } 33 | 34 | func (c ChainHeadResponse) GetCids() []string { 35 | result := make([]string, 0, len(c.Cids)) 36 | for _, cid := range c.Cids { 37 | result = append(result, cid.Cid) 38 | } 39 | return result 40 | } 41 | 42 | func (c ChainHeadResponse) GetTimestamp() int64 { 43 | if len(c.Blocks) == 0 { 44 | return 0 45 | } 46 | return int64(c.Blocks[0].Timestamp) 47 | } 48 | -------------------------------------------------------------------------------- /platform/fio/base.go: -------------------------------------------------------------------------------- 1 | package fio 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitJSONClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Coins[coin.FIO] 21 | } 22 | -------------------------------------------------------------------------------- /platform/fio/client.go: -------------------------------------------------------------------------------- 1 | package fio 2 | 3 | import "github.com/trustwallet/golibs/client" 4 | 5 | // Client for FIO API 6 | type Client struct { 7 | client.Request 8 | } 9 | 10 | func (c *Client) getTransactions(account string) (actions []Action, error error) { 11 | var res GetActionsResponse 12 | err := c.Post(&res, "v1/history/get_actions", GetActionsRequest{ 13 | AccountName: account, 14 | Pos: -1, // latest 15 | Offset: -100, // 100 before last; use 100 because not all actions are transfers 16 | Sort: "desc", 17 | }) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return res.Actions, nil 22 | } 23 | -------------------------------------------------------------------------------- /platform/harmony/base.go: -------------------------------------------------------------------------------- 1 | package harmony 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | p := &Platform{ 15 | client: Client{client.InitJSONClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | return p 18 | } 19 | 20 | func (p *Platform) Coin() coin.Coin { 21 | return coin.Harmony() 22 | } 23 | -------------------------------------------------------------------------------- /platform/harmony/block.go: -------------------------------------------------------------------------------- 1 | package harmony 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.CurrentBlockNumber() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | srcBlock, err := p.client.GetBlockByNumber(num) 11 | if err != nil { 12 | return nil, err 13 | } 14 | block := p.NormalizeBlock(&srcBlock) 15 | return &block, nil 16 | } 17 | 18 | func (p *Platform) NormalizeBlock(block *BlockInfo) types.Block { 19 | blockNumber, err := hexToInt(block.Number) 20 | if err != nil { 21 | return types.Block{} 22 | } 23 | return types.Block{ 24 | Number: int64(blockNumber), 25 | Txs: NormalizeTxs(block.Transactions), 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /platform/harmony/mocks/delegation.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "validator_address": "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy", 4 | "delegator_address": "one1pf75h0t4am90z8uv3y0dgunfqp4lj8wr3t5rsp", 5 | "amount": 12345678900000000000, 6 | "reward": 15854399877248931866418, 7 | "Undelegations": [] 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /platform/harmony/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "blockHash": "0x0bde901cd3599aa082482777fd0a7fed3f02b7b5a9096b7ea7b2fcb8addaa05d", 3 | "blockNumber": "0x12", 4 | "from": "one103q7qe5t2505lypvltkqtddaef5tzfxwsse4z7", 5 | "gas": "0x5208", 6 | "gasPrice": "0x3b9aca00", 7 | "hash": "0x230798fe22abff459b004675bf827a4089326a296fa4165d0c2ad27688e03e0c", 8 | "input": "0x", 9 | "nonce": "0x0", 10 | "to": "one129r9pj3sk0re76f7zs3qz92rggmdgjhtwge62k", 11 | "transactionIndex": "0x1", 12 | "value": "0x16345785d8a0000", 13 | "shardID": 0, 14 | "toShardID": 0, 15 | "v": "0x27", 16 | "r": "0x57766aa1304e97f8b71a9fa54a61b61ce8ef9ad177fcb337dd81827aad184327", 17 | "s": "0x3b3e5767899e8af5e75d62243a725371f08705b91e2305459e6fd8e8d2646651", 18 | "timestamp": "0x5DF5234E" 19 | } 20 | -------------------------------------------------------------------------------- /platform/harmony/model.go: -------------------------------------------------------------------------------- 1 | package harmony 2 | 3 | type TxResponse struct { 4 | Result TxResult `json:"result"` 5 | } 6 | 7 | type TxResult struct { 8 | Transactions []Transaction `json:"transactions"` 9 | } 10 | 11 | type Transaction struct { 12 | BlockHash string `json:"blockHash"` 13 | BlockNumber string `json:"blockNumber"` 14 | From string `json:"from"` 15 | Gas string `json:"gas"` 16 | GasPrice string `json:"gasPrice"` 17 | Hash string `json:"hash"` 18 | Nonce string `json:"nonce"` 19 | To string `json:"to"` 20 | Value string `json:"value"` 21 | Timestamp string `json:"timestamp"` 22 | } 23 | 24 | type BlockInfo struct { 25 | Hash string `json:"hash"` 26 | Number string `json:"number"` 27 | Transactions []Transaction `json:"transactions"` 28 | } 29 | -------------------------------------------------------------------------------- /platform/icon/base.go: -------------------------------------------------------------------------------- 1 | package icon 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Icon() 21 | } 22 | -------------------------------------------------------------------------------- /platform/icon/client.go: -------------------------------------------------------------------------------- 1 | package icon 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | 7 | "github.com/trustwallet/golibs/client" 8 | "github.com/trustwallet/golibs/types" 9 | ) 10 | 11 | type Client struct { 12 | client.Request 13 | } 14 | 15 | func (c *Client) GetAddressTransactions(address string) ([]Tx, error) { 16 | query := url.Values{ 17 | "address": {address}, 18 | "count": {strconv.Itoa(types.TxPerPage)}, 19 | } 20 | var res Response 21 | err := c.Get(&res, "address/txList", query) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return res.Data, nil 26 | } 27 | -------------------------------------------------------------------------------- /platform/icon/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "txHash": "0x34b8b6ec3a52710c24074f5e298f4a9c67bb61a0a1dde20e695efaeb30ff3754", 3 | "height": 357832, 4 | "createDate": "2019-04-16T06:36:34.000+0000", 5 | "fromAddr": "hx1b8959dd5c57d2c502e22ee0a887d33baec09091", 6 | "toAddr": "cx334db6519871cb2bfd154cec0905ced4ea142de1", 7 | "txType": "1", 8 | "dataType": "call", 9 | "amount": "0.00347", 10 | "fee": "0.0017476", 11 | "state": 1, 12 | "targetContractAddr": "cx334db6519871cb2bfd154cec0905ced4ea142de1", 13 | "id": 730841 14 | } 15 | -------------------------------------------------------------------------------- /platform/icon/model.go: -------------------------------------------------------------------------------- 1 | package icon 2 | 3 | // Response describes the ripple transaction response 4 | type Response struct { 5 | Data []Tx `json:"data"` 6 | ListSize uint64 `json:"listSize"` 7 | TotalSize uint64 `json:"totalSize"` 8 | Result string `json:"result"` 9 | Description string `json:"description"` 10 | } 11 | 12 | // Tx describes the ripple transaction 13 | type Tx struct { 14 | TxHash string `json:"txHash"` 15 | Height uint64 `json:"height"` 16 | CreateDate string `json:"createDate"` 17 | FromAddr string `json:"fromAddr"` 18 | ToAddr string `json:"toAddr"` 19 | TxType string `json:"txType"` 20 | DataType string `json:"dataType"` 21 | Amount string `json:"amount"` 22 | Fee string `json:"fee"` 23 | State uint64 `json:"status"` 24 | } 25 | -------------------------------------------------------------------------------- /platform/icon/transaction.go: -------------------------------------------------------------------------------- 1 | package icon 2 | 3 | import ( 4 | "time" 5 | 6 | log "github.com/sirupsen/logrus" 7 | "github.com/trustwallet/golibs/coin" 8 | "github.com/trustwallet/golibs/numbers" 9 | "github.com/trustwallet/golibs/types" 10 | ) 11 | 12 | func (p *Platform) GetTxsByAddress(address string) (types.Txs, error) { 13 | trxs, err := p.client.GetAddressTransactions(address) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | nTrxs := make(types.Txs, 0) 19 | for _, trx := range trxs { 20 | nTrx, ok := Normalize(&trx) 21 | if !ok { 22 | continue 23 | } 24 | nTrxs = append(nTrxs, nTrx) 25 | } 26 | 27 | return nTrxs, nil 28 | } 29 | 30 | // Normalize converts an Icon transaction into the generic model 31 | func Normalize(trx *Tx) (tx types.Tx, b bool) { 32 | date, err := time.Parse("2006-01-02T15:04:05.999Z0700", trx.CreateDate) 33 | if err != nil { 34 | log.Error(err) 35 | return tx, false 36 | } 37 | fee := numbers.DecimalExp(string(trx.Fee), 18) 38 | value := numbers.DecimalExp(string(trx.Amount), 18) 39 | 40 | return types.Tx{ 41 | ID: trx.TxHash, 42 | Coin: coin.ICON, 43 | From: trx.FromAddr, 44 | To: trx.ToAddr, 45 | Fee: types.Amount(fee), 46 | Status: types.StatusCompleted, 47 | Date: date.Unix(), 48 | Type: types.TxTransfer, 49 | Block: trx.Height, 50 | Meta: types.Transfer{ 51 | Value: types.Amount(value), 52 | Symbol: coin.Icon().Symbol, 53 | Decimals: coin.Icon().Decimals, 54 | }, 55 | }, true 56 | } 57 | -------------------------------------------------------------------------------- /platform/iotex/base.go: -------------------------------------------------------------------------------- 1 | package iotex 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Iotex() 21 | } 22 | -------------------------------------------------------------------------------- /platform/iotex/block.go: -------------------------------------------------------------------------------- 1 | package iotex 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.GetLatestBlock() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | var normalized types.Txs 11 | txs, err := p.client.GetTxsInBlock(num) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | for _, action := range txs { 17 | tx := Normalize(action) 18 | if tx != nil { 19 | normalized = append(normalized, *tx) 20 | } 21 | } 22 | 23 | return &types.Block{ 24 | Number: num, 25 | Txs: normalized, 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /platform/iotex/model.go: -------------------------------------------------------------------------------- 1 | package iotex 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | type Response struct { 6 | ActionInfo []*ActionInfo `json:"actionInfo"` 7 | } 8 | 9 | type AccountInfo struct { 10 | AccountMeta *AccountMeta `json:"accountMeta"` 11 | } 12 | 13 | type AccountMeta struct { 14 | Address string `json:"address"` 15 | Balance string `json:"balance"` 16 | Nonce string `json:"nonce"` 17 | PendingNonce string `json:"pendingNonce"` 18 | NumActions string `json:"numActions"` 19 | } 20 | 21 | type ActionInfo struct { 22 | Action *Action `json:"action"` 23 | ActHash string `json:"actHash"` 24 | BlkHeight string `json:"blkHeight"` 25 | Sender string `json:"sender"` 26 | GasFee string `json:"gasFee"` 27 | Timestamp string `json:"timestamp"` 28 | } 29 | 30 | type Action struct { 31 | Core *ActionCore `json:"core"` 32 | } 33 | 34 | type ActionCore struct { 35 | Nonce string `json:"nonce"` 36 | Transfer *Transfer `json:"transfer"` 37 | } 38 | 39 | type Transfer struct { 40 | Amount types.Amount `json:"amount"` 41 | Recipient string `json:"recipient"` 42 | } 43 | 44 | type ChainMeta struct { 45 | Height string `json:"height"` 46 | } 47 | -------------------------------------------------------------------------------- /platform/iotex/stake.go: -------------------------------------------------------------------------------- 1 | package iotex 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/pkg/blockatlas" 5 | "github.com/trustwallet/blockatlas/services/assets" 6 | "github.com/trustwallet/golibs/types" 7 | ) 8 | 9 | func (p *Platform) GetActiveValidators() (blockatlas.StakeValidators, error) { 10 | validators, err := assets.GetValidatorsMap(p) 11 | if err != nil { 12 | return nil, err 13 | } 14 | result := make(blockatlas.StakeValidators, 0, len(validators)) 15 | for _, v := range validators { 16 | result = append(result, v) 17 | } 18 | return result, nil 19 | } 20 | 21 | func (p *Platform) GetValidators() (blockatlas.ValidatorPage, error) { 22 | return p.client.GetValidators() 23 | } 24 | 25 | func (p *Platform) GetDelegations(address string) (blockatlas.DelegationsPage, error) { 26 | return p.client.GetDelegations(address) 27 | } 28 | 29 | func (p *Platform) GetDetails() blockatlas.StakingDetails { 30 | return blockatlas.StakingDetails{ 31 | Reward: blockatlas.StakingReward{Annual: 0}, 32 | MinimumAmount: types.Amount("100000000000000000000"), 33 | LockTime: 259200, 34 | Type: blockatlas.DelegationTypeDelegate, 35 | } 36 | } 37 | 38 | func (p *Platform) UndelegatedBalance(address string) (string, error) { 39 | account, err := p.client.GetAccount(address) 40 | if err != nil { 41 | return "0", err 42 | } 43 | 44 | return account.AccountMeta.Balance, nil 45 | } 46 | -------------------------------------------------------------------------------- /platform/kava/base.go: -------------------------------------------------------------------------------- 1 | package kava 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | CoinIndex uint 12 | } 13 | 14 | func Init(coin uint, api string) *Platform { 15 | return &Platform{ 16 | CoinIndex: coin, 17 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 18 | } 19 | } 20 | 21 | func (p *Platform) Coin() coin.Coin { 22 | return coin.Coins[p.CoinIndex] 23 | } 24 | -------------------------------------------------------------------------------- /platform/kava/block.go: -------------------------------------------------------------------------------- 1 | package kava 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 6 | srcTxs, err := p.client.GetBlockByNumber(num) 7 | if err != nil { 8 | return nil, err 9 | } 10 | 11 | txs := p.NormalizeTxs(srcTxs.Txs) 12 | return &types.Block{ 13 | Number: num, 14 | Txs: txs, 15 | }, nil 16 | } 17 | 18 | func (p *Platform) CurrentBlockNumber() (int64, error) { 19 | return p.client.CurrentBlockNumber() 20 | } 21 | -------------------------------------------------------------------------------- /platform/nano/base.go: -------------------------------------------------------------------------------- 1 | package nano 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | p := &Platform{ 15 | client: Client{client.InitJSONClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | return p 18 | } 19 | 20 | func (p *Platform) Coin() coin.Coin { 21 | return coin.Coins[coin.NANO] 22 | } 23 | -------------------------------------------------------------------------------- /platform/nano/client.go: -------------------------------------------------------------------------------- 1 | package nano 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/trustwallet/golibs/client" 7 | "github.com/trustwallet/golibs/types" 8 | ) 9 | 10 | type Client struct { 11 | client.Request 12 | } 13 | 14 | func (c *Client) GetAccountHistory(address string) (history AccountHistory, err error) { 15 | count := strconv.Itoa(types.TxPerPage) 16 | err = c.Post(&history, "", AccountHistoryRequest{Action: "account_history", Account: address, Count: count}) 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /platform/nano/model.go: -------------------------------------------------------------------------------- 1 | package nano 2 | 3 | const ( 4 | BlockTypeSend BlockType = "send" 5 | BlockTypeReceive BlockType = "receive" 6 | ) 7 | 8 | type BlockType string 9 | 10 | type AccountHistoryRequest struct { 11 | Action string `json:"action"` 12 | Account string `json:"account"` 13 | Count string `json:"count"` 14 | } 15 | 16 | type AccountHistory struct { 17 | Account string `json:"account"` 18 | History interface{} // NANO RPC returns string for address with 0 transactions 19 | } 20 | 21 | type Transaction struct { 22 | Type BlockType `json:"type"` 23 | Account string `json:"account"` 24 | Amount string `json:"amount"` 25 | LocalTimestamp string `json:"local_timestamp"` 26 | Height string `json:"height"` 27 | Hash string `json:"hash"` 28 | } 29 | -------------------------------------------------------------------------------- /platform/near/base.go: -------------------------------------------------------------------------------- 1 | package near 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | p := &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | return p 18 | } 19 | 20 | func (p *Platform) Coin() coin.Coin { 21 | return coin.Coins[coin.NEAR] 22 | } 23 | -------------------------------------------------------------------------------- /platform/near/client.go: -------------------------------------------------------------------------------- 1 | package near 2 | 3 | import "github.com/trustwallet/golibs/client" 4 | 5 | type Client struct { 6 | client.Request 7 | } 8 | -------------------------------------------------------------------------------- /platform/near/transaction.go: -------------------------------------------------------------------------------- 1 | package near 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) GetTxsByAddress(address string) (types.Txs, error) { 6 | normalized := make(types.Txs, 0) 7 | return normalized, nil 8 | } 9 | -------------------------------------------------------------------------------- /platform/nebulas/base.go: -------------------------------------------------------------------------------- 1 | package nebulas 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Nebulas() 21 | } 22 | -------------------------------------------------------------------------------- /platform/nebulas/client.go: -------------------------------------------------------------------------------- 1 | package nebulas 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | 7 | "github.com/trustwallet/golibs/client" 8 | ) 9 | 10 | const TxTypeBinary = "binary" 11 | 12 | type Client struct { 13 | client.Request 14 | } 15 | 16 | func (c *Client) GetTxs(address string, page int) ([]Transaction, error) { 17 | values := url.Values{ 18 | "a": {address}, 19 | "p": {strconv.Itoa(page)}, 20 | } 21 | 22 | return c.GetTransactions(values) 23 | } 24 | 25 | func (c *Client) GetLatestBlock() (int64, error) { 26 | values := url.Values{ 27 | "type": {"newblock"}, 28 | } 29 | var response NewBlockResponse 30 | 31 | err := c.Get(&response, "block", values) 32 | if err != nil || len(response.Data) == 0 { 33 | return 0, err 34 | } 35 | 36 | return response.Data[0].Height, nil 37 | } 38 | 39 | func (c *Client) GetBlockByNumber(num int64) ([]Transaction, error) { 40 | values := url.Values{ 41 | "block": {strconv.Itoa(int(num))}, 42 | } 43 | return c.GetTransactions(values) 44 | } 45 | 46 | func (c *Client) GetTransactions(values url.Values) ([]Transaction, error) { 47 | var response Response 48 | err := c.Get(&response, "tx", values) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | result := make([]Transaction, 0) 54 | for _, tx := range response.Data.Transactions { 55 | if tx.Type == TxTypeBinary { 56 | result = append(result, tx) 57 | } 58 | } 59 | 60 | return result, nil 61 | } 62 | -------------------------------------------------------------------------------- /platform/nebulas/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "96bd280d60447b7dbcdb3fa76a99856e0422a76304e9d01d0c87e1dfceb6d952", 3 | "block": { 4 | "height": 2848548 5 | }, 6 | "from": { 7 | "hash": "n1Yv9xJJcH4UjoJPVDGdUCL2CxK29asFuyV" 8 | }, 9 | "to": { 10 | "hash": "n1TFrmLUDTe5ggQaWJiXHSqNSRzKYdaV6hQ" 11 | }, 12 | "value": "500000000000000000", 13 | "nonce": 7, 14 | "status": 1, 15 | "timestamp": 1565213205000, 16 | "type": "binary", 17 | "currentTimestamp": 1565361175536, 18 | "txFee": "400000000000000" 19 | } 20 | -------------------------------------------------------------------------------- /platform/nebulas/model.go: -------------------------------------------------------------------------------- 1 | package nebulas 2 | 3 | import "encoding/json" 4 | 5 | type Response struct { 6 | Data ResponseData `json:"data"` 7 | } 8 | 9 | type NewBlockResponse struct { 10 | Data []NewBlock `json:"data"` 11 | } 12 | 13 | type ResponseData struct { 14 | Transactions []Transaction `json:"txnList"` 15 | } 16 | 17 | type Transaction struct { 18 | Hash string `json:"hash"` 19 | Type string `json:"type"` 20 | Value json.Number `json:"value"` 21 | TxFee string `json:"txFee"` 22 | Nonce uint64 `json:"nonce"` 23 | Block Block `json:"block"` 24 | From Address `json:"from"` 25 | To Address `json:"to"` 26 | Timestamp int64 `json:"timestamp"` 27 | Status int32 `json:"status"` 28 | } 29 | 30 | type Block struct { 31 | Height uint64 `json:"height"` 32 | } 33 | 34 | type NewBlock struct { 35 | Height int64 `json:"height"` 36 | } 37 | 38 | type Address struct { 39 | Hash string `json:"hash"` 40 | } 41 | -------------------------------------------------------------------------------- /platform/nebulas/transaction_test.go: -------------------------------------------------------------------------------- 1 | package nebulas 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/trustwallet/golibs/coin" 8 | "github.com/trustwallet/golibs/mock" 9 | "github.com/trustwallet/golibs/types" 10 | ) 11 | 12 | func TestNormalizeTx(t *testing.T) { 13 | type args struct { 14 | filename string 15 | } 16 | tests := []struct { 17 | name string 18 | args args 19 | wantTx types.Tx 20 | }{ 21 | { 22 | name: "Test normalize transaction", 23 | args: args{ 24 | filename: "transfer.json", 25 | }, 26 | wantTx: types.Tx{ 27 | ID: "96bd280d60447b7dbcdb3fa76a99856e0422a76304e9d01d0c87e1dfceb6d952", 28 | Coin: coin.NEBULAS, 29 | From: "n1Yv9xJJcH4UjoJPVDGdUCL2CxK29asFuyV", 30 | To: "n1TFrmLUDTe5ggQaWJiXHSqNSRzKYdaV6hQ", 31 | Fee: "400000000000000", 32 | Sequence: 7, 33 | Date: 1565213205, 34 | Block: 2848548, 35 | Status: types.StatusCompleted, 36 | Meta: types.Transfer{ 37 | Value: "500000000000000000", 38 | Symbol: "NAS", 39 | Decimals: 18, 40 | }, 41 | }, 42 | }, 43 | } 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | var srcTx Transaction 47 | _ = mock.JsonModelFromFilePath("mocks/"+tt.args.filename, &srcTx) 48 | gotTx := NormalizeTx(srcTx) 49 | if !reflect.DeepEqual(gotTx, tt.wantTx) { 50 | t.Errorf("NormalizeTx() gotTx = %v, want %v", gotTx, tt.wantTx) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /platform/nimiq/base.go: -------------------------------------------------------------------------------- 1 | package nimiq 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitJSONClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Nimiq() 21 | } 22 | -------------------------------------------------------------------------------- /platform/nimiq/block.go: -------------------------------------------------------------------------------- 1 | package nimiq 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.CurrentBlockNumber() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | srcBlock, err := p.client.GetBlockByNumber(num) 11 | if err != nil { 12 | return nil, err 13 | } 14 | block := NormalizeBlock(srcBlock) 15 | return &block, nil 16 | } 17 | 18 | // NormalizeBlock converts a Nimiq block into the generic model 19 | func NormalizeBlock(srcBlock *Block) types.Block { 20 | return types.Block{ 21 | Number: srcBlock.Number, 22 | Txs: NormalizeTxs(srcBlock.Txs), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /platform/nimiq/client.go: -------------------------------------------------------------------------------- 1 | package nimiq 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/trustwallet/golibs/client" 7 | "github.com/trustwallet/golibs/types" 8 | ) 9 | 10 | type Client struct { 11 | client.Request 12 | } 13 | 14 | func (c *Client) GetTxsOfAddress(address string) (tx []Tx, err error) { 15 | err = c.RpcCall(&tx, "getTransactionsByAddress", []string{address, strconv.Itoa(types.TxPerPage)}) 16 | return 17 | } 18 | 19 | func (c *Client) CurrentBlockNumber() (num int64, err error) { 20 | err = c.RpcCall(&num, "blockNumber", []string{}) 21 | return 22 | } 23 | 24 | func (c *Client) GetBlockByNumber(num int64) (b *Block, err error) { 25 | n := strconv.Itoa(int(num)) 26 | err = c.RpcCall(&b, "getBlockByNumber", []string{n, "true"}) 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /platform/nimiq/mocks/pending_tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": null, 3 | "fee": 300, 4 | "flags": 0, 5 | "from": "d48182276127b149a9710e78c436fb4bc1c4dc0b", 6 | "fromAddress": "NQ74 SJ0Q 49T1 4XQL KABH 1RUC 8DPT 9F0U 9P0B", 7 | "hash": "79719d16f3f347cc98c35cd7a9af708cdce97de578b5135c5ae4393fd7920d61", 8 | "to": "0a17218ffdc42385f45329ba4089919236dd2743", 9 | "toAddress": "NQ97 18BJ 33YV QGHQ BV2K 56V4 12CH J8TD S9S3", 10 | "value": 100000 11 | } 12 | -------------------------------------------------------------------------------- /platform/nimiq/mocks/tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "8b219949f4c1dfe9e7a9cdc5dbbc507e40dc16f44a1a5182ed6125c9a6891a50", 3 | "blockHash": "ab36a0909c6ed5761a984ef261d9c3456b7c1aea6a52d531c5bf2518526a32e6", 4 | "blockNumber": 252575, 5 | "timestamp": 1538924505, 6 | "confirmations": 271245, 7 | "transactionIndex": 37, 8 | "from": "4a88aaad038f9b8248865c4b9249efc554960e16", 9 | "fromAddress": "NQ69 9A4A MB83 HXDQ 4J46 BH5R 4JFF QMA9 C3GN", 10 | "to": "ad25610feb43d75307763d3f010822a757027429", 11 | "toAddress": "NQ15 MLJN 23YB 8FBM 61TN 7LYG 2212 LVBG 4V19", 12 | "value": 10000000000000, 13 | "fee": 138, 14 | "data": null, 15 | "flags": 0 16 | } 17 | -------------------------------------------------------------------------------- /platform/nimiq/model.go: -------------------------------------------------------------------------------- 1 | package nimiq 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/trustwallet/golibs/types" 7 | ) 8 | 9 | type Tx struct { 10 | Hash string `json:"hash"` 11 | BlockHash string `json:"blockHash"` 12 | BlockNumber uint64 `json:"blockNumber"` 13 | Timestamp json.Number `json:"timestamp"` 14 | Confirmations int `json:"confirmations"` 15 | TxIndex int `json:"transactionIndex"` 16 | FromAddress string `json:"fromAddress"` 17 | ToAddress string `json:"toAddress"` 18 | Value types.Amount `json:"value"` 19 | Fee types.Amount `json:"fee"` 20 | } 21 | 22 | type Block struct { 23 | Number int64 `json:"number"` 24 | Hash string `json:"hash"` 25 | PoW string `json:"pow"` 26 | ParentHash string `json:"parentHash"` 27 | Nonce uint32 `json:"nonce"` 28 | BodyHash string `json:"bodyHash"` 29 | AccountsHash string `json:"accountsHash"` 30 | MinerHex string `json:"miner"` 31 | Miner string `json:"minerAddress"` 32 | Difficulty string `json:"difficulty"` 33 | ExtraData string `json:"extraData"` 34 | Size int64 `json:"size"` 35 | Timestamp int64 `json:"timestamp"` 36 | Txs []Tx `json:"transactions"` 37 | } 38 | -------------------------------------------------------------------------------- /platform/oasis/base.go: -------------------------------------------------------------------------------- 1 | package oasis 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | p := &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | return p 18 | } 19 | 20 | func (p *Platform) Coin() coin.Coin { 21 | return coin.Oasis() 22 | } 23 | -------------------------------------------------------------------------------- /platform/oasis/block.go: -------------------------------------------------------------------------------- 1 | package oasis 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/types" 5 | ) 6 | 7 | func (p *Platform) CurrentBlockNumber() (int64, error) { 8 | num, err := p.client.GetCurrentBlock() 9 | if err != nil { 10 | return 0, err 11 | } 12 | return num, nil 13 | } 14 | 15 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 16 | srcBlock, err := p.client.GetBlockByNumber(num) 17 | if err == nil { 18 | txs := NormalizeTxs(*srcBlock) 19 | return &types.Block{ 20 | Number: num, 21 | Txs: txs, 22 | }, nil 23 | } 24 | 25 | return nil, err 26 | } 27 | -------------------------------------------------------------------------------- /platform/oasis/client.go: -------------------------------------------------------------------------------- 1 | package oasis 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | ) 6 | 7 | type Client struct { 8 | client.Request 9 | } 10 | 11 | func (c *Client) GetCurrentBlock() (int64, error) { 12 | var blk int64 13 | 14 | err := c.Post(&blk, "block/tip", nil) 15 | if err != nil { 16 | return 0, err 17 | } 18 | 19 | return blk, nil 20 | } 21 | 22 | func (c *Client) GetBlockByNumber(num int64) (*[]Transaction, error) { 23 | var txs []Transaction 24 | 25 | err := c.Post(&txs, "transactions/block", BlockRequest{BlockIdentifier: num}) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return &txs, nil 31 | } 32 | 33 | func (c *Client) GetTrxOfAddress(address string) (*[]Transaction, error) { 34 | var txs []Transaction 35 | 36 | err := c.Post(&txs, "transactions/address", TransactionsByAddressRequest{Address: address}) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return &txs, nil 42 | } 43 | -------------------------------------------------------------------------------- /platform/oasis/mocks/tx_with_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "tx_hash": "6df10a2d114739ee6007fbd2ae0905b2ae81be6fc1d8ff6c5a9f404923263b84", 3 | "from": "oasis1qrmp3lmcuvxhr9dq90mrrxe2yxzwfqw9xcvqujpu", 4 | "to": "oasis1qqnv3peudzvekhulf8v3ht29z4cthkhy7gkxmph5", 5 | "amount": "10", 6 | "fee": "10", 7 | "date": 1605774037000, 8 | "block": 712027, 9 | "success": false, 10 | "error_message": "insufficient balance", 11 | "sequence": 1 12 | } 13 | -------------------------------------------------------------------------------- /platform/oasis/mocks/tx_with_fee.json: -------------------------------------------------------------------------------- 1 | { 2 | "tx_hash": "2b06966eaf27d830cc3c91fe2e38c0d26d38430cf8754be786f66a084ab127d2", 3 | "from": "oasis1qp29h8ykmxet46eqzw0wennrmmy4al3xzv37m3ca", 4 | "to": "oasis1qz9re9hc0k9qxrhvww7x9zrfv8x8jpr4kcr2twr2", 5 | "amount": "1000000000", 6 | "fee": "15000", 7 | "date": 1605717688000, 8 | "block": 702410, 9 | "success": true, 10 | "sequence": 1 11 | } 12 | -------------------------------------------------------------------------------- /platform/oasis/mocks/tx_without_fee.json: -------------------------------------------------------------------------------- 1 | { 2 | "tx_hash": "a49afb8055ef3bbb4fca1e162886ab32b71c5a0d49555793342971237b031972", 3 | "from": "oasis1qpcgnf84hnvvfvzup542rhc8kjyvqf4aqqlj5kqh", 4 | "to": "oasis1qz9re9hc0k9qxrhvww7x9zrfv8x8jpr4kcr2twr2", 5 | "amount": "170000000000", 6 | "fee": "0", 7 | "date": 1610472221000, 8 | "block": 1502238, 9 | "success": true, 10 | "sequence": 5 11 | } 12 | -------------------------------------------------------------------------------- /platform/oasis/model.go: -------------------------------------------------------------------------------- 1 | package oasis 2 | 3 | type Block struct { 4 | Height int64 `json:"height"` 5 | Hash string `json:"hash"` 6 | Timestamp int64 `json:"timestamp"` 7 | } 8 | 9 | type BlockRequest struct { 10 | BlockIdentifier int64 `json:"block_identifier"` 11 | } 12 | 13 | type Transaction struct { 14 | Hash string `json:"tx_hash"` 15 | From string `json:"from"` 16 | To string `json:"to"` 17 | Amount string `json:"amount"` 18 | Fee string `json:"fee"` 19 | Date int64 `json:"date"` 20 | Block uint64 `json:"block"` 21 | Success bool `json:"success"` 22 | ErrorMsg string `json:"error_message,omitempty"` 23 | Sequence uint64 `json:"sequence"` 24 | } 25 | 26 | type TransactionsByAddressRequest struct { 27 | Address string `json:"address"` 28 | } 29 | -------------------------------------------------------------------------------- /platform/oasis/transaction.go: -------------------------------------------------------------------------------- 1 | package oasis 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/coin" 5 | "github.com/trustwallet/golibs/types" 6 | ) 7 | 8 | func (p *Platform) GetTxsByAddress(address string) (types.Txs, error) { 9 | txs, err := p.client.GetTrxOfAddress(address) 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | return NormalizeTxs(*txs), nil 15 | } 16 | 17 | func NormalizeTxs(srcTxs []Transaction) types.Txs { 18 | var txs types.Txs 19 | for _, srcTx := range srcTxs { 20 | tx := NormalizeTx(srcTx) 21 | txs = append(txs, tx) 22 | } 23 | return txs 24 | } 25 | 26 | func NormalizeTx(srcTx Transaction) types.Tx { 27 | symbol := coin.Coins[coin.OASIS].Symbol 28 | decimals := coin.Coins[coin.OASIS].Decimals 29 | 30 | status := types.StatusCompleted 31 | if !srcTx.Success { 32 | status = types.StatusError 33 | } 34 | 35 | nTx := types.Tx{ 36 | ID: srcTx.Hash, 37 | Coin: coin.OASIS, 38 | From: srcTx.From, 39 | To: srcTx.To, 40 | Fee: types.Amount(srcTx.Fee), 41 | Date: srcTx.Date, 42 | Block: srcTx.Block, 43 | Status: status, 44 | Error: srcTx.ErrorMsg, 45 | Sequence: srcTx.Sequence, 46 | Meta: types.Transfer{ 47 | Value: types.Amount(srcTx.Amount), 48 | Symbol: symbol, 49 | Decimals: decimals, 50 | }, 51 | } 52 | 53 | return nTx 54 | } 55 | -------------------------------------------------------------------------------- /platform/ontology/base.go: -------------------------------------------------------------------------------- 1 | package ontology 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Ontology() 21 | } 22 | -------------------------------------------------------------------------------- /platform/ontology/block.go: -------------------------------------------------------------------------------- 1 | package ontology 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/trustwallet/golibs/types" 7 | ) 8 | 9 | func (p *Platform) CurrentBlockNumber() (int64, error) { 10 | block, err := p.client.CurrentBlockNumber() 11 | if err != nil { 12 | return 0, err 13 | } 14 | if len(block.Result.Records) == 0 { 15 | return 0, errors.New("invalid block height result") 16 | } 17 | return block.Result.Records[0].Height, nil 18 | } 19 | 20 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 21 | blockOnt, err := p.client.GetBlockByNumber(num) 22 | if err != nil { 23 | return nil, err 24 | } 25 | txsRaw, err := p.getTxDetails(blockOnt.Result.Txs) 26 | if err != nil { 27 | return nil, err 28 | } 29 | txs := normalizeTxs(txsRaw, AssetAll) 30 | return &types.Block{ 31 | Number: num, 32 | Txs: txs, 33 | }, nil 34 | } 35 | -------------------------------------------------------------------------------- /platform/ontology/mocks/transfer_fee.json: -------------------------------------------------------------------------------- 1 | { 2 | "tx_hash": "92a79aed526f31999e22d9e3912d4125d3f85ec3c63eede4b7dde4a041826095", 3 | "tx_type": 209, 4 | "tx_time": 1578902776, 5 | "block_height": 7571071, 6 | "fee": "0.01", 7 | "block_index": 1, 8 | "confirm_flag": 1, 9 | "transfers": [ 10 | { 11 | "amount": "0.01", 12 | "from_address": "ARFXGXSmgFT2h9EiS4D5fen127Lzi48Eij", 13 | "to_address": "AFmseVrdL9f9oyCzZefL9tG6UbviEH9ugK", 14 | "asset_name": "ong", 15 | "contract_hash": "0200000000000000000000000000000000000000" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /platform/ontology/mocks/transfer_ong.json: -------------------------------------------------------------------------------- 1 | { 2 | "tx_hash": "e5946ba02f56e17c3709db2bc91f43f76ee3a359006586024daa5c4ad8c54e78", 3 | "tx_type": 209, 4 | "tx_time": 1577631515, 5 | "block_height": 7457989, 6 | "fee": "0.01", 7 | "block_index": 1, 8 | "confirm_flag": 1, 9 | "transfers": [ 10 | { 11 | "amount": "14.69", 12 | "from_address": "ASLbwuar3ZTbUbLPnCgjGUw2WHhMfvJJtx", 13 | "to_address": "ARFXGXSmgFT2h9EiS4D5fen127Lzi48Eij", 14 | "asset_name": "ong", 15 | "contract_hash": "0200000000000000000000000000000000000000" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /platform/ontology/mocks/transfer_ont.json: -------------------------------------------------------------------------------- 1 | { 2 | "tx_hash": "ea0e5d8e389cb96760887094194ca359ac998b2f607be470a576861b91e2bf52", 3 | "tx_type": 209, 4 | "tx_time": 1578903628, 5 | "block_height": 7571132, 6 | "fee": "0.01", 7 | "block_index": 1, 8 | "confirm_flag": 1, 9 | "transfers": [ 10 | { 11 | "amount": "5", 12 | "from_address": "ARFXGXSmgFT2h9EiS4D5fen127Lzi48Eij", 13 | "to_address": "ARFXGXSmgFT2h9EiS4D5fen127Lzi48Eij", 14 | "asset_name": "ont", 15 | "contract_hash": "0100000000000000000000000000000000000000" 16 | }, 17 | { 18 | "amount": "0.01", 19 | "from_address": "ARFXGXSmgFT2h9EiS4D5fen127Lzi48Eij", 20 | "to_address": "AFmseVrdL9f9oyCzZefL9tG6UbviEH9ugK", 21 | "asset_name": "ong", 22 | "contract_hash": "0200000000000000000000000000000000000000" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /platform/ontology/mocks/transfer_rewards.json: -------------------------------------------------------------------------------- 1 | { 2 | "tx_hash": "d7554dcdf01f394b9107ff598df6d84e4c3b00ccf1e720b8c09abf085cbe4987", 3 | "tx_type": 209, 4 | "tx_time": 1579699532, 5 | "block_height": 7644328, 6 | "fee": "0.01", 7 | "block_index": 1, 8 | "confirm_flag": 1, 9 | "transfers": [ 10 | { 11 | "amount": "0.03534404", 12 | "from_address": "AFmseVrdL9f9oyCzZefL9tG6UbvhUMqNMV", 13 | "to_address": "ARFXGXSmgFT2h9EiS4D5fen127Lzi48Eij", 14 | "asset_name": "ong", 15 | "contract_hash": "0200000000000000000000000000000000000000" 16 | }, 17 | { 18 | "amount": "0.01", 19 | "from_address": "ARFXGXSmgFT2h9EiS4D5fen127Lzi48Eij", 20 | "to_address": "AFmseVrdL9f9oyCzZefL9tG6UbviEH9ugK", 21 | "asset_name": "ong", 22 | "contract_hash": "0200000000000000000000000000000000000000" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /platform/polkadot/base.go: -------------------------------------------------------------------------------- 1 | package polkadot 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | CoinIndex uint 12 | } 13 | 14 | func Init(coin uint, api string) *Platform { 15 | return &Platform{ 16 | CoinIndex: coin, 17 | client: Client{client.InitJSONClient(api, middleware.SentryErrorHandler)}, 18 | } 19 | } 20 | 21 | func (p *Platform) Coin() coin.Coin { 22 | return coin.Coins[p.CoinIndex] 23 | } 24 | -------------------------------------------------------------------------------- /platform/polkadot/block.go: -------------------------------------------------------------------------------- 1 | package polkadot 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.GetCurrentBlock() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | if srcBlock, err := p.client.GetBlockByNumber(num); err == nil { 11 | txs := p.NormalizeExtrinsics(srcBlock) 12 | return &types.Block{ 13 | Number: num, 14 | Txs: txs, 15 | }, nil 16 | } else { 17 | return nil, err 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /platform/polkadot/client.go: -------------------------------------------------------------------------------- 1 | package polkadot 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/trustwallet/golibs/client" 7 | "github.com/trustwallet/golibs/types" 8 | ) 9 | 10 | type Client struct { 11 | client.Request 12 | } 13 | 14 | func (c *Client) GetTransfersOfAddress(address string) ([]Transfer, error) { 15 | var res SubscanResponse 16 | err := c.Post(&res, "scan/transfers", TransfersRequest{Address: address, Row: types.TxPerPage}) 17 | if err != nil { 18 | return nil, err 19 | } 20 | return res.Data.Transfers, nil 21 | } 22 | 23 | func (c *Client) GetExtrinsicsOfAddress(address string) ([]Extrinsic, error) { 24 | var res SubscanResponse 25 | err := c.Post(&res, "scan/extrinsics", TransfersRequest{Address: address, Row: types.TxPerPage}) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return res.Data.Extrinsics, nil 30 | } 31 | 32 | func (c *Client) GetCurrentBlock() (int64, error) { 33 | var res SubscanResponse 34 | err := c.Post(&res, "scan/metadata", nil) 35 | if err != nil { 36 | return 0, err 37 | } 38 | block, err := strconv.ParseInt(res.Data.BlockNumber, 10, 64) 39 | if err != nil { 40 | return 0, err 41 | } 42 | return block, nil 43 | } 44 | 45 | func (c *Client) GetBlockByNumber(number int64) ([]Extrinsic, error) { 46 | var res SubscanResponse 47 | err := c.Post(&res, "scan/block", BlockRequest{BlockNumber: number}) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return res.Data.Extrinsics, nil 52 | } 53 | -------------------------------------------------------------------------------- /platform/polkadot/crypto.go: -------------------------------------------------------------------------------- 1 | package polkadot 2 | 3 | import ( 4 | "github.com/btcsuite/btcutil/base58" 5 | "golang.org/x/crypto/blake2b" 6 | ) 7 | 8 | var ss58Prefix = []byte("SS58PRE") 9 | 10 | // PublicKeyToAddress returns an ss58 address string given public key bytes 11 | // see: https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58) 12 | func PublicKeyToAddress(bytes []byte, network byte) string { 13 | encode := []byte{network} 14 | encode = append(encode, bytes...) 15 | hasher, err := blake2b.New(64, nil) 16 | if err != nil { 17 | return "" 18 | } 19 | _, err = hasher.Write(append(ss58Prefix, encode...)) 20 | if err != nil { 21 | return "" 22 | } 23 | checksum := hasher.Sum(nil) 24 | encode = append(encode, checksum[:2]...) 25 | return base58.Encode(encode) 26 | } 27 | -------------------------------------------------------------------------------- /platform/polkadot/crypto_test.go: -------------------------------------------------------------------------------- 1 | package polkadot 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | ) 7 | 8 | func TestPublicKeyToAddress(t *testing.T) { 9 | type args struct { 10 | str string 11 | network byte 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want string 17 | }{ 18 | { 19 | name: "PublicKey bytes to Kusama address", 20 | args: args{ 21 | str: "e8e1b8de72651640e302b62dad1f643ec8b65a3647a7409b2896634db599ed60", 22 | network: NetworkByteMap["KSM"], 23 | }, 24 | want: "HqfgRXDgCQcV8KAuTAPGuA1r91iEzinmmNBPkR9kiKhifJq", 25 | }, 26 | { 27 | name: "PublicKey bytes to Polkadot address", 28 | args: args{ 29 | str: "53d82211c4aadb8c67e1930caef2058a93bc29d7af86bf587fba4aa3b1515037", 30 | network: NetworkByteMap["DOT"], 31 | }, 32 | want: "12twBQPiG5yVSf3jQSBkTAKBKqCShQ5fm33KQhH3Hf6VDoKW", 33 | }, 34 | } 35 | for _, tt := range tests { 36 | t.Run(tt.name, func(t *testing.T) { 37 | bytes, _ := hex.DecodeString(tt.args.str) 38 | if got := PublicKeyToAddress(bytes, tt.args.network); got != tt.want { 39 | t.Errorf("PublicKeyToAddress() = %v, want %v", got, tt.want) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /platform/ripple/base.go: -------------------------------------------------------------------------------- 1 | package ripple 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Ripple() 21 | } 22 | -------------------------------------------------------------------------------- /platform/ripple/block.go: -------------------------------------------------------------------------------- 1 | package ripple 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.GetCurrentBlock() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | if srcBlock, err := p.client.GetBlockByNumber(num); err == nil { 11 | txs := NormalizeTxs(srcBlock) 12 | return &types.Block{ 13 | Number: num, 14 | Txs: txs, 15 | }, nil 16 | } else { 17 | return nil, err 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /platform/ripple/mocks/payment.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "40279A3DE51148BD41409DADF29DE8DCCD50F5AEE30840827B2C4C81C4E36505", 3 | "ledger_index": 34698103, 4 | "date": "2017-12-01T22:45:30+00:00", 5 | "tx": { 6 | "TransactionType": "Payment", 7 | "Flags": 2147483648, 8 | "Sequence": 21, 9 | "LastLedgerSequence": 34698105, 10 | "DestinationTag": 2500, 11 | "Amount": "100000000", 12 | "Fee": "3115", 13 | "SigningPubKey": "03807050F9E271B2E49B0FF658362EF37DBFDD31435E610B6E11C52879DF8A9907", 14 | "TxnSignature": "3045022100D14057AA2A868F54FC7CA2E44C8310D9A944446580EAA45936A75CFFDD00425602205CCBFACB55AB0F5B02659F1EBE619FC04DE75B0227C8EB148DC6D08CABBAB072", 15 | "Account": "rGSxFjoqmWz54PycrgQBQ5dB6e7TUpMxzq", 16 | "Destination": "rMQ98K56yXJbDGv49ZSmW51sLn94Xe1mu1", 17 | "Memos": [ 18 | { 19 | "Memo": { 20 | "MemoType": "636C69656E74", 21 | "MemoFormat": "7274312E342E332D31332D6735383261336135" 22 | } 23 | } 24 | ] 25 | }, 26 | "meta": { 27 | "TransactionIndex": 20, 28 | "TransactionResult": "tesSUCCESS", 29 | "delivered_amount": "100000000" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /platform/ripple/mocks/payment_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "3D8512E02414EF5A6BC00281D945735E85DED9EF739B1DCA9EABE04D9EEC72C1", 3 | "ledger_index": 49163909, 4 | "date": "2019-08-06T17:58:01+00:00", 5 | "tx": { 6 | "TransactionType": "Payment", 7 | "Flags": 2147614720, 8 | "Sequence": 115, 9 | "DestinationTag": 0, 10 | "LastLedgerSequence": 49163911, 11 | "Amount": "1000000000", 12 | "Fee": "120", 13 | "SendMax": { 14 | "value": "0.001", 15 | "currency": "USD", 16 | "issuer": "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq" 17 | }, 18 | "SigningPubKey": "030E4853E7D0B0E2D3C1233EADCB1B1C35DE75AD4AECD94AC534B3057537753B94", 19 | "TxnSignature": "3045022100EBBDDB5D2F59472463CA03429DDDED4F06648FF097662697CCFF3C5C9C36091202205367A18FE65F767D6C6D256B2F7058BBA3C5D35655AD881A94EFC4BA2C2422DF", 20 | "Account": "raz97dHvnyBcnYTbXGYxhV8bGyr1aPrE5w", 21 | "Destination": "rna8qC8Y9uLd2vzYtSEa1AJcdD3896zQ9S", 22 | "Memos": [ 23 | { 24 | "Memo": { 25 | "MemoType": "636C69656E74", 26 | "MemoData": "726D2D312E322E34" 27 | } 28 | } 29 | ] 30 | }, 31 | "meta": { 32 | "TransactionIndex": 24, 33 | "DeliveredAmount": "3100", 34 | "TransactionResult": "tesSUCCESS", 35 | "delivered_amount": "3100" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /platform/ripple/mocks/payment_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "3D8512E02414EF5A6BC00281D945735E85DED9EF739B1DCA9EABE04D9EEC72C1", 3 | "ledger_index": 49163909, 4 | "date": "2019-08-06T17:58:01+00:00", 5 | "tx": { 6 | "TransactionType": "SetRegularKey" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /platform/ripple/mocks/payment_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "1D849E3A0041357EE373C7E17C9564F890047475492D9530B5F20A3BD6D95822", 3 | "ledger_index": 49841027, 4 | "date": "2019-09-06T01:48:32+00:00", 5 | "tx": { 6 | "TransactionType": "Payment", 7 | "Flags": 2147942400, 8 | "Sequence": 292765, 9 | "LastLedgerSequence": 49841035, 10 | "Amount": { 11 | "value": "100000", 12 | "currency": "ETH", 13 | "issuer": "rJavT3eWaX9FubZFHtCvymJ6ZhSgJdMyNx" 14 | }, 15 | "Fee": "162", 16 | "SendMax": "100000000000", 17 | "Account": "r4NT6UfELQyoS689VLye22B3SfgvpM3nHY", 18 | "Destination": "rJavT3eWaX9FubZFHtCvymJ6ZhSgJdMyNx" 19 | }, 20 | "meta": { 21 | "TransactionIndex": 16, 22 | "DeliveredAmount": { 23 | "value": "533.92", 24 | "currency": "ETH", 25 | "issuer": "rJavT3eWaX9FubZFHtCvymJ6ZhSgJdMyNx" 26 | }, 27 | "TransactionResult": "tesSUCCESS", 28 | "delivered_amount": { 29 | "value": "533.92", 30 | "currency": "ETH", 31 | "issuer": "rJavT3eWaX9FubZFHtCvymJ6ZhSgJdMyNx" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /platform/ripple/mocks/payment_failed.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "B9086F7EB895E943C4DDA9F1B582E6E7DE35F4FB91AD13C50AB74F854DC0EBE0", 3 | "ledger_index": 53401154, 4 | "date": "2020-02-13T10:47:52+00:00", 5 | "tx": { 6 | "TransactionType": "Payment", 7 | "Flags": 2147483648, 8 | "Sequence": 2102726, 9 | "LastLedgerSequence": 53401182, 10 | "Amount": "24999750000", 11 | "Fee": "100000", 12 | "SigningPubKey": "02C2EDA75565BA8D3CBD96FB28D53C9BE1B7A4DC1AF6FF1B2EBBD478D520BED52E", 13 | "TxnSignature": "304502210081A1620F2106671FDFB9C0ABEB2976236693E6142E2B4CB7EA89338EA344BF8D02200EC9D2E2BE79C9E053F809802C426AFDC55B7BA5E01E3DF4B193F122740C39A3", 14 | "Account": "rJb5KsHsDHF1YS5B5DU6QCkH5NsPaKQTcy", 15 | "Destination": "rfHj5CuhajwdrzW2C8Y7EDXbx1QMiD5SXP" 16 | }, 17 | "meta": { 18 | "TransactionIndex": 30, 19 | "TransactionResult": "tefBAD_LEDGER", 20 | "delivered_amount": "24999750000" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /platform/solana/base.go: -------------------------------------------------------------------------------- 1 | package solana 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{client: Client{client.InitJSONClient(api, middleware.SentryErrorHandler)}} 15 | } 16 | 17 | func (p *Platform) Coin() coin.Coin { 18 | return coin.Solana() 19 | } 20 | -------------------------------------------------------------------------------- /platform/solana/block.go: -------------------------------------------------------------------------------- 1 | package solana 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/types" 6 | ) 7 | 8 | const ( 9 | errorSkipped = -32009 10 | ) 11 | 12 | func (p *Platform) CurrentBlockNumber() (int64, error) { 13 | return p.client.GetLasteBlock() 14 | } 15 | 16 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 17 | block, err := p.client.GetTransactionsInBlock(num) 18 | if err != nil { 19 | // solana might skip some block which makes block number is not consecutive 20 | rpcError, ok := err.(*client.RpcError) 21 | if ok && rpcError.Code == errorSkipped { 22 | return &types.Block{Number: num, Txs: types.Txs{}}, nil 23 | } 24 | return nil, err 25 | } 26 | 27 | txs := make(types.Txs, 0) 28 | for _, tx := range block.Transactions { 29 | normalized, err := p.NormalizeTx(tx, uint64(num), block.BlockTime) 30 | if err != nil { 31 | continue 32 | } 33 | txs = append(txs, normalized) 34 | } 35 | 36 | return &types.Block{Number: num, Txs: txs}, nil 37 | } 38 | -------------------------------------------------------------------------------- /platform/solana/mocks/GetTxsByAddress.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "4aQuuc4XFP7SQrZF4z3TsFyqdRHQBF37yxK8t1jQLL97JSwGadEvUFSM4GK6DtacWzid7VT7VTaqbfJfbzzsboYt", 4 | "coin": 501, 5 | "from": "HBYC51YrGFAZ8rM7Sj8e9uqKggpSrDYrinQDZzvMtqQp", 6 | "to": "AHy6YZA8BsHgQfVkk7MbwpAN94iyN7Nf1zN4nPqUN32Q", 7 | "fee": "5000", 8 | "date": 1588062639, 9 | "block": 5632752, 10 | "status": "completed", 11 | "sequence": 0, 12 | "type": "transfer", 13 | "direction": "incoming", 14 | "memo": "", 15 | "metadata": { "value": "1230000", "symbol": "SOL", "decimals": 9 } 16 | }, 17 | { 18 | "id": "5QeLdXcC8GRh3KSs67Pq9sNE5uwFKkHt37crYLzcZ19ieh6RSEEGrS2r5eVXJiaXKb8M6FyKF45kHHmQRW2g1LYA", 19 | "coin": 501, 20 | "from": "AHy6YZA8BsHgQfVkk7MbwpAN94iyN7Nf1zN4nPqUN32Q", 21 | "to": "HBYC51YrGFAZ8rM7Sj8e9uqKggpSrDYrinQDZzvMtqQp", 22 | "fee": "5000", 23 | "date": 1588026961, 24 | "block": 5543556, 25 | "status": "completed", 26 | "sequence": 0, 27 | "type": "transfer", 28 | "direction": "outgoing", 29 | "memo": "", 30 | "metadata": { "value": "120000", "symbol": "SOL", "decimals": 9 } 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /platform/stellar/base.go: -------------------------------------------------------------------------------- 1 | package stellar 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | CoinIndex uint 12 | } 13 | 14 | func Init(coin uint, api string) *Platform { 15 | return &Platform{ 16 | CoinIndex: coin, 17 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 18 | } 19 | } 20 | 21 | func (p *Platform) Coin() coin.Coin { 22 | return coin.Coins[p.CoinIndex] 23 | } 24 | -------------------------------------------------------------------------------- /platform/stellar/block.go: -------------------------------------------------------------------------------- 1 | package stellar 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.CurrentBlockNumber() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | if srcBlock, err := p.client.GetBlockByNumber(num); err == nil { 11 | block := p.NormalizeBlock(srcBlock) 12 | return &block, nil 13 | } else { 14 | return nil, err 15 | } 16 | } 17 | func (p *Platform) NormalizeBlock(block *Block) types.Block { 18 | return types.Block{ 19 | Number: block.Ledger.Sequence, 20 | Txs: p.NormalizePayments(block.Payments), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /platform/stellar/mocks/create_tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "25002129911451649", 3 | "paging_token": "25002129911451649", 4 | "transaction_successful": true, 5 | "source_account": "GBEZOC5U4TVH7ZY5N3FLYHTCZSI6VFGTULG7PBITLF5ZEBPJXFT46YZM", 6 | "type": "create_account", 7 | "type_i": 0, 8 | "created_at": "2016-08-10T17:30:20Z", 9 | "transaction_hash": "8b96cf3a660b85ef80b5a84c032cacdb93bb139cfe7e929b974ea9eaa0d29141", 10 | "starting_balance": "47326939370.0000000", 11 | "funder": "GBEZOC5U4TVH7ZY5N3FLYHTCZSI6VFGTULG7PBITLF5ZEBPJXFT46YZM", 12 | "account": "GDKIJJIKXLOM2NRMPNQZUUYK24ZPVFC6426GZAEP3KUK6KEJLACCWNMX" 13 | } 14 | -------------------------------------------------------------------------------- /platform/stellar/mocks/transfer_tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "25008572362395649", 3 | "paging_token": "25008572362395649", 4 | "transaction_successful": true, 5 | "source_account": "GDKIJJIKXLOM2NRMPNQZUUYK24ZPVFC6426GZAEP3KUK6KEJLACCWNMX", 6 | "type": "payment", 7 | "type_i": 1, 8 | "created_at": "2016-08-10T19:39:01Z", 9 | "transaction_hash": "a596dc910bae20b5bbe64aa7aa3f42acbd55769b98307878f5ad095e994bc9cf", 10 | "asset_type": "native", 11 | "from": "GDKIJJIKXLOM2NRMPNQZUUYK24ZPVFC6426GZAEP3KUK6KEJLACCWNMX", 12 | "to": "GAX3BRBNB5WTJ2GNEFFH7A4CZKT2FORYABDDBZR5FIIT3P7FLS2EFOZZ", 13 | "amount": "100000000.0000000", 14 | "transaction": { 15 | "memo": "testing", 16 | "ledger": 123 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /platform/tezos/base.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | rpcClient RpcClient 12 | bakerClient BakerClient 13 | } 14 | 15 | func Init(api, rpc, baker string) *Platform { 16 | p := &Platform{ 17 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 18 | rpcClient: RpcClient{client.InitClient(rpc, middleware.SentryErrorHandler)}, 19 | bakerClient: BakerClient{client.InitClient(baker, middleware.SentryErrorHandler)}, 20 | } 21 | p.client.SetTimeout(35) 22 | return p 23 | } 24 | 25 | func (p *Platform) Coin() coin.Coin { 26 | return coin.Tezos() 27 | } 28 | -------------------------------------------------------------------------------- /platform/tezos/client.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/trustwallet/golibs/client" 9 | ) 10 | 11 | type Client struct { 12 | client.Request 13 | } 14 | 15 | func (c *Client) GetTxsOfAddress(address string, txType []string) (txs ExplorerAccount, err error) { 16 | path := fmt.Sprintf("account/%s/op", address) 17 | err = c.Get(&txs, path, url.Values{ 18 | "order": {"desc"}, 19 | "type": {strings.Join(txType, ",")}, 20 | "limit": {"25"}, 21 | }) 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /platform/tezos/mocks/account.json: -------------------------------------------------------------------------------- 1 | { 2 | "delegate": "tz2FCNBrERXtaTtNX6iimR1UJ5JSDxvdHM93", 3 | "balance": "91237897" 4 | } 5 | -------------------------------------------------------------------------------- /platform/tezos/mocks/validator.json: -------------------------------------------------------------------------------- 1 | [{ "pkh": "tz2TSvNTh2epDMhZHrw73nV9piBX7kLZ9K9m", "rolls": 3726 }] 2 | -------------------------------------------------------------------------------- /platform/tezos/rpc_mock.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | type RpcClientMock struct { 4 | Balance string 5 | } 6 | 7 | func (r *RpcClientMock) GetAccountBalanceAtBlock(address string, block int64) (account AccountBalance, err error) { 8 | return AccountBalance{Balance: r.Balance}, nil 9 | } 10 | -------------------------------------------------------------------------------- /platform/tezos/rpc_model.go: -------------------------------------------------------------------------------- 1 | package tezos 2 | 3 | type RpcBlockHeader struct { 4 | Level int64 `json:"level"` 5 | Timestamp string `json:"timestamp"` 6 | } 7 | 8 | type RpcOperationContent struct { 9 | Hash string `json:"hash"` 10 | Contents []interface{} `json:"contents"` 11 | } 12 | 13 | type RpcTransaction struct { 14 | Kind string `json:"kind"` 15 | Source string `json:"source"` 16 | Fee string `json:"fee"` 17 | Counter string `json:"counter"` 18 | GasLimit string `json:"gas_limit"` 19 | StorageLimit string `json:"storage_limit"` 20 | Amount string `json:"amount,omitempty"` 21 | Destination string `json:"destination,omitempty"` 22 | Delegate string `json:"delegate,omitempty"` 23 | Metadata RpcMetadata `json:"metadata"` 24 | } 25 | 26 | type RpcOperationContents []RpcOperationContent 27 | 28 | type RpcBlock struct { 29 | Header RpcBlockHeader `json:"header"` 30 | Operations []RpcOperationContents `json:"operations"` 31 | } 32 | 33 | type RpcMetadata struct { 34 | OperationResult OperationResult `json:"operation_result"` 35 | } 36 | 37 | type OperationResult struct { 38 | Status string `json:"status"` 39 | } 40 | -------------------------------------------------------------------------------- /platform/theta/base.go: -------------------------------------------------------------------------------- 1 | package theta 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api, key string) *Platform { 14 | request := client.InitClient(api, middleware.SentryErrorHandler) 15 | request.Headers = map[string]string{"x-api-token": key} 16 | return &Platform{ 17 | client: Client{request}, 18 | } 19 | } 20 | 21 | func (p *Platform) Coin() coin.Coin { 22 | return coin.Coins[coin.THETA] 23 | } 24 | -------------------------------------------------------------------------------- /platform/theta/client.go: -------------------------------------------------------------------------------- 1 | package theta 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | 7 | "github.com/trustwallet/golibs/client" 8 | ) 9 | 10 | type Client struct { 11 | client.Request 12 | } 13 | 14 | func (c *Client) FetchAddressTransactions(address string) ([]Tx, error) { 15 | query := url.Values{ 16 | "type": {"2"}, 17 | "pageNumber": {"1"}, 18 | "limitNumber": {"100"}, 19 | "isEqualType": {"true"}, 20 | } 21 | uri := fmt.Sprintf("accounttx/%s", url.PathEscape(address)) 22 | var transfers AccountTxList 23 | err := c.Get(&transfers, uri, query) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return transfers.Body, nil 28 | } 29 | -------------------------------------------------------------------------------- /platform/theta/mocks/tfuel_transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "0x558cb5ec877119c2c84a677277efb5b3059adb830c6e74971b3dbe93221b7132", 3 | "type": 2, 4 | "data": { 5 | "fee": { 6 | "thetawei": "0", 7 | "tfuelwei": "2000000000000" 8 | }, 9 | "inputs": [ 10 | { 11 | "address": "0x0a7d7141e9abe5d1c760cffa1129c6eb94f35a2a", 12 | "coins": { 13 | "thetawei": "0", 14 | "tfuelwei": "44326000000000000" 15 | }, 16 | "sequence": "44" 17 | } 18 | ], 19 | "outputs": [ 20 | { 21 | "address": "0xac0eeb6ee3e32e2c74e14ac74155063e4f4f981f", 22 | "coins": { 23 | "thetawei": "0", 24 | "tfuelwei": "44324000000000000" 25 | } 26 | } 27 | ] 28 | }, 29 | "number": 7785266, 30 | "block_height": "700327", 31 | "timestamp": "1557136821", 32 | "status": "finalized" 33 | } 34 | -------------------------------------------------------------------------------- /platform/theta/mocks/theta_transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "0x413d8423fd1e6df99fc57f425dfd58c791c877657b364c62c15905ade5114a70", 3 | "data": { 4 | "fee": { 5 | "thetawei": "0", 6 | "tfuelwei": "2000000000000" 7 | }, 8 | "inputs": [ 9 | { 10 | "address": "0xac0eeb6ee3e32e2c74e14ac74155063e4f4f981f", 11 | "coins": { 12 | "thetawei": "4000000000000000000", 13 | "tfuelwei": "2000000000000" 14 | }, 15 | "sequence": "43" 16 | } 17 | ], 18 | "outputs": [ 19 | { 20 | "address": "0x0a7d7141e9abe5d1c760cffa1129c6eb94f35a2a", 21 | "coins": { 22 | "thetawei": "4000000000000000000", 23 | "tfuelwei": "0" 24 | } 25 | } 26 | ] 27 | }, 28 | "number": 7785228, 29 | "block_height": "700321", 30 | "timestamp": "1557136781", 31 | "status": "finalized" 32 | } 33 | -------------------------------------------------------------------------------- /platform/theta/model.go: -------------------------------------------------------------------------------- 1 | package theta 2 | 3 | // THETA transaction types https://github.com/thetatoken/theta-mainnet-integration-guide/blob/master/docs/api.md#getblock 4 | const ( 5 | SendTransaction = 2 6 | ) 7 | 8 | // Response from Explorer 9 | type AccountTxList struct { 10 | Body []Tx `json:"body"` 11 | } 12 | 13 | type Tx struct { 14 | Hash string `json:"hash"` 15 | Type int `json:"type"` 16 | Data Data `json:"data"` 17 | BlockHeight string `json:"block_height"` 18 | Timestamp string `json:"timestamp"` 19 | } 20 | 21 | type Data struct { 22 | Fee Fee `json:"fee"` 23 | Inputs []Input `json:"inputs"` 24 | Outputs []Output `json:"outputs"` 25 | } 26 | 27 | type Fee struct { 28 | Thetawei string `json:"thetawei"` 29 | Tfuelwei string `json:"tfuelwei"` 30 | } 31 | 32 | type Input struct { 33 | Address string `json:"address"` 34 | Sequence string `json:"sequence"` 35 | } 36 | type Output struct { 37 | Address string `json:"address"` 38 | Coins Fee `json:"coins"` 39 | } 40 | -------------------------------------------------------------------------------- /platform/tron/address.go: -------------------------------------------------------------------------------- 1 | package tron 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | 7 | "github.com/mr-tron/base58" 8 | ) 9 | 10 | // HexToAddress converts a hex representation of a Tron address 11 | // into a Base58 string with a 4 byte checksum. 12 | func HexToAddress(hexAddr string) (b58 string, err error) { 13 | bytes, err := hex.DecodeString(hexAddr) 14 | if err != nil { 15 | return "", err 16 | } 17 | var checksum [32]byte 18 | checksum = sha256.Sum256(bytes) 19 | checksum = sha256.Sum256(checksum[:]) 20 | bytes = append(bytes, checksum[:4]...) 21 | b58 = base58.EncodeAlphabet(bytes, base58.BTCAlphabet) 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /platform/tron/address_test.go: -------------------------------------------------------------------------------- 1 | package tron 2 | 3 | import "testing" 4 | 5 | func TestHexToAddress(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | hexAddr string 9 | want string 10 | wantErr bool 11 | }{ 12 | { 13 | name: "Test hex to base58 address", 14 | hexAddr: "4182dd6b9966724ae2fdc79b416c7588da67ff1b35", 15 | want: "TMuA6YqfCeX8EhbfYEg5y7S4DqzSJireY9", 16 | wantErr: false, 17 | }, 18 | } 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | gotB58, err := HexToAddress(tt.hexAddr) 22 | if (err != nil) != tt.wantErr { 23 | t.Errorf("HexToAddress() error = %v, wantErr %v", err, tt.wantErr) 24 | return 25 | } 26 | if gotB58 != tt.want { 27 | t.Errorf("HexToAddress() = %v, want %v", gotB58, tt.want) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /platform/tron/base.go: -------------------------------------------------------------------------------- 1 | package tron 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api, apiKey string) *Platform { 14 | request := client.InitClient(api, middleware.SentryErrorHandler) 15 | //TODO: Add when ready 16 | //request.Headers = map[string]string{"TRON-PRO-API-KEY": apiKey} 17 | return &Platform{ 18 | client: Client{request}, 19 | } 20 | } 21 | 22 | func (p *Platform) Coin() coin.Coin { 23 | return coin.Tron() 24 | } 25 | -------------------------------------------------------------------------------- /platform/tron/block.go: -------------------------------------------------------------------------------- 1 | package tron 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/types" 5 | ) 6 | 7 | func (p *Platform) CurrentBlockNumber() (int64, error) { 8 | return p.client.fetchCurrentBlockNumber() 9 | } 10 | 11 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 12 | block, err := p.client.fetchBlockByNumber(num) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | txs := p.NormalizeBlockTxs(block.Txs) 18 | 19 | return &types.Block{ 20 | Number: num, 21 | Txs: txs, 22 | }, nil 23 | } 24 | 25 | func (p *Platform) NormalizeBlockTxs(srcTxs []Tx) []types.Tx { 26 | txs := make([]types.Tx, 0) 27 | for _, srcTx := range srcTxs { 28 | if tx, err := normalize(srcTx); err == nil { 29 | txs = append(txs, *tx) 30 | } 31 | } 32 | return txs 33 | } 34 | -------------------------------------------------------------------------------- /platform/tron/mocks/delegation.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "419241920da7d6bb487a33a6df3838e3d208f0b251", 3 | "frozen": [ 4 | { 5 | "expire_time": 10437262001000, 6 | "frozen_balance": "35000000" 7 | } 8 | ], 9 | "votes": [ 10 | { 11 | "vote_address": "TGzz8gjYiYRqpfmDwnLxfgPuLVNmpCswVp", 12 | "vote_count": 21 13 | }, 14 | { 15 | "vote_address": "TPMGfspxLQGom8sKutrbHcDKtHjRHFbGKw", 16 | "vote_count": 5 17 | }, 18 | { 19 | "vote_address": "TPMGfspxLQGom8sKutrbHcDKtHjRHFbGKw", 20 | "vote_count": 5 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /platform/tron/mocks/delegation_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "419241920da7d6bb487a33a6df3838e3d208f0b251", 3 | "frozen": [ 4 | { 5 | "expire_time": 1569465251000, 6 | "frozen_balance": "5000000" 7 | } 8 | ], 9 | "votes": [ 10 | { 11 | "vote_address": "TPMGfspxLQGom8sKutrbHcDKtHjRHFbGKw", 12 | "vote_count": 5 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /platform/tron/mocks/tokens/asset_1000542_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "meta": { "at": 1592754266101, "page_size": 1 }, 4 | "data": [ 5 | { 6 | "id": 1000542, 7 | "abbr": "FOM", 8 | "description": "Fomo3D is a decentralized, trustless blockchain game.", 9 | "name": "FomoThreeD", 10 | "num": 1, 11 | "total_supply": 80000000000, 12 | "trx_num": 1000000, 13 | "url": "https://fomo3d.games/", 14 | "owner_address": "4134f1b9c19cf40f565661697eb47b0090ac779507", 15 | "start_time": 1534348821000, 16 | "end_time": 1924876800000 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /platform/tron/mocks/tokens/asset_1000567_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "meta": { "at": 1592754347268, "page_size": 1 }, 4 | "data": [ 5 | { 6 | "id": 1000567, 7 | "abbr": "os", 8 | "description": "Open Decentralized Search Engine", 9 | "frozen_supply": [{ "frozen_amount": 74000000000, "frozen_days": 3652 }], 10 | "name": "OtonamiS", 11 | "num": 100, 12 | "total_supply": 99000000000, 13 | "trx_num": 1000000, 14 | "url": "https://OtonamiS.com", 15 | "owner_address": "413cea69143b5a5b1ff15d847a7e30e3bffa6a2247", 16 | "start_time": 1534773969000, 17 | "end_time": 1566280800000 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /platform/tron/mocks/tokens/asset_tr7nh_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "meta": { "at": 1592754347268, "page_size": 1 }, 4 | "data": [] 5 | } 6 | -------------------------------------------------------------------------------- /platform/tron/mocks/tokens/tokens_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | "c195_tTCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9", 3 | "c195_tTCRhVHPv6efvXgogNMhiunAMXFKcMmv2pF", 4 | "c195_tTG7Z1ptC7nRkaniDVRhHSyLycaroaS5PdK", 5 | "c195_tTJSF4iVkzkRkYwEVNkqJkeGDaZpnFbGy9x", 6 | "c195_tTLKyLt4MXuvvdFvUUc3Zma4rZyj2t87Mak", 7 | "c195_tTLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7", 8 | "c195_tTMCMPzmosnQ8UAYW1zcBwjLTxDq8ce4Y5e", 9 | "c195_tTPt8DTDBZYfJ9fuyRjdWJr4PP68tRfptLG", 10 | "c195_tTR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", 11 | "c195_tTTvVC9jv5AfDdHuGeCMcQg6QftmNnfQiVm" 12 | ] 13 | -------------------------------------------------------------------------------- /platform/tron/mocks/tokens/txs_empty_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "meta": { 4 | "at": 1592757318961, 5 | "page_size": 20, 6 | "fingerprint": "AYa6eBNpCs5E2DnumiJJJWJ3n2PYMBRFvU7BLjXD49Jm779DJ1C1hUgjwJAmQx5Y2BnkStKMjzQvcALKeQJYfW51m6sY7YEWW", 7 | "links": { 8 | "next": "https://api.trongrid.io:443/v1/accounts/TM1zzNDZD2DPASbKcgdVoTYhfmYgtfwx9R/transactions?fingerprint=AYa6eBNpCs5E2DnumiJJJWJ3n2PYMBRFvU7BLjXD49Jm779DJ1C1hUgjwJAmQx5Y2BnkStKMjzQvcALKeQJYfW51m6sY7YEWW" 9 | } 10 | }, 11 | "data": [] 12 | } 13 | -------------------------------------------------------------------------------- /platform/tron/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "block_timestamp": 1564797900000, 3 | "raw_data": { 4 | "contract": [ 5 | { 6 | "parameter": { 7 | "value": { 8 | "amount": 100666888000000, 9 | "owner_address": "4182dd6b9966724ae2fdc79b416c7588da67ff1b35", 10 | "to_address": "410583a68a3bcd86c25ab1bee482bac04a216b0261" 11 | } 12 | }, 13 | "type": "TransferContract" 14 | } 15 | ] 16 | }, 17 | "txID": "24a10f7a503e78adc0d7e380b68005531b09e16b9e3f7b524e33f40985d287df" 18 | } 19 | -------------------------------------------------------------------------------- /platform/tron/token.go: -------------------------------------------------------------------------------- 1 | package tron 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/asset" 5 | "github.com/trustwallet/golibs/types" 6 | ) 7 | 8 | func (p *Platform) GetTokenListByAddress(address string) ([]types.Token, error) { 9 | return []types.Token{}, nil 10 | } 11 | 12 | func (p *Platform) GetTokenListIdsByAddress(address string) ([]string, error) { 13 | assetIds := make([]string, 0) 14 | tokens, err := p.client.fetchAccount(address) 15 | if err != nil { 16 | return assetIds, err 17 | } 18 | if len(tokens.Data) == 0 { 19 | return assetIds, nil 20 | } 21 | for _, trc20Tokens := range tokens.Data[0].Trc20 { 22 | for assetId := range trc20Tokens { 23 | assetIds = append(assetIds, asset.BuildID(p.Coin().ID, assetId)) 24 | } 25 | } 26 | 27 | return assetIds, nil 28 | } 29 | -------------------------------------------------------------------------------- /platform/vechain/base.go: -------------------------------------------------------------------------------- 1 | package vechain 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitJSONClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Vechain() 21 | } 22 | -------------------------------------------------------------------------------- /platform/vechain/block.go: -------------------------------------------------------------------------------- 1 | package vechain 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | return p.client.GetCurrentBlock() 7 | } 8 | 9 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 10 | block, err := p.client.GetBlockByNumber(num) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | txs, err := p.getTransactionsByIDs(block.Transactions) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return &types.Block{Number: num, Txs: txs}, nil 21 | } 22 | -------------------------------------------------------------------------------- /platform/vechain/mocks/incoming_vtho_receipt.json: -------------------------------------------------------------------------------- 1 | { 2 | "gasUsed": 36582, 3 | "gasPayer": "0xb5e883349e68ab59307d1604555ac890fac47128", 4 | "paid": "0x1fbad5f2e25570000", 5 | "reward": "0x984d9c8dd8008000", 6 | "reverted": false, 7 | "meta": { 8 | "blockID": "0x007ac4b1a18c0fc3a11f44285d6a42eff591a99312d303c105a4f4de7fa10575", 9 | "blockNumber": 8045745, 10 | "blockTimestamp": 1610958460, 11 | "txID": "0xb356fa7b3a371f1518a5f9bc51e951d0dac2ef04d58b532c7ca50a52aa5cddb4", 12 | "txOrigin": "0xb5e883349e68ab59307d1604555ac890fac47128" 13 | }, 14 | "outputs": [ 15 | { 16 | "contractAddress": null, 17 | "events": [ 18 | { 19 | "address": "0x0000000000000000000000000000456e65726779", 20 | "topics": [ 21 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 22 | "0x000000000000000000000000b5e883349e68ab59307d1604555ac890fac47128", 23 | "0x000000000000000000000000e99399dd211ef54c301a5d1aa813471d92122ea8" 24 | ], 25 | "data": "0x00000000000000000000000000000000000000000000003635c9adc5dea00000" 26 | } 27 | ], 28 | "transfers": [] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /platform/vechain/mocks/incoming_vtho_tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0xb356fa7b3a371f1518a5f9bc51e951d0dac2ef04d58b532c7ca50a52aa5cddb4", 3 | "chainTag": 74, 4 | "blockRef": "0x007ac4b0b0c47a00", 5 | "expiration": 720, 6 | "clauses": [ 7 | { 8 | "to": "0x0000000000000000000000000000456e65726779", 9 | "value": "0x0", 10 | "data": "0xa9059cbb000000000000000000000000e99399dd211ef54c301a5d1aa813471d92122ea800000000000000000000000000000000000000000000003635c9adc5dea00000" 11 | } 12 | ], 13 | "gasPriceCoef": 0, 14 | "gas": 43245, 15 | "origin": "0xb5e883349e68ab59307d1604555ac890fac47128", 16 | "delegator": null, 17 | "nonce": "0x2a2e2da5", 18 | "dependsOn": null, 19 | "size": 188, 20 | "meta": { 21 | "blockID": "0x007ac4b1a18c0fc3a11f44285d6a42eff591a99312d303c105a4f4de7fa10575", 22 | "blockNumber": 8045745, 23 | "blockTimestamp": 1610958460 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /platform/vechain/mocks/outgoing_vtho_receipt.json: -------------------------------------------------------------------------------- 1 | { 2 | "gasUsed": 36518, 3 | "gasPayer": "0xe99399dd211ef54c301a5d1aa813471d92122ea8", 4 | "paid": "0x1fac9ff84f3b70000", 5 | "reward": "0x980966417c508000", 6 | "reverted": false, 7 | "meta": { 8 | "blockID": "0x007ac4bc8090b67c3e3c395e5e986226eab3a8619250807cf32b0c247f6d513c", 9 | "blockNumber": 8045756, 10 | "blockTimestamp": 1610958570, 11 | "txID": "0x0677f91de4787d295087acec0a7ba317b0019fbf296fed630fdb5afbfca97a58", 12 | "txOrigin": "0xe99399dd211ef54c301a5d1aa813471d92122ea8" 13 | }, 14 | "outputs": [ 15 | { 16 | "contractAddress": null, 17 | "events": [ 18 | { 19 | "address": "0x0000000000000000000000000000456e65726779", 20 | "topics": [ 21 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 22 | "0x000000000000000000000000e99399dd211ef54c301a5d1aa813471d92122ea8", 23 | "0x000000000000000000000000b5e883349e68ab59307d1604555ac890fac47128" 24 | ], 25 | "data": "0x0000000000000000000000000000000000000000000000006124fee993bc0000" 26 | } 27 | ], 28 | "transfers": [] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /platform/vechain/mocks/outgoing_vtho_tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0x0677f91de4787d295087acec0a7ba317b0019fbf296fed630fdb5afbfca97a58", 3 | "chainTag": 74, 4 | "blockRef": "0x007ac4babac47a00", 5 | "expiration": 720, 6 | "clauses": [ 7 | { 8 | "to": "0x0000000000000000000000000000456e65726779", 9 | "value": "0x0", 10 | "data": "0xa9059cbb000000000000000000000000b5e883349e68ab59307d1604555ac890fac471280000000000000000000000000000000000000000000000006124fee993bc0000" 11 | } 12 | ], 13 | "gasPriceCoef": 0, 14 | "gas": 43181, 15 | "origin": "0xe99399dd211ef54c301a5d1aa813471d92122ea8", 16 | "delegator": null, 17 | "nonce": "0x2bb08067", 18 | "dependsOn": null, 19 | "size": 188, 20 | "meta": { 21 | "blockID": "0x007ac4bc8090b67c3e3c395e5e986226eab3a8619250807cf32b0c247f6d513c", 22 | "blockNumber": 8045756, 23 | "blockTimestamp": 1610958570 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /platform/vechain/mocks/reverted_receipt.json: -------------------------------------------------------------------------------- 1 | { 2 | "gasUsed": 82618, 3 | "gasPayer": "0x7cffb7632252bae3766734d61f148f0ea78fc08c", 4 | "paid": "0x47a8e194565390000", 5 | "reward": "0x157f76dfb37f78000", 6 | "reverted": true, 7 | "meta": { 8 | "blockID": "0x0079ce53965d837f959154813e163ce4d89f51421bdd540f3facb454929ab7fe", 9 | "blockNumber": 7982675, 10 | "blockTimestamp": 1610326580, 11 | "txID": "0x7fae32a743e42eaec54642e2a5742a185299f5b4bedaf12c60f65705661de932", 12 | "txOrigin": "0x7cffb7632252bae3766734d61f148f0ea78fc08c" 13 | }, 14 | "outputs": [] 15 | } 16 | -------------------------------------------------------------------------------- /platform/vechain/mocks/reverted_tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0x7fae32a743e42eaec54642e2a5742a185299f5b4bedaf12c60f65705661de932", 3 | "chainTag": 74, 4 | "blockRef": "0x0079ce52eccee5e8", 5 | "expiration": 18, 6 | "clauses": [ 7 | { 8 | "to": "0xf8e1faa0367298b55f57ed17f7a2ff3f5f1d1628", 9 | "value": "0x0", 10 | "data": "0x095ea7b3000000000000000000000000c96b1e1a436c5ecf150ac7a7de64c0eec73883e000000000000000000000000000000000000000000000001043561a8829300000" 11 | }, 12 | { 13 | "to": "0xc96b1e1a436c5ecf150ac7a7de64c0eec73883e0", 14 | "value": "0x0", 15 | "data": "0x6d6aa5bb000000000000000000000000000000000000000000000000000000003b9aca5800000000000000000000000000000000000000000000001043561a8829300000" 16 | } 17 | ], 18 | "gasPriceCoef": 0, 19 | "gas": 82618, 20 | "origin": "0x7cffb7632252bae3766734d61f148f0ea78fc08c", 21 | "delegator": null, 22 | "nonce": "0x15a6dce553e53070", 23 | "dependsOn": null, 24 | "size": 286, 25 | "meta": { 26 | "blockID": "0x0079ce53965d837f959154813e163ce4d89f51421bdd540f3facb454929ab7fe", 27 | "blockNumber": 7982675, 28 | "blockTimestamp": 1610326580 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /platform/vechain/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "sender": "0xb5e883349e68ab59307d1604555ac890fac47128", 3 | "recipient": "0x2c7a8d5cce0d5e6a8a31233b7dc3dae9aae4b405", 4 | "amount": "0x12b1815d00738000", 5 | "meta": { 6 | "blockID": "0x004313a4bd4286e821b684cc1749deb3df12fa2a8114435fbd35baa155e82016", 7 | "blockNumber": 4395940, 8 | "blockTimestamp": 1574410670, 9 | "txID": "0x702edd54bd4e13e0012798cc8b2dfa52f7150173945103d203fae26b8e3d2ed7", 10 | "txOrigin": "0xb5e883349e68ab59307d1604555ac890fac47128", 11 | "clauseIndex": 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /platform/vechain/mocks/tx_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "gas": 21000, 3 | "nonce": "0x8cff29df64a414f8" 4 | } 5 | -------------------------------------------------------------------------------- /platform/waves/base.go: -------------------------------------------------------------------------------- 1 | package waves 2 | 3 | import ( 4 | "github.com/trustwallet/golibs/client" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/network/middleware" 7 | ) 8 | 9 | type Platform struct { 10 | client Client 11 | } 12 | 13 | func Init(api string) *Platform { 14 | return &Platform{ 15 | client: Client{client.InitClient(api, middleware.SentryErrorHandler)}, 16 | } 17 | } 18 | 19 | func (p *Platform) Coin() coin.Coin { 20 | return coin.Coins[coin.WAVES] 21 | } 22 | -------------------------------------------------------------------------------- /platform/waves/block.go: -------------------------------------------------------------------------------- 1 | package waves 2 | 3 | import "github.com/trustwallet/golibs/types" 4 | 5 | func (p *Platform) CurrentBlockNumber() (int64, error) { 6 | currentBlock, err := p.client.GetCurrentBlock() 7 | if err != nil { 8 | return 0, err 9 | } 10 | return currentBlock.Height, nil 11 | } 12 | 13 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 14 | srcTxs, err := p.client.GetBlockByNumber(num) 15 | if err != nil { 16 | return nil, err 17 | } 18 | txs := NormalizeTxs(srcTxs.Transactions) 19 | 20 | return &types.Block{ 21 | Number: num, 22 | Txs: txs, 23 | }, nil 24 | } 25 | -------------------------------------------------------------------------------- /platform/waves/client.go: -------------------------------------------------------------------------------- 1 | package waves 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/trustwallet/golibs/client" 7 | ) 8 | 9 | type Client struct { 10 | client.Request 11 | } 12 | 13 | func (c *Client) GetTxs(address string, limit int) ([]Transaction, error) { 14 | path := fmt.Sprintf("transactions/address/%s/limit/%d", address, limit) 15 | txs := make([][]Transaction, 0) 16 | err := c.Get(&txs, path, nil) 17 | 18 | if len(txs) > 0 { 19 | return txs[0], err 20 | } else { 21 | return []Transaction{}, err 22 | } 23 | } 24 | 25 | func (c *Client) GetBlockByNumber(num int64) (block *Block, err error) { 26 | path := fmt.Sprintf("blocks/at/%d", num) 27 | err = c.Get(&block, path, nil) 28 | 29 | return block, err 30 | } 31 | 32 | func (c *Client) GetCurrentBlock() (block *CurrentBlock, err error) { 33 | path := "blocks/height" 34 | err = c.Get(&block, path, nil) 35 | 36 | return block, err 37 | } 38 | -------------------------------------------------------------------------------- /platform/waves/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": 4, 3 | "id": "7QoQc9qMUBCfY4QV35mgBsT8eTXybvGkM2HTumtAvBUL", 4 | "sender": "3PLrCnhKyX5iFbGDxbqqMvea5VAqxMcinPW", 5 | "senderPublicKey": "Ao159h5j1piHBhoEbCAYyaiKNd6uoKvcdwzRZF9za3Vv", 6 | "fee": 100000, 7 | "timestamp": 1561048131740, 8 | "signature": "4WjDwn5t34PLHzgH1NfA4DYdt4PdTbGQDjDdxwKrp82QTQSHFRrgSJXWU2FTYe82afvgUDhnipSKxaiGzMWWo2HW", 9 | "proofs": [ 10 | "4WjDwn5t34PLHzgH1NfA4DYdt4PdTbGQDjDdxwKrp82QTQSHFRrgSJXWU2FTYe82afvgUDhnipSKxaiGzMWWo2HW" 11 | ], 12 | "version": 1, 13 | "recipient": "3PKWyVAmHom1sevggiXVfbGUc3kS85qT4Va", 14 | "assetId": null, 15 | "feeAssetId": null, 16 | "feeAsset": null, 17 | "amount": 9481600000, 18 | "attachment": "", 19 | "height": 1580410 20 | } 21 | -------------------------------------------------------------------------------- /platform/waves/model.go: -------------------------------------------------------------------------------- 1 | package waves 2 | 3 | type Transaction struct { 4 | Id string `json:"id"` 5 | Sender string `json:"sender"` 6 | AssetId string `json:"assetId"` 7 | Recipient string `json:"recipient"` 8 | Amount uint64 `json:"amount"` 9 | FeeAssetId string `json:"feeAssetId"` 10 | Fee uint64 `json:"fee"` 11 | Timestamp uint64 `json:"timestamp"` 12 | Attachment string `json:"attachment"` 13 | Block uint64 `json:"height"` 14 | Type uint64 `json:"type"` 15 | } 16 | 17 | type CurrentBlock struct { 18 | Height int64 `json:"height"` 19 | } 20 | 21 | type Block struct { 22 | Transactions []Transaction `json:"transactions"` 23 | } 24 | -------------------------------------------------------------------------------- /platform/waves/transaction.go: -------------------------------------------------------------------------------- 1 | package waves 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/trustwallet/golibs/coin" 7 | "github.com/trustwallet/golibs/types" 8 | ) 9 | 10 | func (p *Platform) GetTxsByAddress(address string) (types.Txs, error) { 11 | addressTxs, err := p.client.GetTxs(address, 25) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | txs := NormalizeTxs(addressTxs) 17 | 18 | return txs, nil 19 | } 20 | 21 | func NormalizeTxs(srcTxs []Transaction) (txs types.Txs) { 22 | for _, srcTx := range srcTxs { 23 | tx, ok := NormalizeTx(&srcTx) 24 | if !ok || len(txs) >= types.TxPerPage { 25 | continue 26 | } 27 | txs = append(txs, tx) 28 | } 29 | return 30 | } 31 | 32 | func NormalizeTx(srcTx *Transaction) (tx types.Tx, ok bool) { 33 | var result types.Tx 34 | 35 | if srcTx.Type == 4 && len(srcTx.AssetId) == 0 { 36 | result = types.Tx{ 37 | ID: srcTx.Id, 38 | Coin: coin.WAVES, 39 | From: srcTx.Sender, 40 | To: srcTx.Recipient, 41 | Fee: types.Amount(strconv.Itoa(int(srcTx.Fee))), 42 | Date: int64(srcTx.Timestamp) / 1000, 43 | Block: srcTx.Block, 44 | Memo: srcTx.Attachment, 45 | Status: types.StatusCompleted, 46 | Meta: types.Transfer{ 47 | Value: types.Amount(strconv.Itoa(int(srcTx.Amount))), 48 | Symbol: coin.Coins[coin.WAVES].Symbol, 49 | Decimals: coin.Coins[coin.WAVES].Decimals, 50 | }, 51 | } 52 | return result, true 53 | } 54 | 55 | return result, false 56 | } 57 | -------------------------------------------------------------------------------- /platform/zilliqa/base.go: -------------------------------------------------------------------------------- 1 | package zilliqa 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/platform/zilliqa/rpc" 5 | "github.com/trustwallet/blockatlas/platform/zilliqa/viewblock" 6 | "github.com/trustwallet/golibs/coin" 7 | ) 8 | 9 | type Platform struct { 10 | client viewblock.Client 11 | rpcClient rpc.Client 12 | } 13 | 14 | func Init(api, apiKey, rpcUrl string) *Platform { 15 | p := &Platform{ 16 | client: viewblock.InitClient(api, apiKey), 17 | rpcClient: rpc.InitClient(rpcUrl), 18 | } 19 | return p 20 | } 21 | 22 | func (p *Platform) Coin() coin.Coin { 23 | return coin.Zilliqa() 24 | } 25 | -------------------------------------------------------------------------------- /platform/zilliqa/block.go: -------------------------------------------------------------------------------- 1 | package zilliqa 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/trustwallet/blockatlas/platform/zilliqa/viewblock" 7 | "github.com/trustwallet/golibs/types" 8 | ) 9 | 10 | func (p *Platform) CurrentBlockNumber() (int64, error) { 11 | info, err := p.rpcClient.GetBlockchainInfo() 12 | if err != nil { 13 | return 0, err 14 | } 15 | block, err := strconv.ParseInt(info.NumTxBlocks, 10, 64) 16 | if err != nil { 17 | return 0, err 18 | } 19 | 20 | return block, nil 21 | } 22 | 23 | func (p *Platform) GetBlockByNumber(num int64) (*types.Block, error) { 24 | var normalized types.Txs 25 | var txs []viewblock.Tx 26 | 27 | header, rpcTxs, err := p.rpcClient.GetTxInBlock(num) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | for _, tx := range rpcTxs { 33 | if tx := TxFromRpc(tx, header); tx != nil { 34 | txs = append(txs, *tx) 35 | } 36 | } 37 | 38 | for _, srcTx := range txs { 39 | tx := Normalize(&srcTx) 40 | normalized = append(normalized, tx) 41 | } 42 | 43 | block := types.Block{ 44 | Number: num, 45 | Txs: normalized, 46 | } 47 | return &block, nil 48 | } 49 | -------------------------------------------------------------------------------- /platform/zilliqa/crypto.go: -------------------------------------------------------------------------------- 1 | package zilliqa 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "strings" 7 | 8 | "github.com/btcsuite/btcutil/bech32" 9 | ) 10 | 11 | const HRP string = "zil" 12 | 13 | func EncodePublicKeyToAddress(hexString string) string { 14 | hexString = strings.TrimPrefix(hexString, "0x") 15 | bytes, err := hex.DecodeString(hexString) 16 | if err != nil { 17 | return "" 18 | } 19 | keyHash := sha256.Sum256(bytes) 20 | return EncodeKeyHashToAddress(keyHash[12:]) 21 | } 22 | 23 | func EncodeKeyHashToAddress(keyHash []byte) string { 24 | conv, err := bech32.ConvertBits(keyHash, 8, 5, true) 25 | if err != nil { 26 | return "" 27 | } 28 | encoded, err := bech32.Encode(HRP, conv) 29 | if err != nil { 30 | return "" 31 | } 32 | return encoded 33 | } 34 | -------------------------------------------------------------------------------- /platform/zilliqa/mocks/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "0xd44413c79e7518152f3b05ef1edff8ef59afd06119b16d09c8bc72e94fed7843", 3 | "blockHeight": 104282, 4 | "from": "0x88af5ba10796d9091d6893eed4db23ef0bbbca37", 5 | "to": "0x7fccacf066a5f26ee3affc2ed1fa9810deaa632c", 6 | "value": "7997000000000", 7 | "fee": "1000000000", 8 | "timestamp": 1557889788637, 9 | "signature": "0xF0F159C5B47079E36AABC7693E61FEE9D104BDE34F4FEADA62A5066F6363E05B382E65B9381CE8138CC6824A5B62CC60EDA8B7CF13A65264F8482279DF6F768B", 10 | "nonce": "3", 11 | "receiptSuccess": true, 12 | "events": [] 13 | } 14 | -------------------------------------------------------------------------------- /platform/zilliqa/model.go: -------------------------------------------------------------------------------- 1 | package zilliqa 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | "strconv" 7 | 8 | "github.com/trustwallet/blockatlas/platform/zilliqa/rpc" 9 | "github.com/trustwallet/blockatlas/platform/zilliqa/viewblock" 10 | ) 11 | 12 | func TxFromRpc(t rpc.Tx, header rpc.BlockHeader) *viewblock.Tx { 13 | // t.recipient is not parsed correctly. Empty strings. 14 | 15 | to, err := hex.DecodeString(t.ToAddr) 16 | if err != nil { 17 | return nil 18 | } 19 | 20 | timestamp, err := strconv.ParseUint(header.Timestamp, 10, 64) 21 | if err != nil { 22 | timestamp = 0 23 | } 24 | 25 | height, err := strconv.ParseUint(header.Number, 10, 64) 26 | if err != nil { 27 | height = 0 28 | } 29 | 30 | gasLimit, ok := new(big.Int).SetString(t.GasLimit, 10) 31 | if !ok { 32 | return nil 33 | } 34 | gasPrice, ok := new(big.Int).SetString(t.GasPrice, 10) 35 | if !ok { 36 | return nil 37 | } 38 | fee := new(big.Int).Mul(gasLimit, gasPrice) 39 | 40 | return &viewblock.Tx{ 41 | Hash: "0x" + t.ID, 42 | BlockHeight: height, 43 | From: EncodePublicKeyToAddress(t.SenderPubKey), 44 | To: EncodeKeyHashToAddress(to), 45 | Value: t.Amount, 46 | Fee: fee.String(), 47 | Timestamp: int64(timestamp / 1000), 48 | Signature: t.Signature, 49 | Nonce: t.Nonce, 50 | ReceiptSuccess: t.Receipt.Success, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /platform/zilliqa/rpc/model_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestBlockTxs_txs(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | b BlockTxs 12 | want []string 13 | }{ 14 | {"test 1 tx", BlockTxs{{"tx1"}, {}}, []string{"tx1"}}, 15 | {"test 2 txs 1", BlockTxs{{"tx1"}, {"tx2"}}, []string{"tx1", "tx2"}}, 16 | {"test 2 txs 2", BlockTxs{{"tx1", "tx2"}}, []string{"tx1", "tx2"}}, 17 | {"test 3 txs 1", BlockTxs{{"tx1", "tx2"}, {"tx3"}}, []string{"tx1", "tx2", "tx3"}}, 18 | {"test 3 txs 2", BlockTxs{{"tx1"}, {"tx2"}, {"tx3"}}, []string{"tx1", "tx2", "tx3"}}, 19 | {"test 4 txs", BlockTxs{{"tx1", "tx2"}, {"tx3"}, {"tx4"}}, []string{"tx1", "tx2", "tx3", "tx4"}}, 20 | } 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | if got := tt.b.txs(); !reflect.DeepEqual(got, tt.want) { 24 | t.Errorf("txs() = %v, want %v", got, tt.want) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /platform/zilliqa/transaction.go: -------------------------------------------------------------------------------- 1 | package zilliqa 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/platform/zilliqa/viewblock" 5 | "github.com/trustwallet/golibs/coin" 6 | "github.com/trustwallet/golibs/types" 7 | ) 8 | 9 | func (p *Platform) GetTxsByAddress(address string) (types.Txs, error) { 10 | var normalized types.Txs 11 | txs, err := p.client.GetTxsOfAddress(address) 12 | 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | for _, srcTx := range txs { 18 | tx := Normalize(&srcTx) 19 | if len(normalized) >= types.TxPerPage { 20 | break 21 | } 22 | normalized = append(normalized, tx) 23 | } 24 | 25 | return normalized, nil 26 | } 27 | 28 | func Normalize(srcTx *viewblock.Tx) (tx types.Tx) { 29 | tx = types.Tx{ 30 | ID: srcTx.Hash, 31 | Coin: coin.ZILLIQA, 32 | Date: srcTx.Timestamp / 1000, 33 | From: srcTx.From, 34 | To: srcTx.To, 35 | Fee: types.Amount(srcTx.Fee), 36 | Block: srcTx.BlockHeight, 37 | Status: types.StatusCompleted, 38 | Sequence: srcTx.NonceValue(), 39 | Meta: types.Transfer{ 40 | Value: types.Amount(srcTx.Value), 41 | Symbol: coin.Zilliqa().Symbol, 42 | Decimals: coin.Zilliqa().Decimals, 43 | }, 44 | } 45 | if !srcTx.ReceiptSuccess { 46 | tx.Status = types.StatusError 47 | } 48 | return tx 49 | } 50 | -------------------------------------------------------------------------------- /platform/zilliqa/viewblock/client.go: -------------------------------------------------------------------------------- 1 | package viewblock 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/trustwallet/golibs/client" 7 | "github.com/trustwallet/golibs/network/middleware" 8 | ) 9 | 10 | type Client struct { 11 | client.Request 12 | } 13 | 14 | func InitClient(api, apiKey string) Client { 15 | c := Client{client.InitClient(api, middleware.SentryErrorHandler)} 16 | c.Headers["X-APIKEY"] = apiKey 17 | return c 18 | } 19 | 20 | func (c *Client) GetTxsOfAddress(address string) (tx []Tx, err error) { 21 | path := fmt.Sprintf("addresses/%s/txs", address) 22 | err = c.Get(&tx, path, nil) 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /platform/zilliqa/viewblock/model.go: -------------------------------------------------------------------------------- 1 | package viewblock 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type Tx struct { 8 | Hash string `json:"hash"` 9 | BlockHeight uint64 `json:"blockHeight"` 10 | From string `json:"from"` 11 | To string `json:"to"` 12 | Value string `json:"value"` 13 | Fee string `json:"fee"` 14 | Timestamp int64 `json:"timestamp"` 15 | Signature string `json:"signature"` 16 | Nonce interface{} `json:"nonce"` 17 | ReceiptSuccess bool `json:"receiptSuccess"` 18 | } 19 | 20 | func (tx Tx) NonceValue() uint64 { 21 | switch n := tx.Nonce.(type) { 22 | case string: 23 | r, err := strconv.Atoi(n) 24 | if err != nil { 25 | break 26 | } 27 | return uint64(r) 28 | case int: 29 | return uint64(n) 30 | case float64: 31 | return uint64(n) 32 | } 33 | return 0 34 | } 35 | -------------------------------------------------------------------------------- /platform/zilliqa/viewblock/model_test.go: -------------------------------------------------------------------------------- 1 | package viewblock 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTx_NonceValue(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | nonce interface{} 11 | want uint64 12 | }{ 13 | {"test int", 0, 0}, 14 | {"test float", 3.4, 3}, 15 | {"test string", "33", 33}, 16 | {"test error string", "test", 0}, 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | tx := Tx{ 21 | Nonce: tt.nonce, 22 | } 23 | if got := tx.NonceValue(); got != tt.want { 24 | t.Errorf("NonceValue() = %v, want %v", got, tt.want) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /services/assets/client.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/trustwallet/golibs/network/middleware" 7 | 8 | "github.com/trustwallet/golibs/client" 9 | "github.com/trustwallet/golibs/coin" 10 | ) 11 | 12 | const ( 13 | URL = "https://assets.trustwalletapp.com/blockchains/" 14 | ) 15 | 16 | func GetValidatorsInfo(coin coin.Coin) (AssetValidators, error) { 17 | var results AssetValidators 18 | request := client.InitClient(URL+coin.Handle, middleware.SentryErrorHandler) 19 | err := request.GetWithCache(&results, "validators/list.json", nil, time.Hour*1) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return results, nil 24 | } 25 | -------------------------------------------------------------------------------- /services/assets/model.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | type ( 4 | AssetValidators []AssetValidator 5 | 6 | AssetValidatorMap map[string]AssetValidator 7 | 8 | AssetValidator struct { 9 | ID string `json:"id"` 10 | Name string `json:"name"` 11 | Description string `json:"description"` 12 | Website string `json:"website"` 13 | Payout ValidatorPayout `json:"payout,omitempty"` 14 | Status ValidatorStatus `json:"status,omitempty"` 15 | Staking StakingInfo `json:"staking,omitempty"` 16 | } 17 | 18 | ValidatorPayout struct { 19 | Commission float64 `json:"commission"` 20 | } 21 | 22 | ValidatorStatus struct { 23 | Disabled bool `json:"disabled"` 24 | } 25 | 26 | StakingInfo struct { 27 | MinDelegation float64 `json:"minDelegation"` 28 | } 29 | ) 30 | 31 | func (av AssetValidators) ToMap() AssetValidatorMap { 32 | validators := make(AssetValidatorMap) 33 | for _, v := range av { 34 | validators[v.ID] = v 35 | } 36 | return validators 37 | } 38 | 39 | func (av AssetValidators) activeValidators() AssetValidators { 40 | activeAssets := make(AssetValidators, 0) 41 | for _, a := range av { 42 | if !a.Status.Disabled { 43 | activeAssets = append(activeAssets, a) 44 | } 45 | } 46 | return activeAssets 47 | } 48 | -------------------------------------------------------------------------------- /services/notifier/base_test.go: -------------------------------------------------------------------------------- 1 | package notifier 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUnprefixedAddress(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | address string 11 | want string 12 | coin uint 13 | ok bool 14 | }{ 15 | { 16 | name: "Test invalid address", 17 | address: "60", 18 | want: "", 19 | coin: 0, 20 | ok: false, 21 | }, 22 | { 23 | name: "Test invalid address 2", 24 | address: "60_", 25 | want: "", 26 | coin: 0, 27 | ok: false, 28 | }, 29 | { 30 | name: "Test invalid coin", 31 | address: "asdf_0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8", 32 | want: "", 33 | coin: 0, 34 | ok: false, 35 | }, 36 | { 37 | name: "Test ETH", 38 | address: "60_0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8", 39 | want: "0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8", 40 | coin: 60, 41 | ok: true, 42 | }, 43 | } 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | got, got1, got2 := UnprefixedAddress(tt.address) 47 | if got != tt.want { 48 | t.Errorf("UnprefixedAddress() got = %v, want %v", got, tt.want) 49 | } 50 | if got1 != tt.coin { 51 | t.Errorf("UnprefixedAddress() got1 = %v, want %v", got1, tt.coin) 52 | } 53 | if got2 != tt.ok { 54 | t.Errorf("UnprefixedAddress() got2 = %v, want %v", got2, tt.ok) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /services/notifier/delivery.go: -------------------------------------------------------------------------------- 1 | package notifier 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/trustwallet/blockatlas/internal" 7 | "github.com/trustwallet/golibs/types" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "github.com/streadway/amqp" 11 | ) 12 | 13 | func GetTransactionsFromDelivery(delivery amqp.Delivery, service string) (types.Txs, error) { 14 | var transactions types.Txs 15 | 16 | if err := json.Unmarshal(delivery.Body, &transactions); err != nil { 17 | return nil, err 18 | } 19 | 20 | log.WithFields(log.Fields{"service": service, "notifications": len(transactions)}).Info("Consumed") 21 | 22 | return transactions, nil 23 | } 24 | 25 | func publishNotifications(notifications []types.TransactionNotification) error { 26 | raw, err := json.Marshal(notifications) 27 | if err != nil { 28 | return err 29 | } 30 | err = internal.TxNotifications.Publish(raw) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | log.WithFields(log.Fields{"service": Notifier, "notifications": len(notifications)}).Info("Notifications send") 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /services/tokenindexer/models.go: -------------------------------------------------------------------------------- 1 | package tokenindexer 2 | 3 | type ( 4 | Request struct { 5 | From int64 6 | } 7 | 8 | GetTokensByAddressRequest struct { 9 | AddressesByCoin map[string][]string `json:"addresses"` 10 | From uint `json:"from"` 11 | } 12 | 13 | GetTokensAsset struct { 14 | AssetId string `json:"asset_id"` 15 | CreatedAt int64 `json:"created_at"` 16 | UpdatedAt int64 `json:"updated_at"` 17 | } 18 | 19 | GetTokensByAddressResponse []GetTokensAsset 20 | ) 21 | -------------------------------------------------------------------------------- /tests/integration/db_test/db_run_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package db_test 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/trustwallet/blockatlas/db" 11 | "github.com/trustwallet/blockatlas/tests/integration/setup" 12 | ) 13 | 14 | var database *db.Instance 15 | 16 | func TestMain(m *testing.M) { 17 | database = setup.RunPgContainer() 18 | code := m.Run() 19 | setup.StopPgContainer() 20 | os.Exit(code) 21 | } 22 | 23 | func TestPgSetup(t *testing.T) { 24 | assert.NotNil(t, database) 25 | assert.NotNil(t, database.Gorm) 26 | } 27 | -------------------------------------------------------------------------------- /tests/integration/db_test/tracker_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package db_test 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/trustwallet/blockatlas/tests/integration/setup" 10 | ) 11 | 12 | func TestDb_SetBlock(t *testing.T) { 13 | setup.CleanupPgContainer(database.Gorm) 14 | 15 | assert.Nil(t, database.SetLastParsedBlockNumber("ethereum", 0)) 16 | 17 | block, err := database.GetLastParsedBlockNumber("ethereum") 18 | assert.Nil(t, err) 19 | assert.Equal(t, block.Height, int64(0)) 20 | 21 | assert.Nil(t, database.SetLastParsedBlockNumber("ethereum", 110)) 22 | 23 | newBlock, err := database.GetLastParsedBlockNumber("ethereum") 24 | assert.Nil(t, err) 25 | assert.Equal(t, newBlock.Height, int64(110)) 26 | } 27 | -------------------------------------------------------------------------------- /tests/integration/setup/mq.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/ory/dockertest" 8 | "github.com/trustwallet/golibs/network/mq" 9 | ) 10 | 11 | var ( 12 | mqResource *dockertest.Resource 13 | ) 14 | 15 | func runMQContainer() error { 16 | var err error 17 | pool, err := dockertest.NewPool("") 18 | if err != nil { 19 | log.Fatalf("Could not connect to docker: %s", err) 20 | } 21 | 22 | mqResource, err = pool.Run("rabbitmq", "latest", nil) 23 | if err != nil { 24 | log.Fatalf("Could not start resource: %s", err) 25 | } 26 | 27 | if err = pool.Retry(func() error { 28 | return mq.Init(fmt.Sprintf("amqp://localhost:%s", mqResource.GetPort("5672/tcp"))) 29 | }); err != nil { 30 | return err 31 | } 32 | return nil 33 | } 34 | 35 | func stopMQContainer() error { 36 | mq.Close() 37 | return mqResource.Close() 38 | } 39 | -------------------------------------------------------------------------------- /tests/integration/setup/setup.go: -------------------------------------------------------------------------------- 1 | package setup 2 | 3 | import ( 4 | "github.com/trustwallet/blockatlas/db" 5 | "log" 6 | ) 7 | 8 | func RunMQContainer() { 9 | if err := runMQContainer(); err != nil { 10 | log.Fatal(err) 11 | } 12 | } 13 | 14 | func StopMQContainer() { 15 | if err := stopMQContainer(); err != nil { 16 | log.Fatal(err) 17 | } 18 | } 19 | 20 | func RunPgContainer() *db.Instance { 21 | dbConn, err := runPgContainerAndInitConnection() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | return dbConn 26 | } 27 | 28 | func StopPgContainer() { 29 | if err := stopPgContainer(); err != nil { 30 | log.Fatal(err) 31 | } 32 | } 33 | --------------------------------------------------------------------------------