diff --git a/docs/guides/message-debugging.mdx b/docs/guides/message-debugging.mdx
index fe82f957..80068ac9 100644
--- a/docs/guides/message-debugging.mdx
+++ b/docs/guides/message-debugging.mdx
@@ -1,7 +1,8 @@
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
-import CosmosMessageDelivered from "@site/src/components/CosmosMessageDelivered";
+import NonEvmMessageDelivered from "@site/src/components/NonEvmMessageDelivered";
+import SealevelWarpTransferInfo from "@site/src/components/SealevelWarpTransferInfo";
# Message debugging
@@ -36,18 +37,28 @@ import CosmosMessageDelivered from "@site/src/components/CosmosMessageDelivered"
Example on Mintscan showing the log on an origin transaction.
+
+ When viewing the origin transaction on the origin chain's block explorer, scroll to the logs at the very bottom.
+
+ The log starting with **Dispatched message to** contains the message ID.
+
+
+
- **Debugging a message to a Cosmos chain**
+ **Debugging a message to a non-EVM chain (e.g. a Cosmos or Sealevel chain)**
At the moment, the [Hyperlane Explorer](https://explorer.hyperlane.xyz) doesn't support non-EVM chains.
To check if your message has been delivered, select the destination chain and input your message ID below:
-
+
@@ -110,6 +121,9 @@ import CosmosMessageDelivered from "@site/src/components/CosmosMessageDelivered"
Example on Mintscan showing the log on an origin transaction.
+
+
+
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 79dfb70b..79ed736a 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -38,6 +38,7 @@ const config = {
},
plugins: [
+ './webpackPlugin',
[
"@docusaurus/plugin-client-redirects",
{
diff --git a/package.json b/package.json
index 665999b5..eea23d13 100644
--- a/package.json
+++ b/package.json
@@ -23,14 +23,25 @@
"@hyperlane-xyz/core": "5.3.0",
"@hyperlane-xyz/registry": "4.4.1",
"@hyperlane-xyz/sdk": "5.3.0",
+ "@hyperlane-xyz/utils": "^5.3.0",
"@mdx-js/react": "^3.0.0",
+ "assert": "^2.1.0",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^6.0.3",
"clsx": "^2.0.0",
+ "crypto-browserify": "^3.12.0",
+ "https-browserify": "^1.0.0",
"prism-react-renderer": "^2.1.0",
+ "process": "^0.11.10",
+ "querystring-es3": "^0.2.1",
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rehype-katex": "7",
- "remark-math": "6"
+ "remark-math": "6",
+ "stream-browserify": "^3.0.0",
+ "stream-http": "^3.2.0",
+ "url": "^0.11.4"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.1",
diff --git a/src/components/CosmosMessageDelivered.tsx b/src/components/NonEvmMessageDelivered.tsx
similarity index 53%
rename from src/components/CosmosMessageDelivered.tsx
rename to src/components/NonEvmMessageDelivered.tsx
index f5bb92c5..fa4ca9f7 100644
--- a/src/components/CosmosMessageDelivered.tsx
+++ b/src/components/NonEvmMessageDelivered.tsx
@@ -1,27 +1,40 @@
+import * as buffer from "buffer";
import { useState } from "react";
-import { chainMetadata } from "@hyperlane-xyz/registry";
+
+import { CosmWasmCoreAdapter, MultiProtocolCore, MultiProtocolProvider } from "@hyperlane-xyz/sdk";
+import { chainMetadata, chainAddresses } from "@hyperlane-xyz/registry";
+import { ProtocolType, strip0x } from "@hyperlane-xyz/utils";
import ChainDropdown from './ChainDropdown';
+import { useMultiProtocolProvider } from "../utils/registry";
+import useIsBrowser from "@docusaurus/useIsBrowser";
// TODO: these should be in the registry, but for now we'll hardcode them.
// Once they're in the registry, we can move away from this.
-const mailboxes = {
- 'neutron': 'neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4',
- 'injective': 'inj1palm2wtp6urg0c6j4f2ukv5u5ahdcrqek0sapt',
+const addressesOverrides = {
+ neutron: {
+ mailbox: 'neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4',
+ },
+ injective: {
+ mailbox: 'inj1palm2wtp6urg0c6j4f2ukv5u5ahdcrqek0sapt',
+ }
};
-const cosmosChains = Object.keys(mailboxes);
-
-// Adding @hyperlane-xyz/utils as a dependency breaks things, so we copy the function here.
-export function strip0x(hexstr: string) {
- return hexstr.startsWith('0x') ? hexstr.slice(2) : hexstr;
-}
-
-export default function CosmosMessageDelivered({
+export default function NonEvmMessageDelivered({
+ chains,
+}: {
+ chains: string[];
}) {
- const [destinationChain, setDestinationChain] = useState(cosmosChains[0]);
+ const isBrowser = useIsBrowser();
+ if (!isBrowser) {
+ // Difficulty polyfilling Buffer in the browser to be consumed by some Solana libs, so we do this instead
+ window.Buffer = buffer.Buffer;
+ }
+
+ const [destinationChain, setDestinationChain] = useState(chains[0]);
const [messageId, setMessageId] = useState('');
const [status, setStatus] = useState('');
+ const multiProvider = useMultiProtocolProvider();
const onButtonClick = async () => {
const strippedMessageId = strip0x(messageId);
@@ -31,45 +44,39 @@ export default function CosmosMessageDelivered({
}
const metadata = chainMetadata[destinationChain];
- const mailbox = mailboxes[destinationChain];
- if (!mailbox) {
- setStatus(`⛔️ No known Mailbox found for chain ${destinationChain}`);
- return;
- }
if (!metadata) {
- setStatus(`⛔️ No known Mailbox found for chain ${destinationChain}`);
+ setStatus(`⛔️ No metadata found for chain ${destinationChain}`);
return;
}
- const restUrl = metadata.restUrls?.[0]?.http;
- if (!restUrl) {
- setStatus(`⛔️ No available API set for chain ${destinationChain}`);
- return;
- }
- const payload = {
- mailbox: {
- message_delivered: {
- id: strippedMessageId,
- }
- }
- };
- const base64Payload = window.btoa(JSON.stringify(payload));
- const url = `${restUrl}/cosmwasm/wasm/v1/contract/${mailbox}/smart/${base64Payload}`;
+
+ const multiProtocolCore = MultiProtocolCore.fromAddressesMap(
+ // @ts-ignore - doesn't like the types of some recent Sealevel chains and the overrides we provide
+ {
+ ...chainAddresses,
+ ...addressesOverrides,
+ }, multiProvider);
+ const core = multiProtocolCore.adapter(destinationChain);
+
setStatus(`⏳ Checking if message is delivered...`);
- let responseJson;
- console.log(`Fetching from ${url}`);
+
+ let delivered = false;
try {
- const response = await fetch(url, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- responseJson = await response.json();
+ switch (metadata.protocol) {
+ case ProtocolType.Cosmos:
+ // `waitForMessageProcessed` is not implemented on cosmos -- instead we
+ // use the adapter directly.
+ const cosmosCore = core as CosmWasmCoreAdapter;
+ delivered = await cosmosCore.delivered(strip0x(messageId));
+ break;
+ default:
+ delivered = await core.waitForMessageProcessed(messageId, destinationChain, 0, 1);
+ break;
+ }
} catch (e) {
setStatus(`⛔️ Error checking message delivery: ${e}`);
return;
}
- const delivered = responseJson?.data?.delivered ?? false;
+
if (delivered) {
setStatus(`Message successfully delivered: ✅`);
} else {
@@ -85,7 +92,7 @@ export default function CosmosMessageDelivered({
}}>
diff --git a/src/components/SealevelWarpTransferInfo.tsx b/src/components/SealevelWarpTransferInfo.tsx
new file mode 100644
index 00000000..64ba66e8
--- /dev/null
+++ b/src/components/SealevelWarpTransferInfo.tsx
@@ -0,0 +1,111 @@
+import * as buffer from "buffer";
+import { useState } from "react";
+
+import { chainMetadata } from "@hyperlane-xyz/registry";
+import { addressToBytesEvm, bytesToAddressSol, ensure0x, hexOrBase58ToHex, strip0x } from "@hyperlane-xyz/utils";
+
+import ChainDropdown from './ChainDropdown';
+import { useMultiProtocolProvider } from "../utils/registry";
+import useIsBrowser from "@docusaurus/useIsBrowser";
+
+export default function NonEvmMessageDelivered({
+ chains,
+}: {
+ chains: string[];
+}) {
+ const isBrowser = useIsBrowser();
+ if (!isBrowser) {
+ // Difficulty polyfilling Buffer in the browser to be consumed by some Solana libs, so we do this instead
+ window.Buffer = buffer.Buffer;
+ }
+
+ const [originChain, setOriginChain] = useState(chains[0]);
+ const [txId, setTxId] = useState('');
+ const [status, setStatus] = useState('');
+ const multiProvider = useMultiProtocolProvider();
+
+ const setTransferRecipient = async () => {
+ const metadata = chainMetadata[originChain];
+ if (!metadata) {
+ setStatus(`⛔️ No metadata found for chain ${originChain}`);
+ return;
+ }
+
+ const sealevelProvider = multiProvider.getSolanaWeb3Provider(originChain)
+ setStatus(`⏳ Getting transaction...`);
+ let tx = await sealevelProvider.getParsedTransaction(txId);
+ if (!tx) {
+ setStatus(`⛔️ Transaction not found`);
+ return;
+ }
+
+ const finalInstruction = tx.transaction.message.instructions.length - 1;
+ // Types don't all include `data`, but in practice we have it.
+ const transferRemoteInstruction: any = tx.transaction.message.instructions[finalInstruction];
+ const transferRemoteData = transferRemoteInstruction.data;
+ if (!transferRemoteData) {
+ setStatus(`⛔️ No data found in instruction`);
+ return;
+ }
+
+ const hex = strip0x(hexOrBase58ToHex(transferRemoteData));
+ // The first 13 bytes (2 chars per byte) are the instruction discriminator. After that, we have the 32 byte recipient address.
+ const recipientHex = ensure0x(hex.slice(26, 26 + 64));
+ const recipientBytes = addressToBytesEvm(recipientHex);
+ const recipientBase58 = bytesToAddressSol(recipientBytes);
+
+ setStatus(`🕵️♂️ Transfer recipient (hex): ${recipientHex}\n🕵️♂️ Transfer recipient (base58): ${recipientBase58}`);
+ };
+
+ const onButtonClick = async () => {
+ try {
+ await setTransferRecipient();
+ } catch (e) {
+ setStatus(`⛔️ Error: ${e.message}`);
+ }
+ }
+
+ return (
+