├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── NOTICE ├── README.md ├── package.json ├── public ├── _redirects ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── screenshots ├── acknowledgement.png ├── channel.png ├── commitment.png ├── connection.png └── connections.png ├── src ├── App │ ├── components │ │ ├── ChannelCounterpartyData.tsx │ │ ├── ConnectionCounterpartyData.tsx │ │ ├── HeightData.tsx │ │ └── Navigation.tsx │ ├── index.tsx │ ├── paths.ts │ ├── routes │ │ ├── Acknowledgement │ │ │ └── index.tsx │ │ ├── Channel │ │ │ ├── AcknowledgementsList.tsx │ │ │ ├── ChannelData.tsx │ │ │ ├── CommitmentsList.tsx │ │ │ ├── NextSequenceReceiveData.tsx │ │ │ ├── SequenceForm.tsx │ │ │ ├── UnreceivedAcksList.tsx │ │ │ ├── UnreceivedPacketsList.tsx │ │ │ └── index.tsx │ │ ├── Commitment │ │ │ └── index.tsx │ │ ├── Connection │ │ │ ├── ChannelsList.tsx │ │ │ ├── ConnectionData.tsx │ │ │ └── index.tsx │ │ └── Connections │ │ │ └── index.tsx │ └── style.ts ├── config.ts ├── contexts │ └── ClientContext.tsx ├── index.css ├── index.tsx ├── react-app-env.d.ts ├── types │ └── ibc.ts └── utils │ ├── ibc.ts │ └── strings.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | parserOptions: { 4 | ecmaVersion: 2018, 5 | project: "./tsconfig.json", 6 | tsconfigRootDir: __dirname, 7 | }, 8 | plugins: ["@typescript-eslint", "react", "simple-import-sort"], 9 | extends: [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:react/recommended", 13 | "prettier/@typescript-eslint", 14 | "plugin:prettier/recommended", 15 | ], 16 | rules: { 17 | curly: ["warn", "multi-line", "consistent"], 18 | "no-empty": "off", 19 | "no-console": [ 20 | "warn", 21 | { 22 | allow: ["error", "info", "warn"], 23 | }, 24 | ], 25 | "no-param-reassign": "warn", 26 | "prefer-const": "warn", 27 | "sort-imports": "off", // we use the simple-import-sort plugin instead 28 | "spaced-comment": ["warn", "always", { line: { markers: ["/ 0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | }, 35 | "dependencies": { 36 | "@cosmjs/cosmwasm": "^0.25.4", 37 | "@cosmjs/encoding": "^0.25.4", 38 | "@cosmjs/launchpad": "^0.25.4", 39 | "@cosmjs/math": "^0.25.4", 40 | "@cosmjs/stargate": "^0.25.4", 41 | "@cosmjs/tendermint-rpc": "^0.25.4", 42 | "@cosmjs/utils": "^0.25.4", 43 | "@testing-library/jest-dom": "^5.11.4", 44 | "@testing-library/react": "^11.1.0", 45 | "@testing-library/user-event": "^12.1.10", 46 | "@types/jest": "^26.0.15", 47 | "@types/node": "^12.0.0", 48 | "@types/react": "^16.9.53", 49 | "@types/react-dom": "^16.9.8", 50 | "long": "^4.0.0", 51 | "react": "^17.0.1", 52 | "react-dom": "^17.0.1", 53 | "react-router-dom": "^5.2.0", 54 | "react-scripts": "4.0.0", 55 | "tailwindcss": "^1.9.6", 56 | "typescript": "^4.0.3", 57 | "web-vitals": "^0.2.4" 58 | }, 59 | "devDependencies": { 60 | "@types/react-router-dom": "^5.1.6", 61 | "@typescript-eslint/eslint-plugin": "^4.7.0", 62 | "@typescript-eslint/parser": "^4.7.0", 63 | "eslint": "^7.13.0", 64 | "eslint-config-prettier": "^6.15.0", 65 | "eslint-plugin-prettier": "^3.1.4", 66 | "eslint-plugin-react": "^7.21.5", 67 | "eslint-plugin-simple-import-sort": "^5.0.3", 68 | "prettier": "^2.1.2" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interchainio/ibc-visualizer/9db59ee0135c0a746256f4c40dcc5ce89eab6988/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interchainio/ibc-visualizer/9db59ee0135c0a746256f4c40dcc5ce89eab6988/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interchainio/ibc-visualizer/9db59ee0135c0a746256f4c40dcc5ce89eab6988/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /screenshots/acknowledgement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interchainio/ibc-visualizer/9db59ee0135c0a746256f4c40dcc5ce89eab6988/screenshots/acknowledgement.png -------------------------------------------------------------------------------- /screenshots/channel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interchainio/ibc-visualizer/9db59ee0135c0a746256f4c40dcc5ce89eab6988/screenshots/channel.png -------------------------------------------------------------------------------- /screenshots/commitment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interchainio/ibc-visualizer/9db59ee0135c0a746256f4c40dcc5ce89eab6988/screenshots/commitment.png -------------------------------------------------------------------------------- /screenshots/connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interchainio/ibc-visualizer/9db59ee0135c0a746256f4c40dcc5ce89eab6988/screenshots/connection.png -------------------------------------------------------------------------------- /screenshots/connections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interchainio/ibc-visualizer/9db59ee0135c0a746256f4c40dcc5ce89eab6988/screenshots/connections.png -------------------------------------------------------------------------------- /src/App/components/ChannelCounterpartyData.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { IbcChannelCounterparty } from "../../types/ibc"; 4 | import { style } from "../style"; 5 | 6 | interface CounterpartyDataProps { 7 | readonly counterparty?: IbcChannelCounterparty | null; 8 | } 9 | 10 | export function CounterpartyData({ counterparty }: CounterpartyDataProps): JSX.Element { 11 | return ( 12 |
13 | Counterparty 14 | {counterparty?.portId && Port ID: {counterparty.portId}} 15 | {counterparty?.channelId && Channel ID: {counterparty.channelId}} 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/App/components/ConnectionCounterpartyData.tsx: -------------------------------------------------------------------------------- 1 | import { toHex } from "@cosmjs/encoding"; 2 | import React from "react"; 3 | 4 | import { IbcConnectionCounterparty } from "../../types/ibc"; 5 | import { style } from "../style"; 6 | 7 | interface CounterpartyDataProps { 8 | readonly counterparty?: IbcConnectionCounterparty | null; 9 | } 10 | 11 | export function CounterpartyData({ counterparty }: CounterpartyDataProps): JSX.Element { 12 | return ( 13 |
14 | Counterparty 15 | {counterparty?.clientId && Client ID: {counterparty.clientId}} 16 | {counterparty?.connectionId && Connection ID: {counterparty.connectionId}} 17 | {counterparty?.prefix?.keyPrefix?.length ? ( 18 | Prefix: {toHex(counterparty?.prefix?.keyPrefix)} 19 | ) : null} 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/App/components/HeightData.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { IbcHeight } from "../../types/ibc"; 4 | import { style } from "../style"; 5 | 6 | interface HeightDataProps { 7 | readonly height?: IbcHeight | null; 8 | } 9 | 10 | export function HeightData({ height }: HeightDataProps): JSX.Element { 11 | return ( 12 |
13 | Height 14 |
15 | Version height: {height?.revisionHeight?.toString(10) ?? "–"} 16 | Version number: {height?.revisionNumber?.toString(10) ?? "–"} 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/App/components/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link, useHistory } from "react-router-dom"; 3 | 4 | import { style } from "../style"; 5 | 6 | function pathnameToParams(pathname: string): [string, string][] { 7 | // Remove first empty string: "/connections" gets split to "" and "connections" 8 | const pathnameArray = pathname.split("/").slice(1); 9 | const params: [string, string][] = []; 10 | // Fill params array with pairs from pathnameArray 11 | // Even numbers are keys and odd ones are values 12 | pathnameArray.forEach((key, i, pathnameArray) => { 13 | const value = pathnameArray[i + 1]; 14 | if (!key || !value) return; 15 | if (i % 2 === 0) { 16 | params.push([key, value]); 17 | } 18 | }); 19 | 20 | return params; 21 | } 22 | 23 | function paramsToBreadcrumbs(params: [string, string][]): [string, string][] { 24 | if (params.length === 0) return []; 25 | 26 | const lastKey = params.slice(-1)[0][0]; 27 | // Turn "connections" into "Connection" 28 | const label = lastKey.charAt(0).toUpperCase() + lastKey.slice(1, -1); 29 | // Join key and values to look like url 30 | const fullPath = "/" + params.flat().join("/"); 31 | const newBreadcrumb: [string, string] = [label, fullPath]; 32 | // Remove newBreadcrumb params from array 33 | const nextParams = params.slice(0, -1); 34 | 35 | return [...paramsToBreadcrumbs(nextParams), newBreadcrumb]; 36 | } 37 | 38 | export function Navigation(): JSX.Element { 39 | const { pathname } = useHistory().location; 40 | const params = pathnameToParams(pathname); 41 | const breadcrumbs = paramsToBreadcrumbs(params); 42 | 43 | return ( 44 | 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src/App/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Redirect, Route, Switch } from "react-router-dom"; 3 | 4 | import { ClientProvider } from "../contexts/ClientContext"; 5 | import { pathAcknowledgements, pathChannels, pathCommitments, pathConnections } from "./paths"; 6 | import { Acknowledgement } from "./routes/Acknowledgement"; 7 | import { Channel } from "./routes/Channel"; 8 | import { Commitment } from "./routes/Commitment"; 9 | import { Connection } from "./routes/Connection"; 10 | import { Connections } from "./routes/Connections"; 11 | 12 | // portIdChannelId is a joined string of portId and channelId, with a ":" separator: 13 | // : 14 | export const portIdChannelIdSeparator = ":"; 15 | const paramChannel = `${pathChannels}/:portIdChannelId`; 16 | 17 | const paramConnection = `${pathConnections}/:connectionId`; 18 | const paramCommitment = `${pathCommitments}/:sequence`; 19 | const paramAcknowledgement = `${pathAcknowledgements}/:sequence`; 20 | 21 | export function App(): JSX.Element { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | } /> 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/App/paths.ts: -------------------------------------------------------------------------------- 1 | export const pathConnections = "/connections"; 2 | export const pathChannels = "/channels"; 3 | export const pathCommitments = "/commitments"; 4 | export const pathAcknowledgements = "/acknowledgements"; 5 | -------------------------------------------------------------------------------- /src/App/routes/Acknowledgement/index.tsx: -------------------------------------------------------------------------------- 1 | import { toHex } from "@cosmjs/encoding"; 2 | import React, { useEffect, useState } from "react"; 3 | import { useParams } from "react-router-dom"; 4 | 5 | import { portIdChannelIdSeparator } from "../.."; 6 | import { useClient } from "../../../contexts/ClientContext"; 7 | import { IbcPacketAcknowledgementResponse } from "../../../types/ibc"; 8 | import { HeightData } from "../../components/HeightData"; 9 | import { Navigation } from "../../components/Navigation"; 10 | import { style } from "../../style"; 11 | 12 | interface AcknowledgementParams { 13 | readonly portIdChannelId: string; 14 | readonly sequence: string; 15 | } 16 | 17 | export function Acknowledgement(): JSX.Element { 18 | const { portIdChannelId, sequence } = useParams(); 19 | const [portId, channelId] = portIdChannelId.split(portIdChannelIdSeparator); 20 | 21 | const { getClient } = useClient(); 22 | const [ackResponse, setAckResponse] = useState(); 23 | 24 | useEffect(() => { 25 | const sequenceNumber = Number.parseInt(sequence, 10); 26 | 27 | (async function updateAckResponse() { 28 | const ackResponse = await getClient().ibc.channel.packetAcknowledgement( 29 | portId, 30 | channelId, 31 | sequenceNumber, 32 | ); 33 | setAckResponse(ackResponse); 34 | })(); 35 | }, [sequence, getClient, portId, channelId]); 36 | 37 | return ( 38 |
39 | 40 | Data 41 | {portId && Port ID: {portId}} 42 | {channelId && Channel ID: {channelId}} 43 | {sequence && Sequence: {sequence}} 44 | {ackResponse?.acknowledgement ? ( 45 |
46 | Proof: {ackResponse.proof?.length ? toHex(ackResponse.proof) : "–"} 47 | 48 | Data: {toHex(ackResponse.acknowledgement)} 49 |
50 | ) : ( 51 | No acknowledgement found 52 | )} 53 |
54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/App/routes/Channel/AcknowledgementsList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { portIdChannelIdSeparator } from "../.."; 5 | import { useClient } from "../../../contexts/ClientContext"; 6 | import { IbcPacketAcknowledgementsResponse } from "../../../types/ibc"; 7 | import { pathAcknowledgements, pathChannels, pathConnections } from "../../paths"; 8 | import { style } from "../../style"; 9 | 10 | interface AcknowledgementsListProps { 11 | readonly connectionId: string; 12 | readonly portId: string; 13 | readonly channelId: string; 14 | } 15 | 16 | export function AcknowledgementsList({ 17 | connectionId, 18 | portId, 19 | channelId, 20 | }: AcknowledgementsListProps): JSX.Element { 21 | const paramConnection = `${pathConnections}/${connectionId}`; 22 | 23 | const { getClient } = useClient(); 24 | const [packetAcknowledgementsResponse, setPacketAcknowledgementsResponse] = useState< 25 | IbcPacketAcknowledgementsResponse 26 | >(); 27 | 28 | useEffect(() => { 29 | (async function updatePacketAcknowledgementsResponse() { 30 | const packetAcknowledgementsResponse = await getClient().ibc.channel.packetAcknowledgements( 31 | portId, 32 | channelId, 33 | ); 34 | setPacketAcknowledgementsResponse(packetAcknowledgementsResponse); 35 | })(); 36 | }, [getClient, portId, channelId]); 37 | 38 | return packetAcknowledgementsResponse?.acknowledgements?.length ? ( 39 |
40 | Packet acknowledgements 41 |
42 | {packetAcknowledgementsResponse.acknowledgements.map((acknowledgement, index) => { 43 | const portIdChannelId = `${acknowledgement.portId}${portIdChannelIdSeparator}${acknowledgement.channelId}`; 44 | const paramChannel = `${pathChannels}/${portIdChannelId}`; 45 | const paramAcknowledgement = `${pathAcknowledgements}/${acknowledgement.sequence}`; 46 | 47 | return ( 48 | 53 | Sequence: {acknowledgement.sequence ? acknowledgement.sequence.toString(10) : "–"} 54 | 55 | ); 56 | })} 57 |
58 |
59 | ) : ( 60 |
No acknowledgements found
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/App/routes/Channel/ChannelData.tsx: -------------------------------------------------------------------------------- 1 | import { toHex } from "@cosmjs/encoding"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | import { useClient } from "../../../contexts/ClientContext"; 5 | import { IbcChannelResponse } from "../../../types/ibc"; 6 | import { printIbcChannelState, printIbcOrder } from "../../../utils/ibc"; 7 | import { CounterpartyData } from "../../components/ChannelCounterpartyData"; 8 | import { HeightData } from "../../components/HeightData"; 9 | import { style } from "../../style"; 10 | 11 | interface ChannelDataProps { 12 | readonly portId: string; 13 | readonly channelId: string; 14 | } 15 | 16 | export function ChannelData({ portId, channelId }: ChannelDataProps): JSX.Element { 17 | const { getClient } = useClient(); 18 | const [channelResponse, setChannelResponse] = useState(); 19 | 20 | useEffect(() => { 21 | (async function updateChannelResponse() { 22 | const channelResponse = await getClient().ibc.channel.channel(portId, channelId); 23 | setChannelResponse(channelResponse); 24 | })(); 25 | }, [getClient, portId, channelId]); 26 | 27 | return channelResponse?.channel ? ( 28 |
29 |
Data
30 | {portId &&
Port ID: {portId}
} 31 | {channelId &&
Channel ID: {channelId}
} 32 |
Proof: {channelResponse.proof?.length ? toHex(channelResponse.proof) : "–"}
33 | 34 |
35 | 36 | State: {channelResponse.channel.state ? printIbcChannelState(channelResponse.channel.state) : "–"} 37 | 38 | Version: {channelResponse.channel.version ?? "–"} 39 | 40 | Ordering: {channelResponse.channel.ordering ? printIbcOrder(channelResponse.channel.ordering) : "–"} 41 | 42 | 43 | 44 | Connection hops:{" "} 45 | {channelResponse.channel.connectionHops ? channelResponse.channel.connectionHops.join(", ") : "–"} 46 | 47 |
48 |
49 | ) : ( 50 |
No channel found
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/App/routes/Channel/CommitmentsList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { portIdChannelIdSeparator } from "../.."; 5 | import { useClient } from "../../../contexts/ClientContext"; 6 | import { IbcPacketCommitmentsResponse } from "../../../types/ibc"; 7 | import { pathChannels, pathCommitments, pathConnections } from "../../paths"; 8 | import { style } from "../../style"; 9 | 10 | interface CommitmentsListProps { 11 | readonly connectionId: string; 12 | readonly portId: string; 13 | readonly channelId: string; 14 | } 15 | 16 | export function CommitmentsList({ connectionId, portId, channelId }: CommitmentsListProps): JSX.Element { 17 | const paramConnection = `${pathConnections}/${connectionId}`; 18 | 19 | const { getClient } = useClient(); 20 | const [packetCommitmentsResponse, setPacketCommitmentsResponse] = useState(); 21 | 22 | useEffect(() => { 23 | (async function updatePacketCommitmentsResponse() { 24 | const packetCommitmentsResponse = await getClient().ibc.channel.packetCommitments(portId, channelId); 25 | setPacketCommitmentsResponse(packetCommitmentsResponse); 26 | })(); 27 | }, [getClient, portId, channelId]); 28 | 29 | return packetCommitmentsResponse?.commitments?.length ? ( 30 |
31 | Packet commitments 32 |
33 | {packetCommitmentsResponse.commitments.map((commitment, index) => { 34 | const portIdChannelId = `${commitment.portId}${portIdChannelIdSeparator}${commitment.channelId}`; 35 | const paramChannel = `${pathChannels}/${portIdChannelId}`; 36 | const paramCommitment = `${pathCommitments}/${commitment.sequence}`; 37 | 38 | return ( 39 | 44 | Sequence: {commitment.sequence ? commitment.sequence.toString(10) : "–"} 45 | 46 | ); 47 | })} 48 |
49 |
50 | ) : ( 51 |
No commitments found
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /src/App/routes/Channel/NextSequenceReceiveData.tsx: -------------------------------------------------------------------------------- 1 | import { toHex } from "@cosmjs/encoding"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | import { useClient } from "../../../contexts/ClientContext"; 5 | import { IbcNextSequenceReceiveResponse } from "../../../types/ibc"; 6 | 7 | interface NextSequenceReceiveDataProps { 8 | readonly portId: string; 9 | readonly channelId: string; 10 | } 11 | 12 | export function NextSequenceReceiveData({ portId, channelId }: NextSequenceReceiveDataProps): JSX.Element { 13 | const { getClient } = useClient(); 14 | const [nextSequenceReceiveResponse, setNextSequenceReceiveResponse] = useState< 15 | IbcNextSequenceReceiveResponse 16 | >(); 17 | 18 | useEffect(() => { 19 | (async function updateNextSequenceReceiveResponse() { 20 | const nextSequenceReceiveResponse = await getClient().ibc.channel.nextSequenceReceive( 21 | portId, 22 | channelId, 23 | ); 24 | setNextSequenceReceiveResponse(nextSequenceReceiveResponse); 25 | })(); 26 | }, [getClient, portId, channelId]); 27 | 28 | return nextSequenceReceiveResponse ? ( 29 |
30 | 31 | Next sequence receive proof:{" "} 32 | {nextSequenceReceiveResponse.proof?.length ? toHex(nextSequenceReceiveResponse.proof) : "–"} 33 | 34 | 35 | Next sequence receive:{" "} 36 | {nextSequenceReceiveResponse.nextSequenceReceive 37 | ? nextSequenceReceiveResponse.nextSequenceReceive.toString(10) 38 | : "–"} 39 | 40 |
41 | ) : ( 42 |
No next sequence receive found
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/App/routes/Channel/SequenceForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | 3 | import { style } from "../../style"; 4 | 5 | interface SequenceFormProps { 6 | readonly sequence?: number; 7 | readonly setSequence: React.Dispatch>; 8 | } 9 | 10 | export function SequenceForm({ sequence, setSequence }: SequenceFormProps): JSX.Element { 11 | const inputRef = useRef(null); 12 | 13 | function handleSequenceInput(event: React.ChangeEvent): void { 14 | const sequenceString = event.target.value; 15 | if (!sequenceString) { 16 | event.target.value = ""; 17 | return; 18 | } 19 | 20 | const sequenceNumber = Number.parseInt(sequenceString, 10); 21 | const validNumber = !isNaN(sequenceNumber) && sequenceNumber > 0; 22 | const newSequence = validNumber ? sequenceNumber : sequence; 23 | event.target.value = newSequence ? newSequence.toString() : ""; 24 | } 25 | 26 | return ( 27 |
28 | 31 | 38 | 48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/App/routes/Channel/UnreceivedAcksList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | import { useClient } from "../../../contexts/ClientContext"; 4 | import { IbcUnreceivedAcksResponse } from "../../../types/ibc"; 5 | import { style } from "../../style"; 6 | 7 | interface UnreceivedAcksListProps { 8 | readonly portId: string; 9 | readonly channelId: string; 10 | readonly sequence: number; 11 | } 12 | 13 | export function UnreceivedAcksList({ portId, channelId, sequence }: UnreceivedAcksListProps): JSX.Element { 14 | const { getClient } = useClient(); 15 | const [unreceivedAcksResponse, setUnreceivedAcksResponse] = useState(); 16 | 17 | useEffect(() => { 18 | (async function updateUnreceivedAcksResponse() { 19 | const unreceivedAcksResponse = await getClient().ibc.channel.unreceivedAcks(portId, channelId, [ 20 | sequence, 21 | ]); 22 | setUnreceivedAcksResponse(unreceivedAcksResponse); 23 | })(); 24 | }, [getClient, portId, channelId, sequence]); 25 | 26 | return unreceivedAcksResponse?.sequences?.length ? ( 27 |
28 | Unreceived acknowledgements 29 |
30 | {unreceivedAcksResponse.sequences.map((sequence, index) => ( 31 | 32 | Sequence: {sequence.toString(10)} 33 | 34 | ))} 35 |
36 |
37 | ) : ( 38 |
No unreceived acknowledgements for sequence {sequence}
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/App/routes/Channel/UnreceivedPacketsList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | import { useClient } from "../../../contexts/ClientContext"; 4 | import { IbcUnreceivedPacketsResponse } from "../../../types/ibc"; 5 | import { style } from "../../style"; 6 | 7 | interface UnreceivedPacketsListProps { 8 | readonly portId: string; 9 | readonly channelId: string; 10 | readonly sequence: number; 11 | } 12 | 13 | export function UnreceivedPacketsList({ 14 | portId, 15 | channelId, 16 | sequence, 17 | }: UnreceivedPacketsListProps): JSX.Element { 18 | const { getClient } = useClient(); 19 | const [unreceivedPacketsResponse, setUnreceivedPacketsResponse] = useState(); 20 | 21 | useEffect(() => { 22 | (async function updateUnreceivedPacketsResponse() { 23 | const unreceivedPacketsResponse = await getClient().ibc.channel.unreceivedPackets(portId, channelId, [ 24 | sequence, 25 | ]); 26 | setUnreceivedPacketsResponse(unreceivedPacketsResponse); 27 | })(); 28 | }, [getClient, portId, channelId, sequence]); 29 | 30 | return unreceivedPacketsResponse?.sequences?.length ? ( 31 |
32 | Unreceived packets 33 |
34 | {unreceivedPacketsResponse.sequences.map((sequence, index) => ( 35 | 36 | Sequence: {sequence.toString(10)} 37 | 38 | ))} 39 |
40 |
41 | ) : ( 42 |
No unreceived packets for sequence {sequence}
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/App/routes/Channel/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | 4 | import { portIdChannelIdSeparator } from "../.."; 5 | import { Navigation } from "../../components/Navigation"; 6 | import { AcknowledgementsList } from "./AcknowledgementsList"; 7 | import { ChannelData } from "./ChannelData"; 8 | import { CommitmentsList } from "./CommitmentsList"; 9 | import { NextSequenceReceiveData } from "./NextSequenceReceiveData"; 10 | import { SequenceForm } from "./SequenceForm"; 11 | import { UnreceivedAcksList } from "./UnreceivedAcksList"; 12 | import { UnreceivedPacketsList } from "./UnreceivedPacketsList"; 13 | 14 | interface ChannelParams { 15 | readonly connectionId: string; 16 | readonly portIdChannelId: string; 17 | } 18 | 19 | export function Channel(): JSX.Element { 20 | const { connectionId, portIdChannelId } = useParams(); 21 | const [portId, channelId] = portIdChannelId.split(portIdChannelIdSeparator); 22 | 23 | const [sequence, setSequence] = useState(); 24 | 25 | return ( 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | {sequence ? ( 34 | <> 35 | 36 | 37 | 38 | ) : null} 39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /src/App/routes/Commitment/index.tsx: -------------------------------------------------------------------------------- 1 | import { toHex } from "@cosmjs/encoding"; 2 | import Long from "long"; 3 | import React, { useEffect, useState } from "react"; 4 | import { useParams } from "react-router-dom"; 5 | 6 | import { portIdChannelIdSeparator } from "../.."; 7 | import { useClient } from "../../../contexts/ClientContext"; 8 | import { IbcPacketCommitmentResponse } from "../../../types/ibc"; 9 | import { HeightData } from "../../components/HeightData"; 10 | import { Navigation } from "../../components/Navigation"; 11 | import { style } from "../../style"; 12 | 13 | interface CommitmentParams { 14 | readonly portIdChannelId: string; 15 | readonly sequence: string; 16 | } 17 | 18 | export function Commitment(): JSX.Element { 19 | const { portIdChannelId, sequence } = useParams(); 20 | const [portId, channelId] = portIdChannelId.split(portIdChannelIdSeparator); 21 | 22 | const { getClient } = useClient(); 23 | const [commitmentResponse, setCommitmentResponse] = useState(); 24 | 25 | useEffect(() => { 26 | const sequenceNumber = Number.parseInt(sequence, 10); 27 | 28 | (async function updateCommitmentResponse() { 29 | const commitmentResponse = await getClient().ibc.channel.packetCommitment( 30 | portId, 31 | channelId, 32 | Long.fromNumber(sequenceNumber), 33 | ); 34 | setCommitmentResponse(commitmentResponse); 35 | })(); 36 | }, [sequence, getClient, portId, channelId]); 37 | 38 | return ( 39 |
40 | 41 | Data 42 | {portId && Port ID: {portId}} 43 | {channelId && Channel ID: {channelId}} 44 | {sequence && Sequence: {sequence}} 45 | {commitmentResponse?.commitment ? ( 46 |
47 | Proof: {commitmentResponse.proof?.length ? toHex(commitmentResponse.proof) : "–"} 48 | 49 | Data: {toHex(commitmentResponse.commitment)} 50 |
51 | ) : ( 52 | No commitment found 53 | )} 54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/App/routes/Connection/ChannelsList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { portIdChannelIdSeparator } from "../.."; 5 | import { useClient } from "../../../contexts/ClientContext"; 6 | import { IbcChannelsResponse, IbcConnectionChannelsResponse } from "../../../types/ibc"; 7 | import { ellideMiddle } from "../../../utils/strings"; 8 | import { pathChannels, pathConnections } from "../../paths"; 9 | import { style } from "../../style"; 10 | 11 | interface ChannelsListProps { 12 | readonly connectionId: string; 13 | } 14 | 15 | export function ChannelsList({ connectionId }: ChannelsListProps): JSX.Element { 16 | const paramConnection = `${pathConnections}/${connectionId}`; 17 | 18 | const { getClient } = useClient(); 19 | const [channelsResponse, setChannelsResponse] = useState< 20 | IbcChannelsResponse | IbcConnectionChannelsResponse 21 | >(); 22 | 23 | useEffect(() => { 24 | (async function updateChannelsResponse() { 25 | const channelsResponse = await getClient().ibc.channel.connectionChannels(connectionId); 26 | setChannelsResponse(channelsResponse); 27 | })(); 28 | }, [connectionId, getClient]); 29 | 30 | async function loadMoreChannels(): Promise { 31 | if (!channelsResponse?.pagination?.nextKey?.length) return; 32 | 33 | const newChannelsResponse = await getClient().ibc.channel.connectionChannels( 34 | connectionId, 35 | channelsResponse.pagination.nextKey, 36 | ); 37 | 38 | const oldChannels = channelsResponse.channels ?? []; 39 | const newChannels = newChannelsResponse.channels ?? []; 40 | 41 | setChannelsResponse({ 42 | ...newChannelsResponse, 43 | channels: [...oldChannels, ...newChannels], 44 | }); 45 | } 46 | 47 | return channelsResponse?.channels?.length ? ( 48 |
49 | Channels 50 |
51 | {channelsResponse.channels.map((channel, index) => { 52 | const portIdChannelId = `${channel.portId}${portIdChannelIdSeparator}${channel.channelId}`; 53 | const paramChannel = `${pathChannels}/${portIdChannelId}`; 54 | 55 | return ( 56 | 57 | {`${ellideMiddle(channel.portId ?? "–", 20)} | ${ellideMiddle( 58 | channel.channelId ?? "–", 59 | 20, 60 | )}`} 61 | 62 | ); 63 | })} 64 |
65 | {channelsResponse.pagination?.nextKey?.length ? ( 66 | 69 | ) : null} 70 |
71 | ) : ( 72 | No channels found 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /src/App/routes/Connection/ConnectionData.tsx: -------------------------------------------------------------------------------- 1 | import { toHex } from "@cosmjs/encoding"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | import { useClient } from "../../../contexts/ClientContext"; 5 | import { IbcConnectionResponse } from "../../../types/ibc"; 6 | import { printIbcConnectionState } from "../../../utils/ibc"; 7 | import { CounterpartyData } from "../../components/ConnectionCounterpartyData"; 8 | import { HeightData } from "../../components/HeightData"; 9 | import { style } from "../../style"; 10 | 11 | interface ConnectionDataProps { 12 | readonly connectionId: string; 13 | } 14 | 15 | export function ConnectionData({ connectionId }: ConnectionDataProps): JSX.Element { 16 | const { getClient } = useClient(); 17 | const [connectionResponse, setConnectionResponse] = useState(); 18 | 19 | useEffect(() => { 20 | (async function updateConnectionResponse() { 21 | const connectionResponse = await getClient().ibc.connection.connection(connectionId); 22 | setConnectionResponse(connectionResponse); 23 | })(); 24 | }, [getClient, connectionId]); 25 | 26 | return connectionResponse?.connection ? ( 27 |
28 |
Data
29 | {connectionId &&
Connection ID: {connectionId}
} 30 |
Proof: {connectionResponse.proof?.length ? toHex(connectionResponse.proof) : "–"}
31 | 32 |
33 | Client ID: {connectionResponse.connection.clientId ?? "–"} 34 | 35 | State:{" "} 36 | {connectionResponse.connection.state 37 | ? printIbcConnectionState(connectionResponse.connection.state) 38 | : "–"} 39 | 40 | {connectionResponse.connection.versions?.length ? ( 41 |
42 | Versions 43 | {connectionResponse.connection.versions.map((version, index) => ( 44 |
45 | ID: {version.identifier ?? "–"} 46 | Features: {version.features ? version.features.join(", ") : "–"} 47 |
48 | ))} 49 |
50 | ) : ( 51 | No versions found 52 | )} 53 | 54 |
55 |
56 | ) : ( 57 | No connection found 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/App/routes/Connection/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useParams } from "react-router-dom"; 3 | 4 | import { Navigation } from "../../components/Navigation"; 5 | import { ChannelsList } from "./ChannelsList"; 6 | import { ConnectionData } from "./ConnectionData"; 7 | 8 | interface ConnectionParams { 9 | readonly connectionId: string; 10 | } 11 | 12 | export function Connection(): JSX.Element { 13 | const { connectionId } = useParams(); 14 | 15 | return ( 16 |
17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/App/routes/Connections/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { useClient } from "../../../contexts/ClientContext"; 5 | import { IbcConnectionsResponse } from "../../../types/ibc"; 6 | import { ellideMiddle } from "../../../utils/strings"; 7 | import { HeightData } from "../../components/HeightData"; 8 | import { Navigation } from "../../components/Navigation"; 9 | import { pathConnections } from "../../paths"; 10 | import { style } from "../../style"; 11 | 12 | // orders strings like "07-tendermint-0" numerically 13 | function compareClientIds(a: string, b: string): number { 14 | const arrayA = a.split("-"); 15 | const arrayB = b.split("-"); 16 | arrayA.splice(1, 1); 17 | arrayB.splice(1, 1); 18 | const [firstNumberA, secondNumberA] = arrayA.map((stringNum) => Number.parseInt(stringNum, 10)); 19 | const [firstNumberB, secondNumberB] = arrayB.map((stringNum) => Number.parseInt(stringNum, 10)); 20 | 21 | if (firstNumberA > firstNumberB) return firstNumberA - firstNumberB; 22 | return secondNumberA - secondNumberB; 23 | } 24 | 25 | export function Connections(): JSX.Element { 26 | const { getClient } = useClient(); 27 | 28 | const [clientIds, setClientIds] = useState([]); 29 | const [connectionsResponse, setConnectionsResponse] = useState(); 30 | 31 | useEffect(() => { 32 | (async function updateConnectionsResponse() { 33 | const connectionsResponse = await getClient().ibc.connection.connections(); 34 | setConnectionsResponse(connectionsResponse); 35 | 36 | const nonEmptyClientIds = 37 | connectionsResponse.connections 38 | ?.map((connection) => connection.clientId ?? "") 39 | .filter((clientId) => clientId !== "") ?? []; 40 | 41 | const nonDuplicateClientIds = [...new Set(nonEmptyClientIds)]; 42 | const orderedClientIds = nonDuplicateClientIds.sort(compareClientIds); 43 | 44 | setClientIds(orderedClientIds); 45 | })(); 46 | }, [getClient]); 47 | 48 | async function loadMoreConnections(): Promise { 49 | if (!connectionsResponse?.pagination?.nextKey?.length) return; 50 | 51 | const newConnectionsResponse = await getClient().ibc.connection.connections( 52 | connectionsResponse.pagination.nextKey, 53 | ); 54 | 55 | const oldConnections = connectionsResponse.connections ?? []; 56 | const newConnections = newConnectionsResponse.connections ?? []; 57 | 58 | setConnectionsResponse({ 59 | ...newConnectionsResponse, 60 | connections: [...oldConnections, ...newConnections], 61 | }); 62 | 63 | const newClientIds = 64 | newConnectionsResponse.connections 65 | ?.map((connection) => connection.clientId ?? "") 66 | .filter((clientId) => clientId !== "") ?? []; 67 | 68 | setClientIds((oldClientIds) => { 69 | // New paginated connections may be from same client as the old ones, 70 | // must remove duplicate client ids 71 | const mergedClientIds = [...oldClientIds, ...newClientIds]; 72 | const uniqueClientIds = [...new Set(mergedClientIds)]; 73 | const sortedClientIds = uniqueClientIds.sort(compareClientIds); 74 | 75 | return sortedClientIds; 76 | }); 77 | } 78 | 79 | return ( 80 |
81 | 82 |
83 | {connectionsResponse ? ( 84 | <> 85 | Connections / client 86 | 87 | {connectionsResponse.connections?.length ? ( 88 | <> 89 |
90 | {clientIds.map((clientId) => ( 91 |
92 |
Client {clientId}
93 | {connectionsResponse.connections 94 | ?.filter((connection) => connection.clientId === clientId) 95 | .sort((a, b) => { 96 | // orders strings like "connection-6" numerically 97 | const numberA = Number.parseInt(a.id?.split("-")[1] || "", 10); 98 | const numberB = Number.parseInt(b.id?.split("-")[1] || "", 10); 99 | 100 | return numberA - numberB; 101 | }) 102 | .map((connection) => ( 103 | 108 | Connection {ellideMiddle(connection.id ?? "–", 20)} 109 | 110 | ))} 111 |
112 | ))} 113 |
114 | {connectionsResponse.pagination?.nextKey?.length ? ( 115 | 116 | ) : null} 117 | 118 | ) : ( 119 | No connections found 120 | )} 121 | 122 | ) : ( 123 | Loading data … 124 | )} 125 |
126 |
127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /src/App/style.ts: -------------------------------------------------------------------------------- 1 | export const style = { 2 | title: "text-xl font-bold", 3 | subtitle: "text-lg font-bold", 4 | button: "rounded mr-2 mb-2 p-2 bg-purple-800 hover:bg-purple-700 font-bold", 5 | link: "mr-2 mb-2 underline text-purple-500 hover:text-purple-400 font-bold", 6 | listItemStyle: "mr-2 mb-2 text-purple-500 font-bold", 7 | input: 8 | "ml-2 appearance-none bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500", 9 | }; 10 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | interface AppConfig { 2 | readonly rpcUrl: string; 3 | } 4 | interface NetworkConfigs { 5 | readonly [key: string]: AppConfig; 6 | } 7 | 8 | // Configuration for CosmJS development chain simapp. 9 | // See https://github.com/cosmos/cosmjs/tree/master/scripts/simapp. 10 | const local: AppConfig = { 11 | rpcUrl: "http://localhost:26658", 12 | }; 13 | 14 | const musselnet: AppConfig = { 15 | rpcUrl: "https://rpc.musselnet.cosmwasm.com", 16 | }; 17 | 18 | const oysternet: AppConfig = { 19 | rpcUrl: "rpc.oysternet.cosmwasm.com", 20 | }; 21 | 22 | const configs: NetworkConfigs = { local, musselnet, oysternet }; 23 | 24 | function getAppConfig(): AppConfig { 25 | const network = process.env.REACT_APP_NETWORK; 26 | if (!network) return local; 27 | 28 | const config = configs[network]; 29 | if (!config) { 30 | throw new Error(`No configuration found for network ${network}`); 31 | } 32 | 33 | return config; 34 | } 35 | 36 | export const config = getAppConfig(); 37 | -------------------------------------------------------------------------------- /src/contexts/ClientContext.tsx: -------------------------------------------------------------------------------- 1 | import { IbcExtension, QueryClient, setupIbcExtension } from "@cosmjs/stargate"; 2 | import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; 3 | import React, { useEffect } from "react"; 4 | 5 | import { config } from "../config"; 6 | 7 | export type IbcClient = QueryClient & IbcExtension; 8 | 9 | export interface ClientContextType { 10 | readonly getClient: () => IbcClient; 11 | } 12 | 13 | const defaultClientContext: ClientContextType = { 14 | getClient: (): IbcClient => { 15 | throw new Error("not yet initialized"); 16 | }, 17 | }; 18 | 19 | const ClientContext = React.createContext(defaultClientContext); 20 | 21 | export const useClient = (): ClientContextType => React.useContext(ClientContext); 22 | 23 | export function ClientProvider({ children }: React.HTMLAttributes): JSX.Element { 24 | const [tmClient, setTmClient] = React.useState(); 25 | const [ibcClient, setIbcClient] = React.useState(); 26 | const [value, setValue] = React.useState(defaultClientContext); 27 | const [clientsAvailable, setClientsAvailable] = React.useState(false); 28 | 29 | useEffect(() => { 30 | (async function updateTmClient() { 31 | const tmClient = await Tendermint34Client.connect(config.rpcUrl); 32 | setTmClient(tmClient); 33 | })(); 34 | }, []); 35 | 36 | useEffect(() => { 37 | if (!tmClient) return; 38 | 39 | (async function updateIbcClient() { 40 | const ibcClient = QueryClient.withExtensions(tmClient, setupIbcExtension); 41 | setIbcClient(ibcClient); 42 | })(); 43 | }, [tmClient]); 44 | 45 | useEffect(() => { 46 | if (!tmClient || !ibcClient) return; 47 | 48 | setValue({ getClient: () => ibcClient }); 49 | setClientsAvailable(true); 50 | }, [ibcClient, tmClient]); 51 | 52 | return clientsAvailable ? ( 53 | {children} 54 | ) : ( 55 |
Setting up client …
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | *, 6 | *::before, 7 | *::after { 8 | box-sizing: border-box; 9 | } 10 | 11 | body { 12 | min-height: 100vh; 13 | text-rendering: optimizeSpeed; 14 | line-height: 1.5; 15 | margin: 0; 16 | padding: 1rem; 17 | background-color: #1f1f1f; 18 | color: white; 19 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", 20 | "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import "./tailwind.output.css"; 2 | 3 | import React from "react"; 4 | import ReactDOM from "react-dom"; 5 | 6 | import { App } from "./App"; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById("root"), 13 | ); 14 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/types/ibc.ts: -------------------------------------------------------------------------------- 1 | export { 2 | Counterparty as IbcChannelCounterparty, 3 | Order as IbcOrder, 4 | State as IbcChannelState, 5 | } from "@cosmjs/stargate/build/codec/ibc/core/channel/v1/channel"; 6 | export { 7 | QueryChannelResponse as IbcChannelResponse, 8 | QueryChannelsResponse as IbcChannelsResponse, 9 | QueryConnectionChannelsResponse as IbcConnectionChannelsResponse, 10 | QueryNextSequenceReceiveResponse as IbcNextSequenceReceiveResponse, 11 | QueryPacketAcknowledgementResponse as IbcPacketAcknowledgementResponse, 12 | QueryPacketAcknowledgementsResponse as IbcPacketAcknowledgementsResponse, 13 | QueryPacketCommitmentResponse as IbcPacketCommitmentResponse, 14 | QueryPacketCommitmentsResponse as IbcPacketCommitmentsResponse, 15 | QueryUnreceivedAcksResponse as IbcUnreceivedAcksResponse, 16 | QueryUnreceivedPacketsResponse as IbcUnreceivedPacketsResponse, 17 | } from "@cosmjs/stargate/build/codec/ibc/core/channel/v1/query"; 18 | export { Height as IbcHeight } from "@cosmjs/stargate/build/codec/ibc/core/client/v1/client"; 19 | export { 20 | Counterparty as IbcConnectionCounterparty, 21 | State as IbcConnectionState, 22 | } from "@cosmjs/stargate/build/codec/ibc/core/connection/v1/connection"; 23 | export { 24 | QueryClientConnectionsResponse as IbcClientConnectionsResponse, 25 | QueryConnectionResponse as IbcConnectionResponse, 26 | QueryConnectionsResponse as IbcConnectionsResponse, 27 | } from "@cosmjs/stargate/build/codec/ibc/core/connection/v1/query"; 28 | -------------------------------------------------------------------------------- /src/utils/ibc.ts: -------------------------------------------------------------------------------- 1 | import { IbcChannelState, IbcConnectionState, IbcOrder } from "../types/ibc"; 2 | 3 | export function printIbcConnectionState(state: IbcConnectionState): string { 4 | switch (state) { 5 | case IbcConnectionState.STATE_INIT: 6 | return "Initial"; 7 | case IbcConnectionState.STATE_TRYOPEN: 8 | return "Trying to open"; 9 | case IbcConnectionState.STATE_OPEN: 10 | return "Open"; 11 | default: 12 | return "Unspecified"; 13 | } 14 | } 15 | 16 | export function printIbcChannelState(state: IbcChannelState): string { 17 | switch (state) { 18 | case IbcChannelState.STATE_INIT: 19 | return "Initial"; 20 | case IbcChannelState.STATE_TRYOPEN: 21 | return "Trying to open"; 22 | case IbcChannelState.STATE_OPEN: 23 | return "Open"; 24 | case IbcChannelState.STATE_CLOSED: 25 | return "Closed"; 26 | default: 27 | return "Unspecified"; 28 | } 29 | } 30 | 31 | export function printIbcOrder(order: IbcOrder): string { 32 | switch (order) { 33 | case IbcOrder.ORDER_UNORDERED: 34 | return "Unordered"; 35 | case IbcOrder.ORDER_ORDERED: 36 | return "Ordered"; 37 | default: 38 | return "Unspecified"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | export function ellideMiddle(str: string, maxOutLen: number): string { 2 | if (str.length <= maxOutLen) { 3 | return str; 4 | } 5 | const ellide = "…"; 6 | const frontLen = Math.ceil((maxOutLen - ellide.length) / 2); 7 | const tailLen = Math.floor((maxOutLen - ellide.length) / 2); 8 | return str.slice(0, frontLen) + ellide + str.slice(-tailLen); 9 | } 10 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: { 3 | mode: "layers", 4 | content: ["./src/**/*.tsx", "./src/**/*.ts"], 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react", 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "include": ["src"] 20 | } 21 | --------------------------------------------------------------------------------