├── public
├── googleb51c09f1a25c7afb.html
├── favicon.ico
├── img
│ ├── aex.png
│ ├── okex.png
│ ├── btc38.png
│ ├── kraken.png
│ ├── liquid.png
│ ├── paribu.png
│ ├── apay.io.png
│ ├── binance.png
│ ├── bittrex.png
│ ├── changelly.png
│ ├── equid.co.png
│ ├── exrates.png
│ ├── fchain.io.png
│ ├── golix.io.png
│ ├── indodax.png
│ ├── lapo.io.png
│ ├── moni.com.png
│ ├── nezly.com.png
│ ├── papayabot.png
│ ├── poloniex.png
│ ├── ripplefox.png
│ ├── stellarx.png
│ ├── ternio.io.png
│ ├── token.io.png
│ ├── anclax.com.png
│ ├── astral9.io.png
│ ├── coins.asia.png
│ ├── jetmint.org.png
│ ├── naobtc.com.png
│ ├── nrvcoin.in.png
│ ├── ntokens.com.png
│ ├── papayaswap.png
│ ├── pedity.com.png
│ ├── pr.network.png
│ ├── repocoin.io.png
│ ├── six.network.png
│ ├── stellarport.png
│ ├── stellarterm.png
│ ├── stronghold.png
│ ├── tonaira.com.png
│ ├── vcbear.net.png
│ ├── ximcoin.com.png
│ ├── xirkle.com.png
│ ├── anchorusd.com.png
│ ├── cryptotari.io.png
│ ├── fireflywallet.png
│ ├── frasindo.com.png
│ ├── interstellar.png
│ ├── irene.energy.png
│ ├── mobius.network.png
│ ├── ripplefox.com.png
│ ├── smartlands.io.png
│ ├── stemchain.io.png
│ ├── stronghold.co.png
│ ├── superlumen.org.png
│ ├── sureremit.co.png
│ ├── tempo.eu.com.png
│ ├── thewwallet.com.png
│ ├── winsome.gift.png
│ ├── wirexapp.com.png
│ ├── charnatoken.top.png
│ ├── collective21.org.png
│ ├── cowrie.exchange.png
│ ├── cryptomover.com.png
│ ├── flutterwave.com.png
│ ├── old.repocoin.io.png
│ ├── old.sureremit.co.png
│ ├── thefutbolcoin.io.png
│ ├── tontinetrust.com.png
│ ├── interstellar.exchange.png
│ ├── liquido.i-server.org.png
│ ├── support.svg
│ └── gh.svg
├── logo128.png
├── logo512.png
├── logo64.png
├── stellar.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── search
│ ├── search_asset.png
│ ├── search_ledger.png
│ ├── search_tx_hash.png
│ ├── search_account_public.png
│ ├── search_anchor_name_full.png
│ ├── search_stellar_address.png
│ └── search_anchor_name_partial.png
├── search.xml
├── manifest.json
├── logo.svg
├── sitemap.xml
├── index.html
└── keybase.txt
├── screenshots
├── assets.png
├── effects.png
├── header.png
├── payments.png
├── trades.png
├── new.style.png
├── opensearch.png
├── horizon.json.png
├── mobile.splash.png
├── stellar.toml.png
├── account.offers.png
├── inflation_pools.png
├── search_by_asset.png
├── search_by_anchor.png
└── mobile.add.homescreen.jpg
├── src
├── img
│ ├── lang-select.png
│ └── logo.svg
├── lib
│ ├── stellar.js
│ ├── __tests__
│ │ ├── csv.test.js
│ │ ├── __data__
│ │ │ ├── effects.js
│ │ │ └── pathPayments.js
│ │ ├── search.test.js
│ │ ├── utils.test.js
│ │ ├── stellar
│ │ │ └── utils.test.js
│ │ └── __snapshots__
│ │ │ └── csv.test.js.snap
│ ├── stellar
│ │ ├── networks.js
│ │ ├── utils.js
│ │ ├── __tests__
│ │ │ └── networks.test.js
│ │ ├── server.js
│ │ └── sdk.js
│ ├── csv.js
│ ├── search.js
│ └── utils.js
├── index.css
├── __mocks__
│ └── MockXHR.js
├── components
│ ├── operations
│ │ ├── Inflation.js
│ │ ├── Unrecognized.js
│ │ ├── BumpSequence.js
│ │ ├── AccountMerge.js
│ │ ├── ChangeTrust.js
│ │ ├── Trust.js
│ │ ├── AllowTrust.js
│ │ ├── PathPayment.js
│ │ ├── CreateAccount.js
│ │ ├── ManageData.js
│ │ ├── __tests__
│ │ │ ├── ManageData.test.js
│ │ │ └── __snapshots__
│ │ │ │ └── ManageData.test.js.snap
│ │ ├── Payment.js
│ │ ├── Offer.js
│ │ ├── SetOptions.js
│ │ └── Operation.js
│ ├── shared
│ │ ├── NewWindowIcon.js
│ │ ├── __tests__
│ │ │ ├── __snapshots__
│ │ │ │ ├── JSONButton.test.js.snap
│ │ │ │ ├── TitleWithJSONButton.test.js.snap
│ │ │ │ └── LumensRates.test.js.snap
│ │ │ ├── JSONButton.test.js
│ │ │ ├── TitleWithJSONButton.test.js
│ │ │ ├── LumensRates.test.js
│ │ │ └── AccountLink.test.js
│ │ ├── FormattedAmount.js
│ │ ├── JSONButton.js
│ │ ├── ExportButton.js
│ │ ├── HOCs.js
│ │ ├── StellarTomlBadge.js
│ │ ├── Spinner.js
│ │ ├── PagingControls.js
│ │ ├── TransactionHash.js
│ │ ├── TitleWithJSONButton.js
│ │ ├── Error.js
│ │ ├── Asset.js
│ │ ├── NoMatchError.js
│ │ ├── OperationType.js
│ │ ├── TitleWithLink.js
│ │ ├── InsecureNetworkError.js
│ │ ├── Logo.js
│ │ ├── TimeSynchronizedFormattedRelative.js
│ │ ├── Paging.js
│ │ ├── ClipboardCopy.js
│ │ ├── CSVExport.js
│ │ ├── InfoBanner.js
│ │ ├── LumensRates.js
│ │ └── AccountLink.js
│ ├── Accounts.js
│ ├── Trades.js
│ ├── Effects.js
│ ├── layout
│ │ ├── NetworkSelector.js
│ │ ├── Footer.js
│ │ ├── LanguageSelector.js
│ │ └── Header.js
│ ├── Ledgers.js
│ ├── Payments.js
│ ├── Operations.js
│ ├── Transactions.js
│ ├── LedgerTableContainer.js
│ ├── TransactionTableContainer.js
│ ├── Home.js
│ ├── LedgerTable.js
│ ├── EffectTable.js
│ ├── AccountTable.js
│ ├── Exchanges.js
│ ├── OfferTable.js
│ ├── Anchor.js
│ ├── Assets.js
│ ├── TransactionTable.js
│ ├── Anchors.js
│ └── Transaction.js
├── __tests__
│ └── App.test.js
├── index.js
├── data
│ ├── inflation_pools.json
│ ├── distributers.js
│ ├── __tests__
│ │ └── known_accounts.test.js
│ ├── exchanges.json
│ └── known_accounts.js
└── registerServiceWorker.js
├── .travis.yml
├── static.json
├── .gitignore
├── config
├── animationShim.js
├── jest
│ ├── fileTransform.js
│ └── cssTransform.js
├── polyfills.js
├── paths.js
├── env.js
└── webpackDevServer.config.js
├── .eslintrc
├── banner.json
├── .github
└── issue_template.md
├── scripts
├── test.js
└── start.js
├── docker
└── nginx-defaults.conf
├── jest.config.js
├── Dockerfile
├── kubernetes
├── scripts
│ └── deploy_kubernetes_components
└── config
│ └── app.yaml
├── README.md
└── package.json
/public/googleb51c09f1a25c7afb.html:
--------------------------------------------------------------------------------
1 | google-site-verification: googleb51c09f1a25c7afb.html
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/aex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/aex.png
--------------------------------------------------------------------------------
/public/img/okex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/okex.png
--------------------------------------------------------------------------------
/public/logo128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/logo128.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/public/logo64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/logo64.png
--------------------------------------------------------------------------------
/public/stellar.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/stellar.ico
--------------------------------------------------------------------------------
/public/img/btc38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/btc38.png
--------------------------------------------------------------------------------
/public/img/kraken.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/kraken.png
--------------------------------------------------------------------------------
/public/img/liquid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/liquid.png
--------------------------------------------------------------------------------
/public/img/paribu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/paribu.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/img/apay.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/apay.io.png
--------------------------------------------------------------------------------
/public/img/binance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/binance.png
--------------------------------------------------------------------------------
/public/img/bittrex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/bittrex.png
--------------------------------------------------------------------------------
/public/img/changelly.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/changelly.png
--------------------------------------------------------------------------------
/public/img/equid.co.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/equid.co.png
--------------------------------------------------------------------------------
/public/img/exrates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/exrates.png
--------------------------------------------------------------------------------
/public/img/fchain.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/fchain.io.png
--------------------------------------------------------------------------------
/public/img/golix.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/golix.io.png
--------------------------------------------------------------------------------
/public/img/indodax.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/indodax.png
--------------------------------------------------------------------------------
/public/img/lapo.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/lapo.io.png
--------------------------------------------------------------------------------
/public/img/moni.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/moni.com.png
--------------------------------------------------------------------------------
/public/img/nezly.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/nezly.com.png
--------------------------------------------------------------------------------
/public/img/papayabot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/papayabot.png
--------------------------------------------------------------------------------
/public/img/poloniex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/poloniex.png
--------------------------------------------------------------------------------
/public/img/ripplefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/ripplefox.png
--------------------------------------------------------------------------------
/public/img/stellarx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/stellarx.png
--------------------------------------------------------------------------------
/public/img/ternio.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/ternio.io.png
--------------------------------------------------------------------------------
/public/img/token.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/token.io.png
--------------------------------------------------------------------------------
/screenshots/assets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/assets.png
--------------------------------------------------------------------------------
/screenshots/effects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/effects.png
--------------------------------------------------------------------------------
/screenshots/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/header.png
--------------------------------------------------------------------------------
/screenshots/payments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/payments.png
--------------------------------------------------------------------------------
/screenshots/trades.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/trades.png
--------------------------------------------------------------------------------
/src/img/lang-select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/src/img/lang-select.png
--------------------------------------------------------------------------------
/public/img/anclax.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/anclax.com.png
--------------------------------------------------------------------------------
/public/img/astral9.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/astral9.io.png
--------------------------------------------------------------------------------
/public/img/coins.asia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/coins.asia.png
--------------------------------------------------------------------------------
/public/img/jetmint.org.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/jetmint.org.png
--------------------------------------------------------------------------------
/public/img/naobtc.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/naobtc.com.png
--------------------------------------------------------------------------------
/public/img/nrvcoin.in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/nrvcoin.in.png
--------------------------------------------------------------------------------
/public/img/ntokens.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/ntokens.com.png
--------------------------------------------------------------------------------
/public/img/papayaswap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/papayaswap.png
--------------------------------------------------------------------------------
/public/img/pedity.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/pedity.com.png
--------------------------------------------------------------------------------
/public/img/pr.network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/pr.network.png
--------------------------------------------------------------------------------
/public/img/repocoin.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/repocoin.io.png
--------------------------------------------------------------------------------
/public/img/six.network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/six.network.png
--------------------------------------------------------------------------------
/public/img/stellarport.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/stellarport.png
--------------------------------------------------------------------------------
/public/img/stellarterm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/stellarterm.png
--------------------------------------------------------------------------------
/public/img/stronghold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/stronghold.png
--------------------------------------------------------------------------------
/public/img/tonaira.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/tonaira.com.png
--------------------------------------------------------------------------------
/public/img/vcbear.net.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/vcbear.net.png
--------------------------------------------------------------------------------
/public/img/ximcoin.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/ximcoin.com.png
--------------------------------------------------------------------------------
/public/img/xirkle.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/xirkle.com.png
--------------------------------------------------------------------------------
/screenshots/new.style.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/new.style.png
--------------------------------------------------------------------------------
/screenshots/opensearch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/opensearch.png
--------------------------------------------------------------------------------
/public/img/anchorusd.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/anchorusd.com.png
--------------------------------------------------------------------------------
/public/img/cryptotari.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/cryptotari.io.png
--------------------------------------------------------------------------------
/public/img/fireflywallet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/fireflywallet.png
--------------------------------------------------------------------------------
/public/img/frasindo.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/frasindo.com.png
--------------------------------------------------------------------------------
/public/img/interstellar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/interstellar.png
--------------------------------------------------------------------------------
/public/img/irene.energy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/irene.energy.png
--------------------------------------------------------------------------------
/public/img/mobius.network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/mobius.network.png
--------------------------------------------------------------------------------
/public/img/ripplefox.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/ripplefox.com.png
--------------------------------------------------------------------------------
/public/img/smartlands.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/smartlands.io.png
--------------------------------------------------------------------------------
/public/img/stemchain.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/stemchain.io.png
--------------------------------------------------------------------------------
/public/img/stronghold.co.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/stronghold.co.png
--------------------------------------------------------------------------------
/public/img/superlumen.org.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/superlumen.org.png
--------------------------------------------------------------------------------
/public/img/sureremit.co.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/sureremit.co.png
--------------------------------------------------------------------------------
/public/img/tempo.eu.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/tempo.eu.com.png
--------------------------------------------------------------------------------
/public/img/thewwallet.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/thewwallet.com.png
--------------------------------------------------------------------------------
/public/img/winsome.gift.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/winsome.gift.png
--------------------------------------------------------------------------------
/public/img/wirexapp.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/wirexapp.com.png
--------------------------------------------------------------------------------
/screenshots/horizon.json.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/horizon.json.png
--------------------------------------------------------------------------------
/screenshots/mobile.splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/mobile.splash.png
--------------------------------------------------------------------------------
/screenshots/stellar.toml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/stellar.toml.png
--------------------------------------------------------------------------------
/public/img/charnatoken.top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/charnatoken.top.png
--------------------------------------------------------------------------------
/public/img/collective21.org.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/collective21.org.png
--------------------------------------------------------------------------------
/public/img/cowrie.exchange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/cowrie.exchange.png
--------------------------------------------------------------------------------
/public/img/cryptomover.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/cryptomover.com.png
--------------------------------------------------------------------------------
/public/img/flutterwave.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/flutterwave.com.png
--------------------------------------------------------------------------------
/public/img/old.repocoin.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/old.repocoin.io.png
--------------------------------------------------------------------------------
/public/img/old.sureremit.co.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/old.sureremit.co.png
--------------------------------------------------------------------------------
/public/img/thefutbolcoin.io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/thefutbolcoin.io.png
--------------------------------------------------------------------------------
/public/img/tontinetrust.com.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/tontinetrust.com.png
--------------------------------------------------------------------------------
/public/search/search_asset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/search/search_asset.png
--------------------------------------------------------------------------------
/public/search/search_ledger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/search/search_ledger.png
--------------------------------------------------------------------------------
/screenshots/account.offers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/account.offers.png
--------------------------------------------------------------------------------
/screenshots/inflation_pools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/inflation_pools.png
--------------------------------------------------------------------------------
/screenshots/search_by_asset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/search_by_asset.png
--------------------------------------------------------------------------------
/public/search/search_tx_hash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/search/search_tx_hash.png
--------------------------------------------------------------------------------
/screenshots/search_by_anchor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/search_by_anchor.png
--------------------------------------------------------------------------------
/public/img/interstellar.exchange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/interstellar.exchange.png
--------------------------------------------------------------------------------
/public/img/liquido.i-server.org.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/img/liquido.i-server.org.png
--------------------------------------------------------------------------------
/public/search/search_account_public.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/search/search_account_public.png
--------------------------------------------------------------------------------
/screenshots/mobile.add.homescreen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/screenshots/mobile.add.homescreen.jpg
--------------------------------------------------------------------------------
/public/search/search_anchor_name_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/search/search_anchor_name_full.png
--------------------------------------------------------------------------------
/public/search/search_stellar_address.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/search/search_stellar_address.png
--------------------------------------------------------------------------------
/public/search/search_anchor_name_partial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pi-apps/pi-explorer/HEAD/public/search/search_anchor_name_partial.png
--------------------------------------------------------------------------------
/src/lib/stellar.js:
--------------------------------------------------------------------------------
1 | import networks from './stellar/networks'
2 | import sdk from './stellar/sdk'
3 | import Server from './stellar/server'
4 |
5 | export {networks, sdk, Server}
6 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 16px !important; /* override bootstrap */
3 | }
4 |
5 | body {
6 | margin: 0;
7 | padding: 0;
8 | font-family: sans-serif;
9 | }
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | language: node_js
3 | node_js:
4 | - 14
5 | cache:
6 | directories:
7 | - node_modules
8 | script:
9 | - npm test
10 | - npm run build
11 |
--------------------------------------------------------------------------------
/src/__mocks__/MockXHR.js:
--------------------------------------------------------------------------------
1 | const xhrMockClass = () => ({
2 | open: jest.fn(),
3 | send: jest.fn(),
4 | setRequestHeader: jest.fn(),
5 | })
6 |
7 | window.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass)
8 |
--------------------------------------------------------------------------------
/src/components/operations/Inflation.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormattedMessage} from 'react-intl'
3 |
4 | // no props - just runs inflation
5 | const Inflation = () =>
6 |
7 | export default Inflation
8 |
--------------------------------------------------------------------------------
/static.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": "build/",
3 | "routes": {
4 | "/**": "index.html"
5 | },
6 | "https_only": true,
7 | "headers": {
8 | "/search.xml": {
9 | "Content-Type": "application/opensearchdescription+xml"
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/shared/NewWindowIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Glyphicon from 'react-bootstrap/lib/Glyphicon'
3 |
4 | const NewWindowIcon = () => (
5 |
6 | )
7 |
8 | export default NewWindowIcon
9 |
--------------------------------------------------------------------------------
/src/components/operations/Unrecognized.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormattedMessage} from 'react-intl'
3 |
4 | const Unrecognized = ({type}) =>
10 |
11 | export default Unrecognized
12 |
--------------------------------------------------------------------------------
/src/components/shared/__tests__/__snapshots__/JSONButton.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders button with given url 1`] = `
4 | Array [
5 | ,
9 | ]
10 | `;
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | .vscode
16 | npm-debug.log*
17 | yarn-debug.log*
18 | yarn-error.log*
19 | yarn.lock
20 |
21 |
--------------------------------------------------------------------------------
/config/animationShim.js:
--------------------------------------------------------------------------------
1 | // This is a workaround for:
2 | // Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills
3 | // that occurs in tests after upgrading to React 16.
4 | global.requestAnimationFrame = (callback) => {
5 | setTimeout(callback, 0);
6 | };
7 |
--------------------------------------------------------------------------------
/src/components/operations/BumpSequence.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormattedMessage} from 'react-intl'
3 |
4 | const BumpSequence = props => (
5 |
11 | )
12 |
13 | export default BumpSequence
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "rules": {
4 | "react/jsx-wrap-multilines": "off",
5 | "jsx-a11y/anchor-has-content": "off",
6 | "comma-dangle": ["warn", "always-multiline"],
7 | "object-curly-spacing": ["warn", "never"],
8 | "quotes": ["warn", "single"],
9 | "semi": ["warn", "never"]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/shared/FormattedAmount.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import {formatAmount} from '../../lib/utils'
3 |
4 | // chop off any trailing 0s
5 | const FormattedAmount = ({amount}) => formatAmount(amount)
6 |
7 | FormattedAmount.propTypes = {
8 | amount: PropTypes.string.isRequired,
9 | }
10 |
11 | export default FormattedAmount
12 |
--------------------------------------------------------------------------------
/banner.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "\uD83D\uDCA1 Testnet will be reset on February 27th, 2019. See here for more information.",
3 | "expiry": 1553644800000
4 | }
5 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/operations/AccountMerge.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import AccountLink from '../shared/AccountLink'
3 | import {FormattedMessage} from 'react-intl'
4 |
5 | const AccountMerge = ({into}) =>
6 | ,
10 | }}
11 | />
12 |
13 | export default AccountMerge
14 |
--------------------------------------------------------------------------------
/src/components/operations/ChangeTrust.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Trust from './Trust'
3 | import {FormattedMessage} from 'react-intl'
4 |
5 | const ChangeTrust = props =>
6 |
7 |
13 |
14 |
15 | export default ChangeTrust
16 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | ### Please NOTE the following before creating an issue:
2 |
3 | WE CAN'T HELP WITH FUNDS NOT RECEIVED, DELAYED OR WRONG MEMOS - CONTACT THE EXCHANGE OR THE RECEIPIENT IN THESE CASES - Stellar Explorer IS JUST A READ ONLY VIEW OF THE LEDGER
4 |
5 | If your issue is with steexp.com itself or you have any suggestion or comment please enter the details below. Thank you!
6 |
7 | ## Issue Description
8 |
--------------------------------------------------------------------------------
/src/__tests__/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from '../App'
4 |
5 | import '../__mocks__/MockXHR.js'
6 |
7 | // avoid the call out to get the rate
8 | jest.mock('../components/shared/LumensRates', () => 'rates')
9 |
10 | it.skip('renders without crashing', () => {
11 | const div = document.createElement('div')
12 | ReactDOM.render(, div)
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/shared/JSONButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import BackendResourceBadgeButton from './BackendResourceBadgeButton'
3 |
4 | /**
5 | * 'JSON' button that when clicked will show the contents of the backend JSON
6 | * resource at 'url'.
7 | */
8 | const JSONButton = ({url, filterFn}) => (
9 |
10 | )
11 |
12 | export default JSONButton
13 |
--------------------------------------------------------------------------------
/src/lib/__tests__/csv.test.js:
--------------------------------------------------------------------------------
1 | import {jsonToCSV} from '../csv.js'
2 |
3 | import effectsRecords from './__data__/effects'
4 | import pathPaymentsRecords from './__data__/pathPayments'
5 |
6 | describe('jsonToCSV', () => {
7 | test('effects', () => {
8 | expect(jsonToCSV(effectsRecords)).toMatchSnapshot()
9 | })
10 | test('pathPayments', () => {
11 | expect(jsonToCSV(pathPaymentsRecords)).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/shared/__tests__/__snapshots__/TitleWithJSONButton.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders button with given url 1`] = `
4 | Array [
5 |
6 |
7 | Ledger
8 |
9 |
12 |
15 |
16 |
,
17 | ]
18 | `;
19 |
--------------------------------------------------------------------------------
/public/img/support.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Accounts.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Row from 'react-bootstrap/lib/Row'
4 |
5 | import AccountTable from './AccountTable'
6 |
7 | class Accounts extends React.Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 | }
18 |
19 | export default Accounts
20 |
--------------------------------------------------------------------------------
/src/components/operations/Trust.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import AccountLink from '../shared/AccountLink'
3 | import {FormattedMessage} from 'react-intl'
4 |
5 | const Trust = ({assetCode, children, trustee}) =>
6 |
7 | ,
12 | }}
13 | />
14 | {children}
15 |
16 |
17 | export default Trust
18 |
--------------------------------------------------------------------------------
/src/lib/stellar/networks.js:
--------------------------------------------------------------------------------
1 | const networks = {
2 | public: 'public',
3 | test: 'testnet',
4 | local: 'local',
5 | }
6 |
7 | const hostnameToNetworkType = hostname => {
8 | if (hostname === 'steexp.com' || hostname === 'publicnet.local')
9 | return networks.public
10 | else if (hostname === 'testnet.steexp.com' || hostname === 'testnet.local')
11 | return networks.test
12 | else return networks.local
13 | }
14 |
15 | export {networks as default, hostnameToNetworkType}
16 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import App from './App'
5 |
6 | // TODO: Re-enable service worker after renaming the PWA and updating its icon (see manifest.json)
7 | import registerServiceWorker from './registerServiceWorker'
8 | // import { unregister as unregisterServiceWorker } from './registerServiceWorker';
9 |
10 | ReactDOM.render(, document.getElementById('root'))
11 | registerServiceWorker();
12 |
13 | // unregisterServiceWorker()
14 |
--------------------------------------------------------------------------------
/src/components/shared/ExportButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormattedMessage} from 'react-intl'
3 |
4 | /**
5 | * 'Export data to CSV' button that when clicked will generate a CSV file
6 | * with the records and offer it in the browser for download.
7 | */
8 | const ExportButton = ({onClick, label}) => (
9 |
15 | )
16 |
17 | export default ExportButton
18 |
--------------------------------------------------------------------------------
/src/components/operations/AllowTrust.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormattedMessage} from 'react-intl'
3 | import AccountLink from '../shared/AccountLink'
4 | import Trust from './Trust'
5 |
6 | const AllowTrust = props => (
7 |
8 | ,
13 | }}
14 | />
15 |
16 | )
17 |
18 | export default AllowTrust
19 |
--------------------------------------------------------------------------------
/src/components/shared/HOCs.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {getContext} from 'recompose'
3 | import PropTypes from 'prop-types'
4 |
5 | const withEither = (
6 | conditionalRenderingFn,
7 | EitherComponent
8 | ) => Component => props =>
9 | conditionalRenderingFn(props)
10 | ?
11 | :
12 |
13 | // @see App.js which puts this stellar server handle on the context
14 | const withServer = getContext({server: PropTypes.object})
15 |
16 | export {withEither, withServer}
17 |
--------------------------------------------------------------------------------
/src/components/shared/__tests__/JSONButton.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {configure, shallow} from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 |
5 | import JSONButton from '../JSONButton'
6 |
7 | configure({adapter: new Adapter()})
8 |
9 | it('renders button with given url', () => {
10 | const url = 'https://somebackend.xyz/resource/12345'
11 |
12 | const btn = shallow()
13 | expect(btn.props().url).toEqual(url)
14 |
15 | expect(btn.getElements()).toMatchSnapshot()
16 | })
17 |
--------------------------------------------------------------------------------
/src/components/shared/StellarTomlBadge.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import BackendResourceBadgeButton from './BackendResourceBadgeButton'
4 |
5 | const Badge = ({domain}) => {
6 | const tomlUrl = `https://${domain}/.well-known/stellar.toml`
7 | return (
8 |
9 |
10 |
11 | )
12 | }
13 |
14 | Badge.propTypes = {
15 | domain: PropTypes.string.isRequired,
16 | }
17 |
18 | export default Badge
19 |
--------------------------------------------------------------------------------
/src/components/shared/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import MDSpinner from 'react-md-spinner'
3 | import {withEither} from './HOCs'
4 |
5 | const Spinner = () => (
6 |
7 |
14 |
15 | )
16 |
17 | const isLoading = props => props.isLoading === true
18 |
19 | const withSpinner = () => withEither(isLoading, Spinner)
20 |
21 | export {Spinner, withSpinner}
22 |
--------------------------------------------------------------------------------
/src/components/shared/PagingControls.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Pager from 'react-bootstrap/lib/Pager'
3 | import {FormattedMessage} from 'react-intl'
4 |
5 | const PagingControls = ({handleClickNext, handleClickPrev, hidePrev}) =>
6 |
7 | {!hidePrev &&
8 |
9 | ←
10 | }
11 |
12 | →
13 |
14 |
15 |
16 | export default PagingControls
17 |
--------------------------------------------------------------------------------
/src/components/shared/__tests__/TitleWithJSONButton.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {configure, shallow} from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 |
5 | import {TitleWithJSONButton} from '../TitleWithJSONButton'
6 |
7 | configure({adapter: new Adapter()})
8 |
9 | it('renders button with given url', () => {
10 | const url = 'https://somebackend.xyz/ledger/12345'
11 | const title = 'Ledger'
12 | const btn = shallow()
13 | expect(btn.find('JSONButton').props().url).toEqual(url)
14 | expect(btn.getElements()).toMatchSnapshot()
15 | })
16 |
--------------------------------------------------------------------------------
/public/search.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Blockexplorer
4 | Blockexplorer for the Pi Network
5 | UTF-8
6 | https://minepi.com/blockexplorer/favicon-32x32.png
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/shared/TransactionHash.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import {Link} from 'react-router-dom'
4 | import {shortHash} from '../../lib/utils'
5 |
6 | const TransactionHash = ({hash, compact = true}) => {
7 | const hashLabel = compact ? shortHash(hash) : hash
8 | const className = !compact ? 'monospace' : ''
9 | return (
10 |
11 | {hashLabel}
12 |
13 | )
14 | }
15 | TransactionHash.propTypes = {
16 | hash: PropTypes.string.isRequired,
17 | compact: PropTypes.bool,
18 | }
19 |
20 | export default TransactionHash
21 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Blockexplorer",
3 | "name": "Blockexplorer",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#000000",
8 | "icons": [
9 | {
10 | "src": "favicon.ico",
11 | "sizes": "32x32",
12 | "type": "image/x-icon"
13 | },
14 | {
15 | "src": "logo512.png",
16 | "sizes": "512x512",
17 | "type": "image/png"
18 | },
19 | {
20 | "src": "logo128.png",
21 | "sizes": "128x128",
22 | "type": "image/png"
23 | },
24 | {
25 | "src": "logo64.png",
26 | "sizes": "64x64",
27 | "type": "image/png"
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/shared/TitleWithJSONButton.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React from 'react'
3 | import JSONButton from './JSONButton'
4 |
5 | const TitleWithJSONButton = ({title, url}) => (
6 |
7 | {title}
8 |
9 |
10 |
11 |
12 | )
13 |
14 | TitleWithJSONButton.propTypes = {
15 | title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
16 | url: PropTypes.string.isRequired,
17 | }
18 |
19 | const titleWithJSONButton = (title, url) => {
20 | return
21 | }
22 |
23 | export {titleWithJSONButton, TitleWithJSONButton}
24 |
--------------------------------------------------------------------------------
/src/components/operations/PathPayment.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Payment from './Payment'
3 | import Asset from '../shared/Asset'
4 | import {FormattedMessage} from 'react-intl'
5 |
6 | const PathPayment = props => {
7 | const sourceAsset = (
8 |
13 | )
14 | return (
15 |
16 |
23 |
24 | )
25 | }
26 |
27 | export default PathPayment
28 |
--------------------------------------------------------------------------------
/src/components/operations/CreateAccount.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import {FormattedMessage} from 'react-intl'
4 | import AccountLink from '../shared/AccountLink'
5 | import FormattedAmount from '../shared/FormattedAmount'
6 |
7 | const CreateAccount = ({account, startingBalance}) => (
8 | ,
12 | balance: ,
13 | }}
14 | />
15 | )
16 |
17 | CreateAccount.propTypes = {
18 | account: PropTypes.string.isRequired,
19 | startingBalance: PropTypes.string.isRequired,
20 | }
21 |
22 | export default CreateAccount
23 |
--------------------------------------------------------------------------------
/public/img/gh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/shared/Error.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Row from 'react-bootstrap/lib/Row'
4 | import {FormattedMessage} from 'react-intl'
5 |
6 | const knownErrors = ['network']
7 |
8 | class Error extends React.Component {
9 | render() {
10 | const id = this.props.match.params.id
11 | return (
12 |
13 |
14 |
15 | {id && knownErrors.indexOf(id) !== -1 ? (
16 |
17 | ) : (
18 |
19 | )}
20 |
21 |
22 |
23 | )
24 | }
25 | }
26 |
27 | export default Error
28 |
--------------------------------------------------------------------------------
/src/components/shared/__tests__/__snapshots__/LumensRates.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`render negative change in rate 1`] = `
4 | Array [
5 |
6 | Test-π/USD:
7 | 0.025
8 |
9 |
16 | -1.52%
17 |
18 | ,
19 | ]
20 | `;
21 |
22 | exports[`render positive change in rate 1`] = `
23 | Array [
24 |
25 | Test-π/USD:
26 | 0.020
27 |
28 |
35 | +2.1%
36 |
37 | ,
38 | ]
39 | `;
40 |
--------------------------------------------------------------------------------
/src/components/Trades.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Panel from 'react-bootstrap/lib/Panel'
4 | import Row from 'react-bootstrap/lib/Row'
5 | import {injectIntl} from 'react-intl'
6 | import TradeTable from './TradeTable'
7 | import {setTitle} from '../lib/utils'
8 |
9 | class Trades extends React.Component {
10 | render() {
11 | setTitle('Trades')
12 | const {formatMessage} = this.props.intl
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 | }
24 |
25 | export default injectIntl(Trades)
26 |
--------------------------------------------------------------------------------
/src/lib/stellar/utils.js:
--------------------------------------------------------------------------------
1 | import {StrKey} from 'stellar-base/lib/strkey'
2 |
3 | const STROOPS_PER_LUMEN = 10000000
4 | const stroopsToLumens = stroops => stroops / STROOPS_PER_LUMEN
5 |
6 | // stellar federated address (eg. "stellar*fed.network")
7 | const isFederatedAddress = addr => /^[^*,]*\*[a-z0-9-.]*$/i.test(addr)
8 | const isMuxedAddress = addr => StrKey.isValidMed25519PublicKey(addr)
9 | const isPublicKey = keyStr => StrKey.isValidEd25519PublicKey(keyStr)
10 | const isSecretKey = keyStr => StrKey.isValidEd25519SecretSeed(keyStr)
11 | const isTxHash = hashStr => /^[0-9a-f]{64}$/i.test(hashStr)
12 |
13 | export {
14 | isFederatedAddress,
15 | isMuxedAddress,
16 | isPublicKey,
17 | isSecretKey,
18 | isTxHash,
19 | stroopsToLumens,
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/shared/Asset.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import AccountLink from './AccountLink'
4 |
5 | const Asset = ({code, issuer, type}) => {
6 | const isLumens = type === 'native'
7 | const propCode = isLumens ? 'Test-π' : code
8 | return (
9 |
10 | {propCode}{' '}
11 | {!isLumens && (
12 |
13 | []
14 |
15 | )}
16 |
17 | )
18 | }
19 |
20 | // For Test-π code and issuer aren't set. type will be 'native'
21 | Asset.propTypes = {
22 | code: PropTypes.string,
23 | issuer: PropTypes.string,
24 | type: PropTypes.string.isRequired,
25 | }
26 |
27 | export default Asset
28 |
--------------------------------------------------------------------------------
/src/components/Effects.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Panel from 'react-bootstrap/lib/Panel'
4 | import Row from 'react-bootstrap/lib/Row'
5 | import {injectIntl} from 'react-intl'
6 | import EffectTable from './EffectTable'
7 | import {setTitle} from '../lib/utils'
8 |
9 | class Effects extends React.Component {
10 | render() {
11 | setTitle('Effects')
12 | const {formatMessage} = this.props.intl
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 | }
24 |
25 | export default injectIntl(Effects)
26 |
--------------------------------------------------------------------------------
/src/components/layout/NetworkSelector.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {networks} from '../../lib/stellar'
3 |
4 | const NetworkButton = ({networkType, selectedNetworkType, switchNetworkType}) =>
5 |
11 |
12 | const NetworkSelector = props =>
13 |
14 | {
20 | }
21 |
22 |
23 | export default NetworkSelector
24 |
--------------------------------------------------------------------------------
/src/components/Ledgers.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Panel from 'react-bootstrap/lib/Panel'
4 | import Row from 'react-bootstrap/lib/Row'
5 | import {injectIntl} from 'react-intl'
6 | import LedgerTable from './LedgerTableContainer'
7 | import {setTitle} from '../lib/utils'
8 |
9 | class Ledgers extends React.Component {
10 | render() {
11 | setTitle('Ledgers')
12 | const {formatMessage} = this.props.intl
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 | }
24 |
25 | export default injectIntl(Ledgers)
26 |
--------------------------------------------------------------------------------
/src/components/Payments.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Panel from 'react-bootstrap/lib/Panel'
4 | import Row from 'react-bootstrap/lib/Row'
5 | import {injectIntl} from 'react-intl'
6 | import PaymentTable from './PaymentTable'
7 | import {setTitle} from '../lib/utils'
8 |
9 | class Payments extends React.Component {
10 | render() {
11 | const {formatMessage} = this.props.intl
12 | setTitle('Payments')
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 | }
24 |
25 | export default injectIntl(Payments)
26 |
--------------------------------------------------------------------------------
/src/components/Operations.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Panel from 'react-bootstrap/lib/Panel'
4 | import Row from 'react-bootstrap/lib/Row'
5 | import {injectIntl} from 'react-intl'
6 | import OperationTable from './OperationTable'
7 | import {setTitle} from '../lib/utils'
8 |
9 | class Operations extends React.Component {
10 | render() {
11 | setTitle('Operations')
12 | const {formatMessage} = this.props.intl
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 | }
24 |
25 | export default injectIntl(Operations)
26 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/img/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/shared/NoMatchError.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Row from 'react-bootstrap/lib/Row'
4 | import {FormattedMessage} from 'react-intl'
5 |
6 | class NoMatchError extends React.Component {
7 | render() {
8 | const id = this.props.match.params.id
9 | return (
10 |
11 |
12 |
13 | {id ? (
14 |
15 | ) : (
16 |
20 | )}
21 |
22 |
23 |
24 | )
25 | }
26 | }
27 |
28 | export default NoMatchError
29 |
--------------------------------------------------------------------------------
/src/components/Transactions.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Panel from 'react-bootstrap/lib/Panel'
4 | import Row from 'react-bootstrap/lib/Row'
5 | import {injectIntl} from 'react-intl'
6 | import TransactionTable from './TransactionTableContainer'
7 | import {setTitle} from '../lib/utils'
8 |
9 | class Transactions extends React.Component {
10 | render() {
11 | setTitle('Transactions')
12 | const {formatMessage} = this.props.intl
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 | }
24 |
25 | export default injectIntl(Transactions)
26 |
--------------------------------------------------------------------------------
/src/components/shared/__tests__/LumensRates.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {configure, shallow} from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 |
5 | import {LumensRates} from '../LumensRates'
6 |
7 | configure({adapter: new Adapter()})
8 |
9 | it('render positive change in rate', () => {
10 | const rate = shallow()
11 | const changeEl = rate.find('span[style]')
12 | expect(changeEl.props().style.color).toEqual('#00c292')
13 | expect(rate.getElements()).toMatchSnapshot()
14 | })
15 |
16 | it('render negative change in rate', () => {
17 | const rate = shallow()
18 | const changeEl = rate.find('span[style]')
19 | expect(changeEl.props().style.color).toEqual('#fb9678')
20 | expect(rate.getElements()).toMatchSnapshot()
21 | })
22 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | const argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23 | argv.push('--watch');
24 | }
25 |
26 |
27 | jest.run(argv);
28 |
--------------------------------------------------------------------------------
/src/components/shared/OperationType.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import {shortHash} from '../../lib/utils'
4 |
5 | const filterFor = (type) => {
6 | window.location.href = window.location.pathname + '?opTypeFilter=' + type + window.location.hash
7 | }
8 |
9 | const OperationType = ({type, compact = true}) => {
10 | const hashLabel = compact ? shortHash(type) : type
11 | const className = !compact ? 'monospace' : ''
12 | const fn = (event) => {
13 | event.preventDefault()
14 | filterFor(type)
15 | }
16 | return (
17 |
18 | {hashLabel}
19 |
20 | )
21 | }
22 | OperationType.propTypes = {
23 | type: PropTypes.string.isRequired,
24 | compact: PropTypes.bool,
25 | }
26 |
27 | export {OperationType as default, filterFor}
28 |
--------------------------------------------------------------------------------
/docker/nginx-defaults.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 | root /usr/share/nginx/html;
5 | index index.html index.htm;
6 |
7 | #access_log /var/log/nginx/host.access.log main;
8 |
9 | location /blockexplorer {
10 | alias /usr/share/nginx/html;
11 |
12 | # First attempt to serve the requested file, then fall back to index.html, which loads the React app.
13 | try_files $uri /blockexplorer/index.html;
14 |
15 | location ~ ^.+\.(html?)$ {
16 | add_header Cache-Control "public, max-age=300";
17 | }
18 | }
19 |
20 | # error_page 404 /index.html;
21 |
22 | # redirect server error pages to the static page /50x.html
23 | error_page 500 502 503 504 /50x.html;
24 | location = /50x.html {
25 | root /usr/share/nginx/html;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/shared/TitleWithLink.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Link} from 'react-router-dom'
3 | import PropTypes from 'prop-types'
4 |
5 | const linkStyle = {color: 'white', textDecoration: 'underline'}
6 |
7 | class TitleWithLink extends React.Component {
8 | render() {
9 | const {title, rightLinkLabel, rightLinkAddr} = this.props
10 | return (
11 |
12 |
13 | {title}
14 |
15 |
16 |
17 | {rightLinkLabel}
18 |
19 |
20 |
21 | )
22 | }
23 | }
24 |
25 | TitleWithLink.propTypes = {
26 | title: PropTypes.string.isRequired,
27 | rightLinkLabel: PropTypes.string.isRequired,
28 | rightLinkAddr: PropTypes.string.isRequired,
29 | }
30 |
31 | export default TitleWithLink
32 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | collectCoverageFrom: ['src/**/*.{js,jsx}'],
3 | setupFiles: [
4 | '/config/polyfills.js',
5 | '/config/animationShim.js',
6 | ],
7 | testMatch: [
8 | '/src/**/__tests__/**/*.js?(x)',
9 | '/src/**/?(*.)(spec|test).js?(x)',
10 | ],
11 | testPathIgnorePatterns: ['/__data__/'],
12 | testEnvironment: 'node',
13 | testURL: 'http://localhost',
14 | transform: {
15 | '^.+\\.(js|jsx)$': '/node_modules/babel-jest',
16 | '^.+\\.css$': '/config/jest/cssTransform.js',
17 | '^(?!.*\\.(js|jsx|css|json)$)': '/config/jest/fileTransform.js',
18 | },
19 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'],
20 | moduleNameMapper: {
21 | '^react-native$': 'react-native-web',
22 | },
23 | moduleFileExtensions: ['web.js', 'js', 'json', 'web.jsx', 'jsx', 'node'],
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/LedgerTableContainer.js:
--------------------------------------------------------------------------------
1 | import {compose} from 'recompose'
2 | import {withPaging} from './shared/Paging'
3 | import {withDataFetchingContainer} from './shared/DataFetchingContainer'
4 | import LedgerTable from './LedgerTable'
5 |
6 | const rspRecToPropsRec = rspRec => {
7 | return {
8 | sequence: rspRec.sequence,
9 | time: rspRec.closed_at,
10 | txCountSuccessful: rspRec.successful_transaction_count,
11 | txCountFailed: rspRec.failed_transaction_count,
12 | }
13 | }
14 |
15 | const fetchRecords = props => {
16 | const builder = props.server.ledgers()
17 | builder.limit(props.limit)
18 | builder.order('desc')
19 | return builder.call()
20 | }
21 |
22 | const callBuilder = props => props.server.ledgers()
23 |
24 | const enhance = compose(
25 | withPaging(),
26 | withDataFetchingContainer(fetchRecords, rspRecToPropsRec, callBuilder)
27 | )
28 | export default enhance(LedgerTable)
29 |
--------------------------------------------------------------------------------
/src/components/shared/InsecureNetworkError.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Row from 'react-bootstrap/lib/Row'
4 | import {FormattedMessage} from 'react-intl'
5 |
6 | class InsecureNetworkError extends React.Component {
7 | render() {
8 | const uri = this.props.location.search.replace('?', '')
9 | return (
10 |
11 |
12 |
13 |
14 | {uri ? (
15 |
16 | ) : (
17 |
20 | )}
21 |
22 |
23 |
24 |
25 | )
26 | }
27 | }
28 |
29 | export default InsecureNetworkError
30 |
--------------------------------------------------------------------------------
/src/data/inflation_pools.json:
--------------------------------------------------------------------------------
1 | {
2 | "GBL7AE2HGRNQSPWV56ZFLILXNT52QWSMOQGDBBXYOP7XKMQTCKVMX2ZL": {
3 | "name": "futuretense.io",
4 | "website": "https://pool.futuretense.io"
5 | },
6 | "GCCD6AJOYZCUAQLX32ZJF2MKFFAUJ53PVCFQI3RHWKL3V47QYE2BNAUT": {
7 | "name": "Lumenaut",
8 | "website": "https://lumenaut.net"
9 | },
10 | "GB56YLTH5SDOYTUGPWY5MXJ7VQTY7BEM2YVJZTN5O555VA6DJYCTY2MP": {
11 | "name": "MoonPool",
12 | "website": "https://moonpool.space"
13 | },
14 | "GAJOC4WSOL3VUHYTEQPSOY54LP3XDWBS3AEZZ4DH24NEOHBBMEQKK7I7": {
15 | "name": "Stellar Pool",
16 | "website": "https://stellarpool.net"
17 | },
18 | "GDCHDRSDOBRMSUDKRE2C4U4KDLNEATJPIHHR2ORFL5BSD56G4DQXL4VW": {
19 | "name": "StellarTerm",
20 | "website": "https://stellarterm.com"
21 | },
22 | "GA3FUYFOPWZ25YXTCA73RK2UGONHCO27OHQRSGV3VCE67UEPEFEDCOPA": {
23 | "name": "XLMPOOL",
24 | "website": "https://xlmpool.com"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/operations/ManageData.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormattedMessage} from 'react-intl'
3 | import PropTypes from 'prop-types'
4 | import truncate from 'lodash/truncate'
5 | import {base64Decode} from '../../lib/utils'
6 |
7 | const MSG_KEY_PREFIX = 'operation.manage.data'
8 |
9 | const ManageData = ({name, value}) => {
10 | const isRemove = value === ''
11 | return (
12 |
13 |
19 | {!isRemove &&
20 | }
26 |
27 | )
28 | }
29 |
30 | ManageData.propTypes = {
31 | name: PropTypes.string.isRequired,
32 | value: PropTypes.string,
33 | }
34 |
35 | export default ManageData
36 |
--------------------------------------------------------------------------------
/src/components/shared/Logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | // 2 supported logo forms
5 | const squareDimensions = {height: 75, width: 75}
6 | const rectangleDimensions = {height: 40, width: 150}
7 |
8 | // exchange image from anchor image
9 | const imagesInBoth = ['papayabot', 'papayaswap', 'ripplefox']
10 |
11 | const Logo = ({name, type = 'anchor'}) => {
12 | const nameLower = name.toLowerCase()
13 | const imgSrc = `${process.env.PUBLIC_URL}/img/${nameLower}.png`
14 | const dimen =
15 | type !== 'exchange' || imagesInBoth.indexOf(nameLower) !== -1
16 | ? squareDimensions
17 | : rectangleDimensions
18 | return (
19 |
20 |
27 |
28 | )
29 | }
30 |
31 | Logo.propTypes = {
32 | name: PropTypes.string.isRequired,
33 | type: PropTypes.string,
34 | }
35 |
36 | export default Logo
37 |
--------------------------------------------------------------------------------
/src/components/operations/__tests__/ManageData.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {configure, shallow} from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 |
5 | import ManageData from '../ManageData'
6 |
7 | configure({adapter: new Adapter()})
8 |
9 | it('decodes ordinary string values', () => {
10 | const link = shallow()
11 | expect(link.getElements()).toMatchSnapshot()
12 | })
13 |
14 | it('decodes utf8 string values', () => {
15 | const link = shallow()
16 | expect(link.getElements()).toMatchSnapshot()
17 | })
18 |
19 | it('truncates a long key and long value', () => {
20 | const aLongPrefix = 'some_really_long_12345678901234567890123456789'
21 | const aLongName = `name_${aLongPrefix}`
22 | const aLongValue = Buffer.from(`value_${aLongPrefix}`).toString('base64')
23 | const link = shallow()
24 | expect(link.getElements()).toMatchSnapshot()
25 | })
26 |
--------------------------------------------------------------------------------
/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | https://minepi.com/blockexplorer
6 |
7 |
8 |
13 |
14 |
15 | https://minepi.com/blockexplorer/blocks
16 |
17 |
18 |
19 |
20 | https://minepi.com/blockexplorer/txs
21 |
22 |
23 |
38 |
39 |
40 | https://minepi.com/blockexplorer/payments
41 |
42 |
43 |
53 |
54 |
--------------------------------------------------------------------------------
/src/lib/__tests__/__data__/effects.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | id: '0000774537222303745-0000000002',
4 | account: 'GCOLHJY3DMPDSZW7PNK4RWBQMCYE5Q67W7TT44SD46RKTVDEAUJZTNE6',
5 | type: 'account_debited',
6 | typeI: 3,
7 | createdAt: '2018-10-15T03:06:14Z',
8 | assetType: 'credit_alphanum4',
9 | assetCode: 'HNY',
10 | assetIssuer: 'GCOLHJY3DMPDSZW7PNK4RWBQMCYE5Q67W7TT44SD46RKTVDEAUJZTNE6',
11 | amount: '1000000000.0000000',
12 | },
13 | {
14 | id: '0000773746948313089-0000000003',
15 | account: 'GCOLHJY3DMPDSZW7PNK4RWBQMCYE5Q67W7TT44SD46RKTVDEAUJZTNE6',
16 | type: 'signer_created',
17 | typeI: 10,
18 | createdAt: '2018-10-15T02:50:22Z',
19 | weight: 1,
20 | publicKey: 'GCOLHJY3DMPDSZW7PNK4RWBQMCYE5Q67W7TT44SD46RKTVDEAUJZTNE6',
21 | key: '',
22 | },
23 | {
24 | id: '0000773746948313089-0000000001',
25 | account: 'GCOLHJY3DMPDSZW7PNK4RWBQMCYE5Q67W7TT44SD46RKTVDEAUJZTNE6',
26 | type: 'account_created',
27 | typeI: 0,
28 | createdAt: '2018-10-15T02:50:22Z',
29 | startingBalance: '10000.0000000',
30 | },
31 | ]
32 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Source: https://typeofnan.dev/how-to-serve-a-react-app-with-nginx-in-docker/
2 | # Multi-stage
3 | # 1) Node image for building app assets
4 | # 2) nginx stage to serve app assets
5 |
6 | FROM node:13-alpine AS base
7 |
8 | # Set working directory
9 | WORKDIR /home/app
10 |
11 | # Copy our node modules specification
12 | COPY ./package.json .
13 | COPY ./package-lock.json .
14 |
15 | # Install node modules and build assets
16 | RUN npm install
17 |
18 | #Copy all files from current directory to working dir in image
19 | COPY . .
20 |
21 | # Create a build of the app
22 | RUN npm run build
23 |
24 | # nginx state for serving content
25 | FROM nginx:alpine
26 |
27 | EXPOSE 80
28 |
29 | # Set working directory to nginx asset directory
30 | WORKDIR /usr/share/nginx/html
31 |
32 | # Remove default nginx static assets
33 | RUN rm -rf ./*
34 |
35 | # Copy static assets from builder stage
36 | COPY --from=base /home/app/build .
37 |
38 | COPY ./docker/nginx-defaults.conf /etc/nginx/conf.d/default.conf
39 |
40 | # Containers run nginx with global directives and daemon off
41 | ENTRYPOINT ["nginx", "-g", "daemon off;"]
--------------------------------------------------------------------------------
/src/components/operations/Payment.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormattedMessage} from 'react-intl'
3 | import PropTypes from 'prop-types'
4 | import Asset from '../shared/Asset'
5 | import AccountLink from '../shared/AccountLink'
6 | import FormattedAmount from '../shared/FormattedAmount'
7 |
8 | const Payment = ({
9 | amount,
10 | assetCode,
11 | assetIssuer,
12 | assetType,
13 | children,
14 | to,
15 | toMuxed,
16 | }) => (
17 |
18 | ,
22 | asset: ,
23 | recipient: ,
24 | }}
25 | />
26 | {children}
27 |
28 | )
29 |
30 | Payment.propTypes = {
31 | amount: PropTypes.string.isRequired,
32 | assetCode: PropTypes.string,
33 | assetIssuer: PropTypes.string,
34 | assetType: PropTypes.string.isRequired,
35 | to: PropTypes.string.isRequired,
36 | toMuxed: PropTypes.string,
37 | }
38 |
39 | export default Payment
40 |
--------------------------------------------------------------------------------
/src/lib/stellar/__tests__/networks.test.js:
--------------------------------------------------------------------------------
1 | import networks, {hostnameToNetworkType} from '../networks'
2 |
3 | describe('hostnameToNetwork', () => {
4 | it('detects network type correctly from the hostname', () => {
5 | // public network
6 | expect(hostnameToNetworkType('steexp.com')).toEqual(networks.public)
7 |
8 | // test network
9 | expect(hostnameToNetworkType('testnet.steexp.com')).toEqual(networks.test)
10 |
11 | // localhost for development
12 | expect(hostnameToNetworkType('localnet.local')).toEqual(networks.local)
13 | expect(hostnameToNetworkType('testnet.local')).toEqual(networks.test)
14 | expect(hostnameToNetworkType('publicnet.local')).toEqual(networks.public)
15 |
16 | // unknown hosts default to local
17 | expect(hostnameToNetworkType()).toEqual(networks.local)
18 | expect(hostnameToNetworkType('')).toEqual(networks.local)
19 | expect(hostnameToNetworkType('localhost')).toEqual(networks.local)
20 | expect(hostnameToNetworkType('0.0.0.0')).toEqual(networks.local)
21 | expect(hostnameToNetworkType('not.steexp.com')).toEqual(networks.local)
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/src/components/shared/TimeSynchronizedFormattedRelative.js:
--------------------------------------------------------------------------------
1 | import {FormattedRelative} from 'react-intl'
2 |
3 | /**
4 | * A variation on FormattedRelative that respects updates to the initialNow
5 | * property. see react-intl for the default behaviour.
6 |
7 | * This version allows synchronisation of multiple FormattedRelative time
8 | * components that were created at various times to a single baseline now value.
9 | */
10 | // export default class extends FormattedRelative {
11 | // componentWillReceiveProps({initialNow: nextNow}) {
12 | // this.setState({now: nextNow})
13 | // }
14 | // }
15 | import React from 'react'
16 | import PropTypes from 'prop-types'
17 | import {Link} from 'react-router-dom'
18 |
19 | const TimeSynchronizedFormattedRelative = ({hash,time}) => {
20 | return (
21 |
22 | { hash ?
23 | : }
24 |
25 | )
26 | }
27 |
28 | TimeSynchronizedFormattedRelative.propTypes = {
29 | hash: PropTypes.string,
30 | time: PropTypes.string,
31 | }
32 | export default TimeSynchronizedFormattedRelative
--------------------------------------------------------------------------------
/src/data/distributers.js:
--------------------------------------------------------------------------------
1 | import directory from '../data/directory'
2 | const {anchors} = directory
3 |
4 | /**
5 | * Register some known distributer accounts linking to thier issuer accounts.
6 | *
7 | * NOTE: where a single distributer/issuer pair is used for multiple
8 | * asset types an issuer lookup for just one asset is required to
9 | * make the link. (eg. apay.io:ETH)
10 | */
11 |
12 | const issuer = (name, asset) =>
13 | anchors[name].assets[asset].substring(asset.length + 1)
14 |
15 | export default {
16 | GDSNYE6WMDQQW7JNAIIFEIJ562GS76WGSKXG3K6DPXLRN3COA47JRAJH: issuer(
17 | 'smartlands.io',
18 | 'SLT'
19 | ),
20 | GDBWXSZDYO4C3EHYXRLCGU3NP55LUBEQO5K2RWIWWMXWVI57L7VUWSZA: issuer(
21 | 'apay.io',
22 | 'ETH'
23 | ),
24 | GCGJVS7JZ7AP54H5GJIDNKGDDCOQ34H6NZBV7VCBLW4VCD4JOWERABA5: issuer(
25 | 'ripplefox.com',
26 | 'CNY'
27 | ),
28 | GDW3CNKSP5AOTDQ2YCKNGC6L65CE4JDX3JS5BV427OB54HCF2J4PUEVG: issuer(
29 | 'funtracker.site',
30 | 'FUNT'
31 | ),
32 | GBF6JDOF7SKMKMSMXHBHLYBRMLZF5QF6YNGXWM3NMX3H3HDJ7VVPCHQR: issuer(
33 | 'nrvcoin.in',
34 | 'NRV'
35 | ),
36 | GBNDDA3CJ6WDRE36TDGDNTTVV3QET7MRDINP3HUMIKBPQBKQGITZ73T5: issuer(
37 | 'nrvcoin.in',
38 | 'NRV'
39 | ),
40 | }
41 |
--------------------------------------------------------------------------------
/src/lib/stellar/server.js:
--------------------------------------------------------------------------------
1 | import sdk from './sdk'
2 | import networks from './networks'
3 |
4 | export const defaultNetworkAddresses = {
5 | public: 'https://horizon.stellar.org',
6 | test: 'https://horizon-testnet.stellar.org',
7 | // local: 'http://localhost:8000',
8 | local: 'https://api.testnet.minepi.com',
9 | }
10 |
11 | /**
12 | * Wrap the stellar-sdk Server hiding setup of horizon addresses and adding
13 | * some helper functions. These helpers are more easily mocked for testing then
14 | * direct use of sdk fluent api.
15 | */
16 | class WrappedServer extends sdk.Server {
17 | constructor(networkType, networkAddress, storage) {
18 | try {
19 | // allowHttp: public/test use HTTPS; local can use HTTP
20 | super(networkAddress, {allowHttp: networkType === networks.local})
21 | } catch(err) {
22 | storage.removeItem('networkAddress')
23 | window.location.href = `/error/insecure-horizon-server/?${networkAddress}`
24 | };
25 | }
26 |
27 | //
28 | // Horizon url resolvers
29 | //
30 |
31 | accountURL = id => `${this.serverURL}accounts/${id}`
32 | effectURL = id => `${this.serverURL}operations/${id}/effects`
33 | ledgerURL = id => `${this.serverURL}ledgers/${id}`
34 | opURL = id => `${this.serverURL}operations/${id}`
35 | txURL = id => `${this.serverURL}transactions/${id}`
36 | }
37 |
38 | const Server = (...args)=> new WrappedServer(...args)
39 |
40 | export default Server
41 |
--------------------------------------------------------------------------------
/src/components/shared/Paging.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PagingControls from './PagingControls'
3 |
4 | const usePagingCondition = props => props.usePaging === true
5 |
6 | const withPaging = () => Component => {
7 | return class extends React.Component {
8 | state = {
9 | hide: false,
10 | page: 0,
11 | }
12 |
13 | handleClickNext = e =>
14 | this.setState({
15 | page: this.state.page + 1,
16 | })
17 |
18 | handleClickPrev = e =>
19 | this.setState({
20 | page: this.state.page >= 1 ? this.state.page - 1 : 0,
21 | })
22 |
23 | // let children dynamically hide the paging controls
24 | // eg. if a result set is 0 or small
25 | handleHide = () => {
26 | this.setState({hide: true})
27 | }
28 |
29 | render() {
30 | if (usePagingCondition(this.props) === false)
31 | return
32 | return (
33 |
34 | {!this.state.hide && (
35 |
40 | )}
41 |
46 |
47 | )
48 | }
49 | }
50 | }
51 |
52 | export {withPaging}
53 |
--------------------------------------------------------------------------------
/src/data/__tests__/known_accounts.test.js:
--------------------------------------------------------------------------------
1 | import accounts from '../known_accounts'
2 | import {isPublicKey} from '../../lib/stellar/utils'
3 |
4 | const findByName = name => {
5 | const addr = Object.keys(accounts).find(key => accounts[key].name === name)
6 | return {addr, account: accounts[addr]}
7 | }
8 |
9 | it('anchor account included', () => {
10 | const {addr: tonairaAddr, account: tonaira} = findByName('Tonaira')
11 | expect(isPublicKey(tonairaAddr)).toBe(true)
12 | expect(tonaira.name).toBe('Tonaira')
13 | expect(tonaira.website).toBe('https://tonaira.com/')
14 | expect(tonaira.type).toBe('issuer')
15 |
16 | // check basics of another
17 | const {addr: vcbearAddr, account: vcbear} = findByName('VCBear')
18 | expect(isPublicKey(vcbearAddr)).toBe(true)
19 | expect(vcbear).toBeDefined()
20 | expect(vcbear.name).toBe('VCBear')
21 | expect(vcbear.website).toBe('https://vcbear.net/')
22 | })
23 |
24 | it('standard exchange accounts are included', () => {
25 | const {addr: poloniexAddr, account: poloniex} = findByName('Poloniex')
26 | expect(isPublicKey(poloniexAddr)).toBe(true)
27 | expect(poloniex).toBeDefined()
28 | expect(poloniex.name).toBe('Poloniex')
29 | expect(poloniex.website).toBe('poloniex.com')
30 | expect(poloniex.type).toBe('exchange')
31 | })
32 |
33 | it('exchange accounts with logo override sets logo', () => {
34 | const {addr: papayaAddr} = findByName('PapayaBot')
35 | expect(isPublicKey(papayaAddr)).toBe(true)
36 | })
37 |
--------------------------------------------------------------------------------
/kubernetes/scripts/deploy_kubernetes_components:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # All the variables ($VARIABLE) are Gitlab pipeline variables
4 | # They are set at different places:
5 | # - $CI and $GITLAB ones are set by Gitlab
6 | # - $IMAGE_TAG is set in the gitlab-ci.yml file
7 |
8 | # Creates a kubernetes secret file
9 | kubectl create secret docker-registry gitlab-registry \
10 | --docker-server="$CI_REGISTRY" \
11 | --docker-username="$CI_DEPLOY_USER" \
12 | --docker-password="$CI_DEPLOY_PASSWORD" \
13 | --docker-email="$GITLAB_USER_EMAIL" \
14 | -o yaml --dry-run=client | kubectl apply -f -
15 |
16 |
17 | # Replaces variables in the configuration file by their actual value
18 | # Details: sed -ri "s|xxx|yyy|g" file
19 | # - 'xxx' is what we want to replace
20 | # - 'yyy' is what we want to put in place of 'xxx'
21 | # - 'file' is the file in which we want to make this replacement
22 | sed -ri \
23 | -e "s|__CI_PROJECT_PATH_SLUG__|$CI_PROJECT_PATH_SLUG|g" \
24 | -e "s|__CI_ENVIRONMENT_SLUG__|$CI_ENVIRONMENT_SLUG|g" \
25 | -e "s|__APP_IMAGE_TAG__|$APP_IMAGE_TAG|g" \
26 | -e "s|__APP_POD_CPU_LIMIT__|$APP_POD_CPU_LIMIT|g" \
27 | -e "s|__APP_POD_EPHEMERAL_STORAGE_LIMIT__|$APP_POD_EPHEMERAL_STORAGE_LIMIT|g" \
28 | -e "s|__APP_POD_MEMORY_LIMIT__|$APP_POD_MEMORY_LIMIT|g" \
29 | -e "s|__APP_MIN_REPLICA__|$APP_MIN_REPLICA|g" \
30 | -e "s|__APP_MAX_REPLICA__|$APP_MAX_REPLICA|g" \
31 | ./kubernetes/config/app.yaml
32 |
33 |
34 | # Applies the configuration file to deploy the app
35 | kubectl apply -f ./kubernetes/config/app.yaml
36 |
--------------------------------------------------------------------------------
/src/components/operations/__tests__/__snapshots__/ManageData.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`decodes ordinary string values 1`] = `
4 | Array [
5 |
6 |
14 |
22 | ,
23 | ]
24 | `;
25 |
26 | exports[`decodes utf8 string values 1`] = `
27 | Array [
28 |
29 |
37 |
45 | ,
46 | ]
47 | `;
48 |
49 | exports[`truncates a long key and long value 1`] = `
50 | Array [
51 |
52 |
60 |
68 | ,
69 | ]
70 | `;
71 |
--------------------------------------------------------------------------------
/src/components/shared/ClipboardCopy.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Glyphicon from 'react-bootstrap/lib/Glyphicon'
4 | import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'
5 | import Tooltip from 'react-bootstrap/lib/Tooltip'
6 | import CopyToClipboard from 'react-copy-to-clipboard'
7 |
8 | const CopyIcon = (
9 |
10 | )
11 | const TooltipCopy = Copy to Clipboard
12 | const TooltipCopied = (
13 |
14 | Copied!
15 |
16 | )
17 |
18 | class ClipboardCopy extends React.Component {
19 | static defaultProps = {
20 | text: '',
21 | }
22 |
23 | state = {
24 | copied: false,
25 | }
26 |
27 | constructor(props, context) {
28 | super(props, context)
29 | this.handleCopy = this.handleCopy.bind(this)
30 | }
31 |
32 | handleCopy() {
33 | this.setState({copied: true})
34 | setTimeout(() => this.setState({copied: false}), 10000)
35 | }
36 |
37 | render() {
38 | return (
39 |
44 |
45 | {CopyIcon}
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | ClipboardCopy.propTypes = {
53 | text: PropTypes.string,
54 | }
55 |
56 | export default ClipboardCopy
57 |
--------------------------------------------------------------------------------
/src/components/shared/CSVExport.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormattedMessage} from 'react-intl'
3 | import has from 'lodash/has'
4 |
5 | import ExportButton from './ExportButton'
6 |
7 | class CSVExport extends React.Component {
8 |
9 | static defaultProps = {
10 | limit: 100,
11 | page: 0,
12 | refresh: false,
13 | usePaging: false,
14 | }
15 |
16 | state = {
17 | cursor: 0,
18 | wasExportStarted: false,
19 | isExportingFinished: false,
20 | fetchedRecords: [],
21 | }
22 |
23 | render() {
24 | if (this.props.wasExportStarted) {
25 | return (
26 |
27 | {this.props.isExportingFinished === true ? (
28 | this.props.exportLimitExceeded === true ? (
29 |
32 | ) : (
33 |
0 ? 'csv-export.complete': 'csv-export.no-records'}
35 | values={{count: has(this.props, 'fetchedRecords') && this.props.fetchedRecords.length}} />
36 | )
37 | ) : (
38 |
39 | |
40 |
41 | )}
42 |
43 | )
44 | }
45 |
46 | return (
47 |
48 | )
49 | }
50 | }
51 |
52 | export default CSVExport
53 |
--------------------------------------------------------------------------------
/src/lib/csv.js:
--------------------------------------------------------------------------------
1 | import {saveAs} from './filesaver'
2 |
3 | // build and return csv string from array of column values
4 | const toCsvString = stringArr =>
5 | stringArr.reduce(
6 | (accumulated, val, idx) =>
7 | (accumulated += `"${
8 | typeof val === 'object' ? JSON.stringify(val).replace(/"/g, '""') : val
9 | }"${idx < stringArr.length - 1 ? ',' : ''}`),
10 | ''
11 | )
12 |
13 | const jsonToCSV = records => {
14 | const columns = []
15 | // get list of all columns across all records (as some record lists contain mixed structures)
16 | records.forEach(rec => {
17 | const newKeys = Object.keys(rec).filter(
18 | key => columns.indexOf(key) === -1 && typeof rec[key] !== 'function'
19 | )
20 | if (newKeys.length > 0) {
21 | columns.push(...newKeys)
22 | }
23 | })
24 |
25 | // map col name to col idx
26 | const colToIdx = columns.reduce((accumulated, curCol, curIdx) => {
27 | accumulated[curCol] = curIdx
28 | return accumulated
29 | }, {})
30 |
31 | const numCols = columns.length
32 | const headerCsv = toCsvString(columns)
33 |
34 | return records.reduce((csvStr, curRec) => {
35 | const row = new Array(numCols)
36 | row.fill('')
37 |
38 | Object.keys(curRec).forEach(key => (row[colToIdx[key]] = curRec[key]))
39 | csvStr = `${csvStr}\n${toCsvString(row)}`
40 |
41 | return csvStr
42 | }, headerCsv)
43 | }
44 |
45 | const exportCSV = records => {
46 | const csvData = jsonToCSV(records)
47 | const autoByteOrderMark = true
48 | saveAs(
49 | new Blob(['\ufeff', csvData], {type: 'text/csv;charset=utf-8'}),
50 | 'pi-blockexplorer-export.csv',
51 | autoByteOrderMark
52 | )
53 | }
54 |
55 | export {exportCSV, jsonToCSV}
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pi Explorer
2 |
3 | A block explorer for the [Pi Blockchain](https://minepi.com) based on Chris Hatch' repository.
4 |
5 | To use it, visit the Pi App [pi://blockchain.pi](pi://blockchain.pi) on the [Pi Browser](https://developers.minepi.com) or visit https://minepi.com/blockexplorer
6 |
7 |
8 | ## Exploring Private / Local Development Networks
9 |
10 | It connects to a local horizon instance at http://localhost:8000 by default. If you are running a local private network for development this is quite handy for browsing your changes to the ledger.
11 |
12 | Alternatively you can run locally connecting to the testnet or public network horizon instances. To do this define these aliases to localhost:
13 |
14 | ```
15 | 127.0.1.1 testnet.local # for steexp use testnet horizon
16 | 127.0.1.1 publicnet.local # for steexp use mainnet horizon
17 | ```
18 |
19 | Navigate to http://testnet.local:3000 or http://publicnet.local:3000 to select the network your interesting in exploring.
20 |
21 | ## Development
22 |
23 | See the section [Exploring Private / Local Development Networks](#private-networks) for connecting to different backend networks. By default steexp will look for a local instance of horizon.
24 |
25 | Start:
26 |
27 | ```
28 | npm i && npm start
29 | ```
30 |
31 | Test:
32 |
33 | ```
34 | npm i && npm test
35 | ```
36 |
37 | Build:
38 |
39 | ```
40 | npm i && npm run build
41 | ```
42 |
43 | ## Languages
44 |
45 | Use the language selector in the top right corner to change the language.
46 |
47 | Translation files are here:
48 | https://github.com/pi-apps/pi-explorer/tree/master/src/languages
49 |
50 | Submit pull requests with new languages or languages fixes if you like.
51 |
52 |
--------------------------------------------------------------------------------
/src/components/shared/InfoBanner.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Col from 'react-bootstrap/lib/Col'
3 | import Grid from 'react-bootstrap/lib/Grid'
4 | import Row from 'react-bootstrap/lib/Row'
5 |
6 | // import FetchPonyfill from 'fetch-ponyfill'
7 | // import isEmpty from 'lodash/isEmpty'
8 |
9 | // const fetch = FetchPonyfill().fetch
10 |
11 | // const BANNER_JSON =
12 | // 'https://raw.githubusercontent.com/chatch/stellarexplorer/master/banner.json'
13 |
14 | const InfoBanner = ({message}) => (
15 |
16 |
17 |
18 |
19 | {/*
20 |
21 | */}
22 |
23 | {/* */}
24 |
25 |
26 |
27 | )
28 |
29 | // class InfoBannerContainer extends React.Component {
30 | // state = {}
31 |
32 | // componentDidMount() {
33 | // fetch(BANNER_JSON)
34 | // .then(rsp => rsp.json())
35 | // .then(({message, expiry}) => {
36 | // if (
37 | // !isEmpty(message) &&
38 | // Number.isInteger(Number(expiry)) &&
39 | // expiry > Date.now()
40 | // ) {
41 | // this.setState({message})
42 | // }
43 | // })
44 | // .catch(err => {
45 | // console.error(`Failed to fetch banner.json: [${err}]`)
46 | // console.error(`stack: [${err.stack}]`)
47 | // })
48 | // }
49 |
50 | // render() {
51 | // return
52 | // }
53 | // }
54 |
55 | export default InfoBanner
56 |
--------------------------------------------------------------------------------
/src/components/TransactionTableContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {compose} from 'recompose'
3 | import {withPaging} from './shared/Paging'
4 | import {withDataFetchingContainer} from './shared/DataFetchingContainer'
5 | import {withDataFetchingAllContainer} from './shared/DataFetchingAllContainer'
6 | import {isPublicKey} from '../lib/stellar/utils'
7 | import {isDefInt} from '../lib/utils'
8 | import TransactionTable from './TransactionTable'
9 | import CSVExport from './shared/CSVExport'
10 |
11 | const rspRecToPropsRec = rspRec => {
12 | return {
13 | hash: rspRec.hash,
14 | ledger: rspRec.ledger_attr,
15 | opCount: rspRec.operation_count,
16 | sourceAccount: rspRec.source_account,
17 | time: rspRec.created_at,
18 | }
19 | }
20 |
21 | const fetchRecords = props => {
22 | const builder = props.server.transactions()
23 | if (isDefInt(props, 'ledger')) builder.forLedger(props.ledger)
24 | if (isPublicKey(props.account)) builder.forAccount(props.account)
25 | builder.limit(props.limit)
26 | builder.order('desc')
27 | return builder.call()
28 | }
29 |
30 | const callBuilder = props => props.server.transactions()
31 |
32 | const enhance = compose(
33 | withPaging(),
34 | withDataFetchingContainer(fetchRecords, rspRecToPropsRec, callBuilder)
35 | )
36 |
37 | const ExportToCSVComponent = withDataFetchingAllContainer(fetchRecords)(
38 | CSVExport
39 | )
40 |
41 | const wrapHOC = Component => props => (
42 |
43 |
44 |
45 |
46 | {!props.noCSVExport && (
47 |
48 |
49 |
50 | )}
51 |
52 | )
53 |
54 | export default enhance(wrapHOC(TransactionTable))
55 |
--------------------------------------------------------------------------------
/src/components/layout/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Grid from 'react-bootstrap/lib/Grid'
3 | import Row from 'react-bootstrap/lib/Row'
4 | import Col from 'react-bootstrap/lib/Col'
5 | // import LumensRates from '../shared/LumensRates'
6 |
7 | class Footer extends React.PureComponent {
8 | render() {
9 | return (
10 |
56 | )
57 | }
58 | }
59 |
60 | export default Footer
61 |
--------------------------------------------------------------------------------
/kubernetes/config/app.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: blockexplorer-app-deployment
5 | annotations:
6 | app.gitlab.com/app: "__CI_PROJECT_PATH_SLUG__"
7 | app.gitlab.com/env: "__CI_ENVIRONMENT_SLUG__"
8 | labels:
9 | app: blockexplorer-app
10 | spec:
11 | selector:
12 | matchLabels:
13 | app: blockexplorer-app
14 | template:
15 | metadata:
16 | annotations:
17 | app.gitlab.com/app: "__CI_PROJECT_PATH_SLUG__"
18 | app.gitlab.com/env: "__CI_ENVIRONMENT_SLUG__"
19 | labels:
20 | app: blockexplorer-app
21 | spec:
22 | containers:
23 | - name: blockexplorer-app
24 | image: __APP_IMAGE_TAG__
25 | ports:
26 | - containerPort: 80
27 | resources:
28 | requests:
29 | cpu: __APP_POD_CPU_LIMIT__
30 | ephemeral-storage: __APP_POD_EPHEMERAL_STORAGE_LIMIT__
31 | memory: __APP_POD_MEMORY_LIMIT__
32 | imagePullSecrets:
33 | - name: gitlab-registry
34 | ---
35 | apiVersion: v1
36 | kind: Service
37 | metadata:
38 | name: blockexplorer-app-service
39 | spec:
40 | selector:
41 | app: blockexplorer-app
42 | type: LoadBalancer
43 | ports:
44 | - protocol: TCP
45 | port: 80
46 | targetPort: 80
47 | ---
48 | apiVersion: autoscaling/v2beta1
49 | kind: HorizontalPodAutoscaler
50 | metadata:
51 | name: blockexplorer-app-deployment-hpa
52 | spec:
53 | scaleTargetRef:
54 | apiVersion: apps/v1
55 | kind: Deployment
56 | name: blockexplorer-app-deployment
57 | minReplicas: __APP_MIN_REPLICA__
58 | maxReplicas: __APP_MAX_REPLICA__
59 | metrics:
60 | - resource:
61 | name: memory
62 | targetAverageUtilization: 80
63 | type: Resource
64 | - resource:
65 | name: cpu
66 | targetAverageUtilization: 80
67 | type: Resource
68 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
18 | Pi Blockexplorer
19 |
20 |
34 |
35 |
36 |
37 |
38 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/lib/search.js:
--------------------------------------------------------------------------------
1 | import isNaN from 'lodash/isNaN'
2 | import isEmpty from 'lodash/isEmpty'
3 | import isString from 'lodash/isString'
4 | import toNumber from 'lodash/toNumber'
5 | import {sdk} from './stellar'
6 |
7 | import {
8 | isFederatedAddress,
9 | isMuxedAddress,
10 | isPublicKey,
11 | isSecretKey,
12 | isTxHash,
13 | } from './stellar/utils'
14 | import directory from '../data/directory'
15 |
16 | const {anchors, assets} = directory
17 |
18 | const lcEquals = (str1, str2) =>
19 | !isEmpty(str1) && !isEmpty(str2) && str1.toLowerCase() === str2.toLowerCase()
20 |
21 | const lcIncludes = (str1, str2) =>
22 | !isEmpty(str1) &&
23 | !isEmpty(str2) &&
24 | str1.toLowerCase().includes(str2.toLowerCase())
25 |
26 | const searchAssetCode = code =>
27 | Object.keys(assets)
28 | .filter(key => lcEquals(assets[key].code, code.toUpperCase()))
29 | .map(key => assets[key])
30 |
31 | const searchAnchorName = name =>
32 | Object.keys(anchors).filter(
33 | key =>
34 | lcIncludes(anchors[key].name, name) ||
35 | lcIncludes(anchors[key].displayName, name)
36 | )
37 |
38 | const searchStrToPath = searchStr => {
39 | if (!isString(searchStr) || searchStr.trim() === '') return null
40 |
41 | const str = searchStr.trim()
42 |
43 | if (isPublicKey(str) || isFederatedAddress(str) || isMuxedAddress(str)) {
44 | return `/account/${str}`
45 | } else if (isTxHash(str)) {
46 | return `/tx/${str}`
47 | } else if (!isNaN(toNumber(str))) {
48 | return `/block/${toNumber(str)}`
49 | } else if (isSecretKey(str)) {
50 | const kp = sdk.Keypair.fromSecret(str)
51 | return `/account/${kp.publicKey()}`
52 | }
53 |
54 | // search by asset code
55 | const codeMatch = searchAssetCode(str)
56 | if (codeMatch.length > 0) {
57 | return `/asset/${str.toUpperCase()}`
58 | }
59 |
60 | // search by anchor name (exact or substring)
61 | const nameMatch = searchAnchorName(str)
62 | if (nameMatch.length > 0) {
63 | return `/anchor/${nameMatch[0]}`
64 | }
65 |
66 | return `/error/not-found/${searchStr}`
67 | }
68 |
69 | export {searchStrToPath}
70 |
--------------------------------------------------------------------------------
/src/components/shared/__tests__/AccountLink.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {configure, shallow} from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 |
5 | import knownAccounts from '../../../data/known_accounts'
6 | import AccountLink, {BaseAccountLink} from '../AccountLink'
7 |
8 | configure({adapter: new Adapter()})
9 |
10 | const ACC_KNOWN = Object.keys(knownAccounts).find(
11 | (key) => knownAccounts[key].displayName === 'NaoBTC'
12 | )
13 | const ACC_UNKNOWN = 'GCGG3CIRBG2TTBR4HYZJ7JLDRFKZIYOAHFXRWLU62CA2QN52P2SUQNPJ'
14 | const LABEL = 'Anchor'
15 |
16 | describe('AccountLink', () => {
17 | it('renders with label', () => {
18 | const link = shallow()
19 | expect(link.getElements()).toMatchInlineSnapshot(`
20 | Array [
21 | ,
26 | ]
27 | `)
28 | })
29 |
30 | it('renders short account for label when no label property', () => {
31 | const link = shallow()
32 | expect(link.getElements()).toMatchInlineSnapshot(`
33 | Array [
34 | ,
38 | ]
39 | `)
40 | })
41 |
42 | it('renders anchor name in italics if account a known anchor', () => {
43 | const link = shallow()
44 | expect(link.getElements()).toMatchInlineSnapshot(`
45 | Array [
46 |
55 | NaoBTC
56 |
57 | }
58 | subPath="GATEMHCCKCY67ZUCKTROYN24ZYT5GK4EQZ65JJLDHKHRUZI3EUEKMTCH"
59 | title="Base Address: GATEMHCCKCY67ZUCKTROYN24ZYT5GK4EQZ65JJLDHKHRUZI3EUEKMTCH"
60 | />,
61 | ]
62 | `)
63 | })
64 | })
65 |
66 | describe('MuxedAccount', () => {
67 | it.todo('muxed account renders muxed and base')
68 | })
69 |
--------------------------------------------------------------------------------
/src/components/shared/LumensRates.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import FetchPonyfill from 'fetch-ponyfill'
4 | const fetch = FetchPonyfill().fetch
5 |
6 | const FEED_URL = 'https://api.coinmarketcap.com/v1/ticker/stellar/'
7 | const UPDATE_INTERVAL = 5 * 60 * 1000
8 |
9 | class LumensRatesContainer extends React.PureComponent {
10 | componentDidMount() {
11 | this.updatePrice()
12 | this.intervalId = setInterval(
13 | () => this.updatePrice.bind(this),
14 | UPDATE_INTERVAL
15 | )
16 | }
17 |
18 | componentWillUnmount() {
19 | clearInterval(this.intervalId)
20 | }
21 |
22 | updatePrice() {
23 | fetch(FEED_URL)
24 | .then(rsp => rsp.json())
25 | .then(rspJson => {
26 | const lumens = rspJson[0]
27 | const newState = {
28 | change: lumens.percent_change_24h,
29 | usd: lumens.price_usd,
30 | }
31 | this.setState(newState)
32 | })
33 | .catch(err => {
34 | console.error(`Failed to fetch price: [${err}]`)
35 | console.error(`stack: [${err.stack}]`)
36 | })
37 | }
38 |
39 | render() {
40 | if (!this.state) return null
41 | return
42 | }
43 | }
44 |
45 | class LumensRates extends React.PureComponent {
46 | isPositive(changeNumStr) {
47 | const asFloat = Number.parseFloat(changeNumStr)
48 | return Number.isNaN(asFloat) === false && Number(asFloat) >= 0
49 | }
50 |
51 | renderChange(change) {
52 | const positive = this.isPositive(change)
53 | const valueStr = `${positive ? '+' : ''}${this.props.change}%`
54 | const style = {
55 | color: positive ? '#00c292' : '#fb9678',
56 | }
57 | return {valueStr}
58 | }
59 |
60 | render() {
61 | return (
62 |
63 | Test-π/USD: {this.props.usd} {this.renderChange(this.props.change)}
64 |
65 | )
66 | }
67 | }
68 |
69 | LumensRates.propTypes = {
70 | change: PropTypes.string.isRequired,
71 | usd: PropTypes.string.isRequired,
72 | }
73 |
74 | export {LumensRatesContainer as default, LumensRates}
75 |
--------------------------------------------------------------------------------
/src/lib/__tests__/__data__/pathPayments.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | _links: {
4 | self: {
5 | href: 'https://horizon.stellar.org/operations/92853980303990785',
6 | },
7 | transaction: {
8 | href:
9 | 'https://horizon.stellar.org/transactions/35e241e71a4769f94ed51b224c09f2cf9997e48c9b976674e3ccdf756c1a33cd',
10 | },
11 | effects: {
12 | href:
13 | 'https://horizon.stellar.org/operations/92853980303990785/effects',
14 | },
15 | succeeds: {
16 | href:
17 | 'https://horizon.stellar.org/effects?order=desc\u0026cursor=92853980303990785',
18 | },
19 | precedes: {
20 | href:
21 | 'https://horizon.stellar.org/effects?order=asc\u0026cursor=92853980303990785',
22 | },
23 | },
24 | id: '92853980303990785',
25 | paging_token: '92853980303990785',
26 | source_account: 'GALUJVTISLHBRDI3LYYMQFFIAILACUUZKIOLYWNDJEEYWECUQQNYQGOX',
27 | type: 'path_payment',
28 | type_i: 2,
29 | created_at: '2018-12-23T18:34:02Z',
30 | transaction_hash:
31 | '35e241e71a4769f94ed51b224c09f2cf9997e48c9b976674e3ccdf756c1a33cd',
32 | asset_type: 'credit_alphanum4',
33 | asset_code: 'BTC',
34 | asset_issuer: 'GBSTRH4QOTWNSVA6E4HFERETX4ZLSR3CIUBLK7AXYII277PFJC4BBYOG',
35 | from: 'GALUJVTISLHBRDI3LYYMQFFIAILACUUZKIOLYWNDJEEYWECUQQNYQGOX',
36 | to: 'GALUJVTISLHBRDI3LYYMQFFIAILACUUZKIOLYWNDJEEYWECUQQNYQGOX',
37 | amount: '0.0282862',
38 | path: [
39 | {
40 | asset_type: 'credit_alphanum4',
41 | asset_code: 'LTC',
42 | asset_issuer:
43 | 'GCSTRLTC73UVXIYPHYTTQUUSDTQU2KQW5VKCE4YCMEHWF44JKDMQAL23',
44 | },
45 | {
46 | asset_type: 'credit_alphanum4',
47 | asset_code: 'USD',
48 | asset_issuer:
49 | 'GBSTRUSD7IRX73RQZBL3RQUH6KS3O4NYFY3QCALDLZD77XMZOPWAVTUK',
50 | },
51 | ],
52 | source_amount: '0.0282762',
53 | source_max: '0.0282862',
54 | source_asset_type: 'credit_alphanum4',
55 | source_asset_code: 'BTC',
56 | source_asset_issuer:
57 | 'GBSTRH4QOTWNSVA6E4HFERETX4ZLSR3CIUBLK7AXYII277PFJC4BBYOG',
58 | },
59 | ]
60 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right