Skip to content

Commit

Permalink
Merge pull request #145 from hyperlane-xyz/injective-msg-id
Browse files Browse the repository at this point in the history
Merge #144 into injective
  • Loading branch information
jmrossy authored Mar 15, 2024
2 parents 6d0bfe7 + 64759cb commit 38c8d56
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 69 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@hyperlane-xyz/warp-ui-template",
"description": "A web app template for building Hyperlane Warp Route UIs",
"version": "3.8.0",
"version": "3.8.1",
"author": "J M Rossy",
"dependencies": {
"@chakra-ui/next-js": "^2.1.5",
Expand All @@ -16,8 +16,8 @@
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.14",
"@hyperlane-xyz/sdk": "^3.8.0",
"@hyperlane-xyz/utils": "^3.8.0",
"@hyperlane-xyz/sdk": "^3.8.1",
"@hyperlane-xyz/utils": "^3.8.1",
"@hyperlane-xyz/widgets": "^3.8.0",
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6",
"@rainbow-me/rainbowkit": "1.3.0",
Expand Down
8 changes: 8 additions & 0 deletions src/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,11 @@ export function getIndexForToken(token?: IToken): number | undefined {
if (index >= 0) return index;
else return undefined;
}

export function tryFindToken(chain: ChainName, addressOrDenom?: string): IToken | null {
try {
return getWarpCore().findToken(chain, addressOrDenom);
} catch (error) {
return null;
}
}
3 changes: 2 additions & 1 deletion src/features/transfer/TransfersDetailsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export function TransfersDetailsModal({
<TransferProperty name="Sender Address" value={sender} url={fromUrl} />
<TransferProperty name="Recipient Address" value={recipient} url={toUrl} />
{token?.addressOrDenom && (
<TransferProperty name="Token Address" value={token.addressOrDenom} />
<TransferProperty name="Token Address or Denom" value={token.addressOrDenom} />
)}
{originTxHash && (
<TransferProperty
Expand All @@ -173,6 +173,7 @@ export function TransfersDetailsModal({
url={originTxUrl}
/>
)}
{msgId && <TransferProperty name="Message ID" value={msgId} />}
{explorerLink && (
<div className="flex justify-between">
<span className="text-gray-350 text-xs leading-normal tracking-wider">
Expand Down
18 changes: 9 additions & 9 deletions src/features/transfer/useTokenTransfer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useState } from 'react';
import { toast } from 'react-toastify';

import { WarpTxCategory } from '@hyperlane-xyz/sdk';
import { TypedTransactionReceipt, WarpTxCategory } from '@hyperlane-xyz/sdk';
import { toTitleCase, toWei } from '@hyperlane-xyz/utils';

import { toastTxSuccess } from '../../components/toast/TxSuccessToast';
Expand All @@ -16,6 +16,7 @@ import {
} from '../wallet/hooks/multiProtocol';

import { TransferContext, TransferFormValues, TransferStatus } from './types';
import { tryGetMsgIdFromTransferReceipt } from './utils';

export function useTokenTransfer(onDone?: () => void) {
const { transfers, addTransfer, updateTransferStatus } = useStore((s) => ({
Expand Down Expand Up @@ -138,28 +139,27 @@ async function executeTransfer({
});

const hashes: string[] = [];
let txReceipt: TypedTransactionReceipt | undefined = undefined;
for (const tx of txs) {
updateTransferStatus(transferIndex, (transferStatus = txCategoryToStatuses[tx.category][0]));
const { hash, confirm } = await sendTransaction({
tx: tx.transaction,
tx,
chainName: origin,
activeChainName: activeChain.chainName,
providerType: tx.type,
});
updateTransferStatus(transferIndex, (transferStatus = txCategoryToStatuses[tx.category][1]));
const receipt = await confirm();
txReceipt = await confirm();
const description = toTitleCase(tx.category);
logger.debug(`${description} transaction confirmed, hash:`, receipt.transactionHash);
toastTxSuccess(`${description} transaction sent!`, receipt.transactionHash, origin);
logger.debug(`${description} transaction confirmed, hash:`, hash);
toastTxSuccess(`${description} transaction sent!`, hash, origin);
hashes.push(hash);
}

// TODO
// const msgId = tryGetMsgIdFromTransferReceipt(transferReceipt);
const msgId = txReceipt ? tryGetMsgIdFromTransferReceipt(origin, txReceipt) : undefined;

updateTransferStatus(transferIndex, (transferStatus = TransferStatus.ConfirmedTransfer), {
originTxHash: hashes.at(-1),
msgId: '',
msgId,
});
} catch (error) {
logger.error(`Error at stage ${transferStatus}`, error);
Expand Down
48 changes: 39 additions & 9 deletions src/features/transfer/utils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import { TransactionReceipt } from 'viem';

import { HyperlaneCore } from '@hyperlane-xyz/sdk';
import {
ChainMap,
CoreAddresses,
MultiProtocolCore,
ProviderType,
TypedTransactionReceipt,
} from '@hyperlane-xyz/sdk';

import { getMultiProvider } from '../../context/context';
import { logger } from '../../utils/logger';

// TODO multiprotocol
export function tryGetMsgIdFromTransferReceipt(receipt: TransactionReceipt) {
export function tryGetMsgIdFromTransferReceipt(
origin: ChainName,
receipt: TypedTransactionReceipt,
) {
try {
// TODO viem
// @ts-ignore
const messages = HyperlaneCore.getDispatchedMessages(receipt);
// IBC transfers have no message IDs
if (receipt.type === ProviderType.CosmJs) return undefined;

if (receipt.type === ProviderType.Viem) {
// Massage viem type into ethers type because that's still what the
// SDK expects. In this case they're compatible.
receipt = {
type: ProviderType.EthersV5,
receipt: receipt.receipt as any,
};
}

const multiProvider = getMultiProvider();
const addressStubs = multiProvider
.getKnownChainNames()
.reduce<ChainMap<CoreAddresses>>((acc, chainName) => {
// Actual core addresses not required for the id extraction
acc[chainName] = {
validatorAnnounce: '',
proxyAdmin: '',
mailbox: '',
};
return acc;
}, {});
const core = new MultiProtocolCore(multiProvider, addressStubs);
const messages = core.extractMessageIds(origin, receipt);
if (messages.length) {
const msgId = messages[0].id;
const msgId = messages[0].messageId;
logger.debug('Message id found in logs', msgId);
return msgId;
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/features/wallet/SideBarMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SmallSpinner } from '../../components/animation/SmallSpinner';
import { ChainLogo } from '../../components/icons/ChainLogo';
import { Identicon } from '../../components/icons/Identicon';
import { PLACEHOLDER_COSMOS_CHAIN } from '../../consts/values';
import { getWarpCore } from '../../context/context';
import { tryFindToken } from '../../context/context';
import ArrowRightIcon from '../../images/icons/arrow-right.svg';
import CollapseIcon from '../../images/icons/collapse-icon.svg';
import Logout from '../../images/icons/logout.svg';
Expand Down Expand Up @@ -175,7 +175,7 @@ function TransferSummary({
onClick: () => void;
}) {
const { amount, origin, destination, status, timestamp, originTokenAddressOrDenom } = transfer;
const token = getWarpCore().findToken(origin, originTokenAddressOrDenom);
const token = tryFindToken(origin, originTokenAddressOrDenom);

return (
<button
Expand Down
34 changes: 20 additions & 14 deletions src/features/wallet/hooks/cosmos.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { DeliverTxResponse, ExecuteResult } from '@cosmjs/cosmwasm-stargate';
import { DeliverTxResponse, ExecuteResult, IndexedTx } from '@cosmjs/cosmwasm-stargate';
import { useChain, useChains } from '@cosmos-kit/react';
import { useCallback, useMemo } from 'react';
import { toast } from 'react-toastify';

import { ProviderType } from '@hyperlane-xyz/sdk';
import { HexString, ProtocolType } from '@hyperlane-xyz/utils';
import { ProviderType, TypedTransactionReceipt, WarpTypedTransaction } from '@hyperlane-xyz/sdk';
import { HexString, ProtocolType, assert } from '@hyperlane-xyz/utils';

import { PLACEHOLDER_COSMOS_CHAIN } from '../../../consts/values';
import { logger } from '../../../utils/logger';
Expand Down Expand Up @@ -67,32 +67,38 @@ export function useCosmosTransactionFns(): ChainTransactionFns {
tx,
chainName,
activeChainName,
providerType,
}: {
tx: any;
tx: WarpTypedTransaction;
chainName: ChainName;
activeChainName?: ChainName;
providerType?: ProviderType;
}) => {
const chainContext = chainToContext[chainName];
if (!chainContext?.address) throw new Error(`Cosmos wallet not connected for ${chainName}`);

if (activeChainName && activeChainName !== chainName) await onSwitchNetwork(chainName);

logger.debug(`Sending tx on chain ${chainName}`);
const { getSigningCosmWasmClient, getSigningStargateClient } = chainContext;
let result: ExecuteResult | DeliverTxResponse;
if (providerType === ProviderType.CosmJsWasm) {
let txDetails: IndexedTx | null;
if (tx.type === ProviderType.CosmJsWasm) {
const client = await getSigningCosmWasmClient();
result = await client.executeMultiple(chainContext.address, [tx], 'auto');
} else if (providerType === ProviderType.CosmJs) {
result = await client.executeMultiple(chainContext.address, [tx.transaction], 'auto');
txDetails = await client.getTx(result.transactionHash);
} else if (tx.type === ProviderType.CosmJs) {
const client = await getSigningStargateClient();
result = await client.signAndBroadcast(chainContext.address, [tx], 'auto');
result = await client.signAndBroadcast(chainContext.address, [tx.transaction], 'auto');
txDetails = await client.getTx(result.transactionHash);
} else {
throw new Error(`Invalid cosmos provider type ${providerType}`);
throw new Error(`Invalid cosmos provider type ${tx.type}`);
}

const confirm = async () => {
if (result.transactionHash) return result;
throw new Error(`Cosmos tx ${result.transactionHash} failed: ${JSON.stringify(result)}`);
const confirm = async (): Promise<TypedTransactionReceipt> => {
assert(txDetails, `Cosmos tx failed: ${JSON.stringify(result)}`);
return {
type: tx.type,
receipt: { ...txDetails, transactionHash: result.transactionHash },
};
};
return { hash: result.transactionHash, confirm };
},
Expand Down
29 changes: 23 additions & 6 deletions src/features/wallet/hooks/evm.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useConnectModal } from '@rainbow-me/rainbowkit';
import { sendTransaction, switchNetwork, waitForTransaction } from '@wagmi/core';
import { getNetwork, sendTransaction, switchNetwork, waitForTransaction } from '@wagmi/core';
import { useCallback, useMemo } from 'react';
import { useAccount, useDisconnect, useNetwork } from 'wagmi';

import { ProtocolType, sleep } from '@hyperlane-xyz/utils';
import { ProviderType, TypedTransactionReceipt, WarpTypedTransaction } from '@hyperlane-xyz/sdk';
import { ProtocolType, assert, sleep } from '@hyperlane-xyz/utils';

import { logger } from '../../../utils/logger';
import { getChainMetadata, tryGetChainMetadata } from '../../chains/utils';
Expand Down Expand Up @@ -66,19 +67,35 @@ export function useEvmTransactionFns(): ChainTransactionFns {
chainName,
activeChainName,
}: {
tx: any;
tx: WarpTypedTransaction;
chainName: ChainName;
activeChainName?: ChainName;
}) => {
if (tx.type !== ProviderType.EthersV5) throw new Error(`Unsupported tx type: ${tx.type}`);

// If the active chain is different from tx origin chain, try to switch network first
if (activeChainName && activeChainName !== chainName) await onSwitchNetwork(chainName);
logger.debug(`Sending tx on chain ${chainName}`);

// Since the network switching is not foolproof, we also force a network check here
const chainId = getChainMetadata(chainName).chainId as number;
const wagmiTx = ethers5TxToWagmiTx(tx);
logger.debug('Checking wallet current chain');
const latestNetwork = await getNetwork();
assert(
latestNetwork.chain?.id === chainId,
`Wallet not on chain ${chainName} (ChainMismatchError)`,
);

logger.debug(`Sending tx on chain ${chainName}`);
const wagmiTx = ethers5TxToWagmiTx(tx.transaction);
const { hash } = await sendTransaction({
chainId,
...wagmiTx,
});
const confirm = () => waitForTransaction({ chainId, hash, confirmations: 1 });
const confirm = (): Promise<TypedTransactionReceipt> =>
waitForTransaction({ chainId, hash, confirmations: 1 }).then((r) => ({
type: ProviderType.Viem,
receipt: r,
}));
return { hash, confirm };
},
[onSwitchNetwork],
Expand Down
19 changes: 14 additions & 5 deletions src/features/wallet/hooks/solana.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { useWalletModal } from '@solana/wallet-adapter-react-ui';
import { Connection, Transaction } from '@solana/web3.js';
import { Connection } from '@solana/web3.js';
import { useCallback, useMemo } from 'react';
import { toast } from 'react-toastify';

import { ProviderType, TypedTransactionReceipt, WarpTypedTransaction } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';

import { getMultiProvider } from '../../../context/context';
Expand Down Expand Up @@ -65,10 +66,11 @@ export function useSolTransactionFns(): ChainTransactionFns {
chainName,
activeChainName,
}: {
tx: Transaction;
tx: WarpTypedTransaction;
chainName: ChainName;
activeChainName?: ChainName;
}) => {
if (tx.type !== ProviderType.SolanaWeb3) throw new Error(`Unsupported tx type: ${tx.type}`);
if (activeChainName && activeChainName !== chainName) await onSwitchNetwork(chainName);
const rpcUrl = getMultiProvider().getRpcUrl(chainName);
const connection = new Connection(rpcUrl, 'confirmed');
Expand All @@ -78,10 +80,17 @@ export function useSolTransactionFns(): ChainTransactionFns {
} = await connection.getLatestBlockhashAndContext();

logger.debug(`Sending tx on chain ${chainName}`);
const signature = await sendSolTransaction(tx, connection, { minContextSlot });
const signature = await sendSolTransaction(tx.transaction, connection, { minContextSlot });

const confirm = (): Promise<TypedTransactionReceipt> =>
connection
.confirmTransaction({ blockhash, lastValidBlockHeight, signature })
.then(() => connection.getTransaction(signature))
.then((r) => ({
type: ProviderType.SolanaWeb3,
receipt: r!,
}));

const confirm = () =>
connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature });
return { hash: signature, confirm };
},
[onSwitchNetwork, sendSolTransaction],
Expand Down
8 changes: 5 additions & 3 deletions src/features/wallet/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ProviderType } from '@hyperlane-xyz/sdk';
import { TypedTransactionReceipt, WarpTypedTransaction } from '@hyperlane-xyz/sdk';
import { HexString, ProtocolType } from '@hyperlane-xyz/utils';

export interface ChainAddress {
Expand All @@ -23,11 +23,13 @@ export interface ActiveChainInfo {
chainName?: ChainName;
}

export type SendTransactionFn<TxReq = any, TxResp = any> = (params: {
export type SendTransactionFn<
TxReq extends WarpTypedTransaction = WarpTypedTransaction,
TxResp extends TypedTransactionReceipt = TypedTransactionReceipt,
> = (params: {
tx: TxReq;
chainName: ChainName;
activeChainName?: ChainName;
providerType?: ProviderType;
}) => Promise<{ hash: string; confirm: () => Promise<TxResp> }>;

export type SwitchNetworkFn = (chainName: ChainName) => Promise<void>;
Expand Down
Loading

0 comments on commit 38c8d56

Please sign in to comment.