├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── _redirects
├── favicon.ico
└── index.html
├── src
├── App.vue
├── components
│ ├── GenericData.vue
│ ├── Header.vue
│ ├── JsonRenderer.vue
│ ├── LedgerNav.vue
│ ├── Loading.vue
│ └── ResolveHash.vue
├── main.js
├── plugins
│ ├── commands.js
│ ├── helpers.js
│ ├── ledger.js
│ └── xrpl.js
├── router
│ └── index.js
└── views
│ ├── Account.vue
│ ├── AmbiguousHash.vue
│ ├── CustomCommand.vue
│ ├── Home.vue
│ ├── HookNamespace.vue
│ ├── Ledger.vue
│ ├── LedgerEntry.vue
│ ├── NotFound.vue
│ ├── Sample.vue
│ └── Transaction.vue
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | '@vue/standard'
9 | ],
10 | parserOptions: {
11 | parser: 'babel-eslint'
12 | },
13 | rules: {
14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | name: Build explorer for local /ws
2 |
3 | on:
4 | push:
5 | branches: [ "main", "dev" ]
6 | pull_request:
7 | branches: [ "main", "dev" ]
8 | schedule:
9 | - cron: "1 0 1 * *" # minute hour dayofmonth month dayofweek, at least once a month
10 |
11 | concurrency:
12 | group: ${{ github.workflow }}-${{ github.ref }}
13 | cancel-in-progress: true
14 |
15 | jobs:
16 | build:
17 | runs-on: ubuntu-latest
18 |
19 | strategy:
20 | matrix:
21 | node-version: [20.x]
22 |
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v3
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 | cache: 'npm'
30 | - run: npm install
31 | - run: NODE_OPTIONS=--openssl-legacy-provider VUE_APP_WSS_ENDPOINT=/ws npm run build
32 |
33 | - name: Upload artifact
34 | uses: actions/upload-artifact@v4
35 | with:
36 | name: dist
37 | path: dist
38 |
39 | - name: Archive
40 | uses: thedoctor0/zip-release@0.7.5
41 | with:
42 | type: 'zip'
43 | filename: 'explorer.zip'
44 | directory: 'dist'
45 |
46 | - name: Release
47 | uses: softprops/action-gh-release@v2
48 | with:
49 | prerelease: false
50 | draft: false
51 | make_latest: true
52 | name: autorelease
53 | tag_name: autorelease
54 | files: |
55 | dist/explorer.zip
56 | fail_on_unmatched_files: true
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
25 | deploy*.sh
26 | .dccache
27 |
28 | .npmrc
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 XRP Ledger Foundation (Official)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XRP Ledger Technical Explorer [](https://app.netlify.com/sites/xrpl-technical-explorer/deploys)
2 |
3 | A technical (geeky) JSON viewing explorer for the XRP Ledger.
4 |
5 | ## BETA!
6 | Early beta of a new (technical) tx / ledger / object / hash explorer I'm working on, for XRPLF.
7 |
8 | - Mainnet: https://explorer.xrplf.org
9 | - Testnet: https://explorer-testnet.xrplf.org
10 | - Xahau Mainnet: https://explorer.xahau.network
11 | - Xahau Testnet: https://explorer.xahau-test.net
12 |
13 | It's easy to roll your own as the wss endpoint is an env. var.
14 |
15 | ## Custom nodes
16 |
17 | You can connect to a custom node by appending a websocket location without the two slashes after the protocol to the URL, e.g.:
18 | `https://explorer.my-domain.com/wss:xahau-test.net:444/`
19 |
20 | Lots to do, but the beginning is here. You can view (JSON, technical (!) explorer) ledgers, transactions, meta, etc. and click through, on ledger numbers, hashes, transaction hashes, accounts, etc.
21 |
22 | ## Project setup
23 | ```
24 | npm install
25 | ```
26 |
27 | Configure alternative WebSocket endpoint with the env. variable:
28 | ```
29 | VUE_APP_WSS_ENDPOINT
30 | ```
31 |
32 | E.g. `VUE_APP_WSS_ENDPOINT=wss://hooks-testnet-v3.xrpl-labs.com`
33 |
34 | ### Compiles and hot-reloads for development
35 | ```
36 | npm run serve
37 | ```
38 |
39 | ### Compiles and minifies for production
40 | ```
41 | npm run build
42 | ```
43 |
44 | ### Lints and fixes files
45 | ```
46 | npm run lint
47 | ```
48 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hooks-testnet-explorer",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.6.5",
12 | "jsonlint": "^1.6.3",
13 | "jsonlint-mod": "^1.7.6",
14 | "mitt": "^2.1.0",
15 | "ripple-binary-codec": "^1.6.0",
16 | "vue": "^2.6.11",
17 | "vue-codemirror": "^4.0.6",
18 | "vue-json-pretty": "^1.8.1",
19 | "vue-router": "^3.2.0",
20 | "xrpl-client": "^2.4.0"
21 | },
22 | "devDependencies": {
23 | "@vue/cli-plugin-babel": "~4.5.0",
24 | "@vue/cli-plugin-eslint": "^3.1.1",
25 | "@vue/cli-plugin-router": "~4.5.0",
26 | "@vue/cli-service": "^3.12.1",
27 | "@vue/eslint-config-standard": "^5.1.2",
28 | "babel-eslint": "^10.1.0",
29 | "eslint": "^6.7.2",
30 | "eslint-plugin-import": "^2.20.2",
31 | "eslint-plugin-node": "^11.1.0",
32 | "eslint-plugin-promise": "^4.2.1",
33 | "eslint-plugin-standard": "^4.0.0",
34 | "eslint-plugin-vue": "^6.2.2",
35 | "sass": "^1.26.5",
36 | "sass-loader": "^8.0.2",
37 | "vue-template-compiler": "^2.6.11"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XRPLF/XRPL-Technical-Explorer/1b7fedaef40e2aeb707ff2b225b839e75a5061fd/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Technical Ledger Explorer
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | This content can only be used when (client side) javascript execution is enabled.
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
24 |
25 |
121 |
--------------------------------------------------------------------------------
/src/components/GenericData.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $route.meta.title }}
4 |
5 |
6 |
7 |
8 |
9 | Load more...
10 |
11 |
12 | No data.
13 |
14 |
15 |
16 |
17 |
18 |
79 |
80 |
82 |
--------------------------------------------------------------------------------
/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | {{ nodeSelectLabel.match(/xahau/i) ? 'Xahau ': (nodeSelectLabel.match(/xrpl/i) ? 'XRPL ' : '') }}Explorer
12 | {{ nodeSelectLabel.match(/xahau/i) ? 'Xahau ': (nodeSelectLabel.match(/xrpl/i) ? 'XRP Ledger ' : '') }} Explorer
13 |
14 |
15 |
16 |
17 |
18 |
42 |
43 |
44 |
45 |
46 |
147 |
148 |
150 |
--------------------------------------------------------------------------------
/src/components/JsonRenderer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 | Click key to display & toggle between HEX and JSON + Binary Decoded view
18 | {{ defaultValue }}
19 |
20 |
21 | {{ defaultValue }}
22 |
23 |
24 |
25 | {{ defaultKey }}
26 |
27 | {{ defaultKey }}
28 |
29 |
30 |
31 |
32 |
33 |
168 |
169 |
171 |
--------------------------------------------------------------------------------
/src/components/LedgerNav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 | {{ ledger }}
14 | {{ typeof getTxCount(ledger) === 'number' ? getTxCount(ledger) : '…' }}
15 |
16 | ×
17 |
18 |
19 |
20 | {{ localnet ? 'Waiting for closed ledger' : 'Connecting' }}...
21 | Waiting for the next ledger to close...
22 |
23 |
24 | {{ l.ledger_index }}
25 |
26 |
27 | {{ l.ledger_index }}
28 | {{ l.txn_count }}
29 |
30 |
31 | Close ledger
32 |
33 |
34 |
35 |
36 |
107 |
108 |
113 |
--------------------------------------------------------------------------------
/src/components/Loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Yo, XRP Ledger!?
10 |
Loading...
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
24 |
26 |
--------------------------------------------------------------------------------
/src/components/ResolveHash.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
87 |
88 |
90 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import Xrpl from './plugins/xrpl'
4 | import Ledger from './plugins/ledger'
5 | import Mitt from 'mitt'
6 | import router from './router'
7 |
8 | Vue.config.productionTip = false
9 |
10 | Vue.prototype.$events = Mitt()
11 |
12 | Vue.use(Xrpl, { router })
13 | Vue.use(Ledger)
14 |
15 | router.$ws = Xrpl
16 |
17 | new Vue({
18 | router,
19 | render: h => h(App),
20 | watch: {
21 | '$route.params.ledger' () {
22 | if (this.$route.params.ledger) {
23 | this.$events.emit('route:ledger', this.$route.params.ledger)
24 | }
25 | }
26 | }
27 | }).$mount('#app')
28 |
--------------------------------------------------------------------------------
/src/plugins/commands.js:
--------------------------------------------------------------------------------
1 | export default {
2 | account_channels: {
3 | command: 'account_channels',
4 | account: 'rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH',
5 | destination_account: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
6 | ledger_index: 'validated'
7 | },
8 | account_currencies: {
9 | command: 'account_currencies',
10 | account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
11 | strict: true,
12 | ledger_index: 'validated'
13 | },
14 | account_info: {
15 | command: 'account_info',
16 | account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
17 | strict: true,
18 | ledger_index: 'current',
19 | queue: true
20 | },
21 | account_lines: {
22 | command: 'account_lines',
23 | account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'
24 | },
25 | account_namespace: {
26 | command: 'account_lines',
27 | account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
28 | namespace_id: '0000000000000000000000000000000000000000000000000000000000000000'
29 | },
30 | account_nfts: {
31 | command: 'account_nfts',
32 | account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'
33 | },
34 | server_definitions: {
35 | command: 'server_definitions'
36 | },
37 | account_objects: {
38 | command: 'account_objects',
39 | account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
40 | ledger_index: 'validated',
41 | type: 'state',
42 | deletion_blockers_only: false,
43 | limit: 10
44 | },
45 | account_offers: {
46 | command: 'account_offers',
47 | account: 'rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM'
48 | },
49 | account_tx: {
50 | command: 'account_tx',
51 | account: 'rLNaPoKeeBjZe2qs6x52yVPZpZ8td4dc6w',
52 | ledger_index_min: -1,
53 | ledger_index_max: -1,
54 | binary: false,
55 | limit: 2,
56 | forward: false
57 | },
58 | book_offers: {
59 | command: 'book_offers',
60 | taker: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
61 | taker_gets: {
62 | currency: 'XRP'
63 | },
64 | taker_pays: {
65 | currency: 'USD',
66 | issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
67 | },
68 | limit: 10
69 | },
70 | channel_verify: {
71 | command: 'channel_verify',
72 | channel_id: '5DB01B7FFED6B67E6B0414DED11E051D2EE2B7619CE0EAA6286D67A3A4D5BDB3',
73 | signature: '304402204EF0AFB78AC23ED1C472E74F4299C0C21F1B21D07EFC0A3838A420F76D783A400220154FB11B6F54320666E4C36CA7F686C16A3A0456800BBC43746F34AF50290064',
74 | public_key: 'aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3',
75 | amount: '1000000'
76 | },
77 | deposit_authorized: {
78 | command: 'deposit_authorized',
79 | source_account: 'rEhxGqkqPPSxQ3P25J66ft5TwpzV14k2de',
80 | destination_account: 'rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8',
81 | ledger_index: 'validated'
82 | },
83 | fee: {
84 | command: 'fee'
85 | },
86 | gateway_balances: {
87 | command: 'gateway_balances',
88 | account: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
89 | strict: true,
90 | hotwallet: ['rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ', 'ra7JkEzrgeKHdzKgo4EUUVBnxggY4z37kt'],
91 | ledger_index: 'validated'
92 | },
93 | ledger: {
94 | command: 'ledger',
95 | ledger_index: 'validated',
96 | full: false,
97 | accounts: false,
98 | transactions: false,
99 | expand: false,
100 | owner_funds: false
101 | },
102 | ledger_closed: {
103 | command: 'ledger_closed'
104 | },
105 | ledger_current: {
106 | command: 'ledger_current'
107 | },
108 | ledger_data: {
109 | ledger_hash: '842B57C1CC0613299A686D3E9F310EC0422C84D3911E5056389AA7E5808A93C8',
110 | command: 'ledger_data',
111 | limit: 5,
112 | binary: true
113 | },
114 | ledger_entry: {
115 | command: 'ledger_entry',
116 | index: '7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4',
117 | ledger_index: 'validated'
118 | },
119 | manifest: {
120 | command: 'manifest',
121 | public_key: 'nHUFE9prPXPrHcG3SkwP1UzAQbSphqyQkQK9ATXLZsfkezhhda3p'
122 | },
123 | noripple_check: {
124 | command: 'noripple_check',
125 | account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
126 | role: 'gateway',
127 | ledger_index: 'current',
128 | limit: 2,
129 | transactions: true
130 | },
131 | path_find: {
132 | command: 'path_find',
133 | subcommand: 'create',
134 | source_account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
135 | destination_account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
136 | destination_amount: {
137 | value: '0.001',
138 | currency: 'USD',
139 | issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
140 | }
141 | },
142 | ping: {
143 | command: 'ping'
144 | },
145 | random: {
146 | command: 'random'
147 | },
148 | ripple_path_find: {
149 | command: 'ripple_path_find',
150 | source_account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
151 | source_currencies: [
152 | {
153 | currency: 'XRP'
154 | },
155 | {
156 | currency: 'USD'
157 | }
158 | ],
159 | destination_account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
160 | destination_amount: {
161 | value: '0.001',
162 | currency: 'USD',
163 | issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
164 | }
165 | },
166 | server_info: {
167 | command: 'server_info'
168 | },
169 | server_state: {
170 | command: 'server_state'
171 | },
172 | submit: {
173 | command: 'submit',
174 | tx_blob: '1200002280000000240000001E61D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA968400000000000000B732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7447304502210095D23D8AF107DF50651F266259CC7139D0CD0C64ABBA3A958156352A0D95A21E02207FCF9B77D7510380E49FF250C21B57169E14E9B4ACFD314CEDC79DDD0A38B8A681144B4E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754'
175 | },
176 | subscribe: {
177 | command: 'subscribe',
178 | accounts: ['rrpNnNLKrartuEqfJGpqyDwPj1AFPg9vn1']
179 | },
180 | transaction_entry: {
181 | command: 'transaction_entry',
182 | tx_hash: 'C53ECF838647FA5A4C780377025FEC7999AB4182590510CA461444B207AB74A9',
183 | ledger_index: 56865245
184 | },
185 | tx: {
186 | command: 'tx',
187 | transaction: 'C53ECF838647FA5A4C780377025FEC7999AB4182590510CA461444B207AB74A9',
188 | binary: false
189 | },
190 | tx_history: {
191 | command: 'tx_history',
192 | start: 0
193 | },
194 | unsubscribe: {
195 | command: 'unsubscribe',
196 | streams: ['ledger', 'server', 'transactions', 'transactions_proposed'],
197 | accounts: ['rrpNnNLKrartuEqfJGpqyDwPj1AFPg9vn1'],
198 | accounts_proposed: ['rrpNnNLKrartuEqfJGpqyDwPj1AFPg9vn1'],
199 | books: [
200 | {
201 | taker_pays: {
202 | currency: 'XRP'
203 | },
204 | taker_gets: {
205 | currency: 'USD',
206 | issuer: 'rUQTpMqAF5jhykj4FExVeXakrZpiKF6cQV'
207 | },
208 | both: true
209 | }
210 | ]
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/plugins/helpers.js:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 |
3 | const hookHashToLedgerObjectHash = (hookHash) => {
4 | return crypto.createHash('SHA512')
5 | .update(Buffer.from('0044' + hookHash, 'hex'))
6 | .digest().slice(0, 32).toString('hex')
7 | .toUpperCase()
8 | }
9 |
10 | export {
11 | hookHashToLedgerObjectHash
12 | }
13 |
--------------------------------------------------------------------------------
/src/plugins/ledger.js:
--------------------------------------------------------------------------------
1 | export default {
2 | async install (Vue, options) {
3 | Vue.prototype.$ledger = new Vue({
4 | data () {
5 | return {
6 | ledgers: []
7 | }
8 | },
9 | computed: {
10 | list () {
11 | return this.ledgers.map(l => l.ledgerIndex)
12 | }
13 | },
14 | created () {
15 | this.$events.on('route:ledger', ledger => {
16 | // console.log('ledger.js', ledger)
17 | this.hydrate(ledger)
18 | })
19 | },
20 | methods: {
21 | purge (ledger) {
22 | const matched = this.ledgers.filter(l => l.ledgerIndex === Number(ledger))
23 | if (matched) {
24 | const index = this.ledgers.indexOf(matched[0])
25 | if (index > -1) {
26 | this.ledgers.splice(index, 1)
27 | }
28 | }
29 | // TODO: If no ledgers left: route back home
30 | },
31 | getLedger (ledger) {
32 | const ledgerIndex = Number(ledger)
33 | const matched = this.ledgers.filter(l => l.ledgerIndex === ledgerIndex)
34 | if (matched) {
35 | return matched[0]?.ledgerData
36 | } else {
37 | return this.hydrate(ledger)
38 | }
39 | },
40 | async hydrate (ledger) {
41 | const ledgerIndex = Number(ledger)
42 | if (this.ledgers.filter(l => l.ledgerIndex === ledgerIndex).length > 0) {
43 | console.log('Skip hydrating: known', ledger)
44 | } else {
45 | console.log('Hydrate', ledger)
46 |
47 | const existingRecordIndex = this.ledgers.map(l => l.ledgerIndex).indexOf(ledgerIndex)
48 | if (existingRecordIndex < 0) {
49 | this.ledgers.push({ ledgerIndex, ledgerData: {} })
50 | }
51 |
52 | const ledgerData = await this.$ws.send({ command: 'ledger', ledger_index: Number(ledger), transactions: true, expand: true })
53 | // console.log(ledgerData)
54 | Object.assign(this.ledgers[this.ledgers.map(l => l.ledgerIndex).indexOf(ledgerIndex)], {
55 | ledgerData
56 | })
57 |
58 | console.log('Hydrated', ledger)
59 | return ledgerData
60 | }
61 | }
62 | }
63 | })
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/plugins/xrpl.js:
--------------------------------------------------------------------------------
1 | import { XrplClient } from 'xrpl-client'
2 |
3 | export default {
4 | async install (Vue, options) {
5 | let _endpoint = process?.env?.VUE_APP_WSS_ENDPOINT
6 | const customEndpoint = options.router?.options?.endpoint
7 | ? options.router?.options?.endpoint
8 | : typeof _endpoint === 'string' && _endpoint.match(/^\/[a-z0-9]/)
9 | ? window.location.protocol.replace(/^http/, 'ws') + '//' + window.location.host + _endpoint
10 | : typeof _endpoint === 'string' && _endpoint.match(/^:[0-9]+[/a-z0-9]{0,}/)
11 | ? window.location.protocol.replace(/^http/, 'ws') + '//' + window.location.host.split(':')[0] + _endpoint
12 | : ''
13 |
14 | // console.log({
15 | // _endpoint,
16 | // customEndpoint
17 | // })
18 |
19 | if (customEndpoint !== '') _endpoint = options.router.options.endpoint = customEndpoint
20 | const endpoint = String(_endpoint || '')
21 | // console.log(endpoint)
22 | Vue.prototype.$ws = new XrplClient(endpoint)
23 | Vue.prototype.$localnet = false
24 |
25 | Vue.prototype.$ws.send({ command: 'server_info' }).then(r => {
26 | Vue.prototype.$localnet = r?.info?.last_close?.proposers === 0
27 | if (Vue.prototype.$localnet) {
28 | Vue.prototype.$events.emit('islocalnet', true)
29 | }
30 | })
31 |
32 | const net = {
33 | live: endpoint === '' || endpoint.match(/xrplcluster|xrpl\.ws|xrpl\.link|s[12]\.ripple\.com/),
34 | test: endpoint.match(/rippletest|\/testnet\.xrpl-labs/),
35 | xahaulive: endpoint.match(/xahau.*network/),
36 | xahautest: endpoint.match(/xahau.*test/),
37 | custom: customEndpoint !== ''
38 | }
39 |
40 | Vue.prototype.$net = net
41 |
42 | Vue.prototype.$ws.on('ledger', ledger => Vue.prototype.$events.emit('ledger', ledger))
43 | // console.info('Connecting @ `plugins/xrpl`')
44 | await Vue.prototype.$ws.ready()
45 | const state = Vue.prototype.$ws.getState()
46 | // console.info('Connected @ `plugins/xrpl`', state.server)
47 | Vue.prototype.$events.emit('connected', state.server.publicKey)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Home from '../views/Home.vue'
4 | import Ledger from '../views/Ledger.vue'
5 | import ResolveHash from '../components/ResolveHash.vue'
6 | import Transaction from '../views/Transaction.vue'
7 | import HookNamespace from '../views/HookNamespace.vue'
8 | import LedgerEntry from '../views/LedgerEntry.vue'
9 | import Account from '../views/Account.vue'
10 | import NotFound from '../views/NotFound.vue'
11 | import CustomCommand from '../views/CustomCommand.vue'
12 | import GenericData from '../components/GenericData.vue'
13 | import publicCommands from '../plugins/commands'
14 |
15 | Vue.use(VueRouter)
16 |
17 | const routes = [
18 | {
19 | path: '/',
20 | name: 'home',
21 | component: Home
22 | },
23 | {
24 | path: '/:ledger([0-9]{1,20})',
25 | name: 'ledger',
26 | component: Ledger
27 | },
28 | {
29 | path: '/tx/:hash([a-fA-F0-9]{64})',
30 | name: 'transaction',
31 | component: Transaction
32 | },
33 | {
34 | path: '/:hash([a-fA-F0-9]{16})', // CTID
35 | name: 'ctid',
36 | component: Transaction
37 | },
38 | {
39 | path: '/entry/:hash([a-fA-F0-9]{64})',
40 | name: 'ledgerentry',
41 | component: LedgerEntry
42 | },
43 | {
44 | path: '/:account(r[a-zA-Z0-9]{15,})',
45 | name: 'account',
46 | component: Account,
47 | children: [
48 | {
49 | name: 'account_tx',
50 | path: 'tx',
51 | component: GenericData,
52 | meta: {
53 | title: 'Transactions',
54 | element: 'transactions',
55 | map: 'tx'
56 | }
57 | },
58 | {
59 | name: 'account_lines',
60 | path: 'lines',
61 | component: GenericData,
62 | meta: {
63 | title: 'Account (Trust) Lines',
64 | element: 'lines',
65 | map: ''
66 | }
67 | },
68 | {
69 | name: 'account_nfts',
70 | path: 'nfts',
71 | component: GenericData,
72 | meta: {
73 | title: 'Account NFTs',
74 | element: 'account_nfts',
75 | map: ''
76 | }
77 | },
78 | {
79 | name: 'account_objects',
80 | path: 'objects',
81 | component: GenericData,
82 | meta: {
83 | title: 'Account (Ledger) Objects',
84 | element: 'account_objects',
85 | map: ''
86 | }
87 | },
88 | {
89 | name: 'account_offers',
90 | path: 'offers',
91 | component: GenericData,
92 | meta: {
93 | title: 'Account (DEX) Offers',
94 | element: 'offers',
95 | map: ''
96 | }
97 | }
98 | ]
99 | },
100 | {
101 | path: '/:hash([a-fA-F0-9]{64})',
102 | name: 'hash',
103 | component: ResolveHash
104 | },
105 | {
106 | path: '/command',
107 | name: 'custom_command',
108 | component: CustomCommand
109 | },
110 | ...Object.keys(publicCommands).map(command => {
111 | return {
112 | path: '/' + command,
113 | name: 'command_' + command,
114 | component: CustomCommand,
115 | meta: {
116 | isPublicCommand: true,
117 | template: publicCommands[command]
118 | }
119 | }
120 | }),
121 | {
122 | path: '/ledger_entry/:hash([a-fA-F0-9]{64})',
123 | name: '_command_ledger_entry',
124 | component: CustomCommand,
125 | meta: {
126 | isPublicCommand: true,
127 | template: publicCommands.ledger_entry,
128 | replaceProp: 'index',
129 | replaceParam: 'hash'
130 | }
131 | },
132 | {
133 | path: '/namespace/:account(r[a-zA-Z0-9]{15,})/:namespace_id([a-fA-F0-9]{64})',
134 | name: 'hooknamespace',
135 | component: HookNamespace
136 | },
137 | {
138 | path: '/404',
139 | alias: '*',
140 | name: 'notfound',
141 | component: NotFound
142 | }
143 | ]
144 |
145 | let routerBasePrefix = ''
146 | const endpointRegex = window.location.href.match(/\/(ws[s]{0,1}:[a-zA-Z0-9-\\.:\\[\]]+[:0-9]{0,})/)
147 | if (endpointRegex) {
148 | routerBasePrefix = endpointRegex[1]
149 | }
150 |
151 | const router = new VueRouter({
152 | mode: 'history',
153 | base: process.env.BASE_URL + routerBasePrefix,
154 | routes,
155 | endpoint: routerBasePrefix.replace(/^(ws[s]{0,1}:)/, '$1//')
156 | })
157 |
158 | export default router
159 |
--------------------------------------------------------------------------------
/src/views/Account.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Open with...
6 |
7 |
17 |
18 |
19 | Account
20 | {{ $router.currentRoute.params.account }}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
Account info
28 |
29 |
30 |
31 | Transactions
32 | Lines
33 | NFTs
34 | Objects
35 | Offers
36 |
37 |
38 |
39 |
40 |
41 |
86 |
87 |
89 |
--------------------------------------------------------------------------------
/src/views/AmbiguousHash.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Ambiguous hash
4 |
{{ $router.currentRoute.params.hash }}
5 |
6 |
7 | What's the one you're looking for?
8 |
9 |
10 |
11 |
12 | {{ t(entry, 'command') }}
13 |
14 |
15 |
16 |
17 |
18 |
54 |
55 |
57 |
--------------------------------------------------------------------------------
/src/views/CustomCommand.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Custom command
5 |
Pick one
6 |
7 |
8 | Which one are you looking for?
9 |
10 |
11 |
12 |
13 | {{ entry }}
14 |
15 |
16 |
17 |
18 |
22 |
Custom command
23 |
24 |
25 |
26 |
27 |
28 | Execute
29 |
30 |
39 |
40 |
41 |
42 |
43 |
44 |
168 |
169 |
171 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hi! Have a great one!
4 | 👋 from your friends at XRPL Labs .
5 |
6 | Simple technical TX / ledger / object / hash explorer.
7 |
8 |
9 |
10 | You can view (JSON, technical (!) explorer) ledgers, transactions,
11 | meta, etc. and click through, on ledger numbers, hashes, transaction hashes, accounts , etc.
12 |
13 |
14 |
15 | Click a ledger or search for something (account, hash, ...)
16 |
17 |
18 |
19 |
20 |
32 |
33 |
35 |
--------------------------------------------------------------------------------
/src/views/HookNamespace.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hook Namespace
4 |
5 | {{ $router.currentRoute.params.hash }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
53 |
54 |
56 |
--------------------------------------------------------------------------------
/src/views/Ledger.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | Ledger
10 | {{ $route.params.ledger }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Transactions
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
92 |
93 |
95 |
--------------------------------------------------------------------------------
/src/views/LedgerEntry.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ledger Entry
4 | {{ $router.currentRoute.params.hash }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
50 |
51 |
53 |
--------------------------------------------------------------------------------
/src/views/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Not found
5 |
How do you make one vanish?
6 |
Prepend g and it's gone.
7 |
8 |
9 |
10 |
11 |
16 |
17 |
19 |
--------------------------------------------------------------------------------
/src/views/Sample.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Primary
7 |
Success
8 |
Warning
9 |
Error
10 |
Disabled
11 |
12 |
13 |
14 |
{"command":"server_info"}
15 |
16 |
17 |
18 |
19 | Textarea
20 |
21 |
22 |
23 |
24 |
25 | Primary
26 | Success
27 |
28 |
29 |
30 |
31 |
32 |
Container.is-centered
33 |
Good morning. Thou hast had a good night's sleep, I hope.
34 |
35 |
36 |
37 |
Container.is-dark
38 |
Good morning. Thou hast had a good night's sleep, I hope.
39 |
40 |
41 |
42 |
Container.is-dark
43 |
Good morning. Thou hast had a good night's sleep, I hope.
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
58 |
59 |
61 |
--------------------------------------------------------------------------------
/src/views/Transaction.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Open with...
6 |
7 |
17 |
18 |
19 | Transaction
20 |
21 | {{ $router.currentRoute.params.hash }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
75 |
76 |
78 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | devServer: {
3 | disableHostCheck: true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------