├── 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | {name} 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 | {/*
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 | 11 | 12 | 13 | { 14 | // Disable rates for now, it doesn't refresh correctly: 15 | } 16 | 17 | 18 | 19 | github 25 | Source Code 26 | 27 | 28 | 29 | {/* 30 | 31 | 32 | support 38 | Support 39 | 40 | 41 | 42 | 43 | 44 | 45 | stellar 51 | Stellar.org 52 | 53 | 54 | */} 55 | 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