├── .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 [![Netlify Status](https://api.netlify.com/api/v1/badges/c16a50c4-d71f-4c20-be25-93f993497873/deploy-status)](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 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 121 | -------------------------------------------------------------------------------- /src/components/GenericData.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 79 | 80 | 82 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 147 | 148 | 150 | -------------------------------------------------------------------------------- /src/components/JsonRenderer.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 168 | 169 | 171 | -------------------------------------------------------------------------------- /src/components/LedgerNav.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 107 | 108 | 113 | -------------------------------------------------------------------------------- /src/components/Loading.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | 24 | 26 | -------------------------------------------------------------------------------- /src/components/ResolveHash.vue: -------------------------------------------------------------------------------- 1 | 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 | 40 | 41 | 86 | 87 | 89 | -------------------------------------------------------------------------------- /src/views/AmbiguousHash.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 54 | 55 | 57 | -------------------------------------------------------------------------------- /src/views/CustomCommand.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 168 | 169 | 171 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 32 | 33 | 35 | -------------------------------------------------------------------------------- /src/views/HookNamespace.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 53 | 54 | 56 | -------------------------------------------------------------------------------- /src/views/Ledger.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 92 | 93 | 95 | -------------------------------------------------------------------------------- /src/views/LedgerEntry.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 50 | 51 | 53 | -------------------------------------------------------------------------------- /src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /src/views/Sample.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 58 | 59 | 61 | -------------------------------------------------------------------------------- /src/views/Transaction.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 75 | 76 | 78 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | disableHostCheck: true 4 | } 5 | } 6 | --------------------------------------------------------------------------------